From 197f355cf74944ac1f6ea9256b69784f2c02bea8 Mon Sep 17 00:00:00 2001 From: master3395 Date: Sun, 15 Feb 2026 23:24:04 +0100 Subject: [PATCH] Plugins: fix 404 - add plugin roots to sys.path before loading URLs - pluginHolder/urls: insert /usr/local/CyberCP and source paths at top of module so __import__(plugin_name + '.urls') finds plugin packages (fixes No module named 'X.urls'). Register plugin routes before catch-all help route. - pluginHolder/views: add debug_loaded_plugins API and log full traceback on plugin import failure. panelAccess/emailMarketing settings/ routes added earlier; cspManager resilient to missing DB table. Author: master3395 --- pluginHolder/urls.py | 18 ++++++++++++++++-- pluginHolder/views.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/pluginHolder/urls.py b/pluginHolder/urls.py index c76049d98..446fc7125 100644 --- a/pluginHolder/urls.py +++ b/pluginHolder/urls.py @@ -13,10 +13,17 @@ from django.urls import path, include import os import sys +# Ensure plugin roots are on sys.path first so __import__(plugin_name + '.urls') can find packages +_INSTALLED_PLUGINS_PATH = '/usr/local/CyberCP' +_PLUGIN_SOURCE_PATHS = ['/home/cyberpanel/plugins', '/home/cyberpanel-plugins'] +for _p in [_INSTALLED_PLUGINS_PATH] + _PLUGIN_SOURCE_PATHS: + if _p and os.path.isdir(_p) and _p not in sys.path: + sys.path.insert(0, _p) + from . import views # Installed plugins live under this path (must match pluginInstaller and pluginHolder.views) -INSTALLED_PLUGINS_PATH = '/usr/local/CyberCP' +INSTALLED_PLUGINS_PATH = _INSTALLED_PLUGINS_PATH # Source paths for plugins (same as pluginHolder.views PLUGIN_SOURCE_PATHS) # Checked when plugin is not under INSTALLED_PLUGINS_PATH so URLs still work @@ -95,22 +102,29 @@ urlpatterns = [ path('api/store/upgrade//', views.upgrade_plugin, name='upgrade_plugin'), path('api/backups//', views.get_plugin_backups, name='get_plugin_backups'), path('api/revert//', views.revert_plugin, name='revert_plugin'), + path('api/debug-plugins/', views.debug_loaded_plugins, name='debug_loaded_plugins'), ] # Include each installed plugin's URLs *before* the catch-all so /plugins//settings/ etc. match +_loaded_plugins = [] +_failed_plugins = {} for _plugin_name, _path_parent in _get_installed_plugin_list(): try: if _path_parent not in sys.path: sys.path.insert(0, _path_parent) __import__(_plugin_name + '.urls') urlpatterns.append(path(_plugin_name + '/', include(_plugin_name + '.urls'))) - except (ImportError, AttributeError) as e: + _loaded_plugins.append(_plugin_name) + except Exception as e: + import traceback + _failed_plugins[_plugin_name] = str(e) try: from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as _logging _logging.writeToFile( 'pluginHolder.urls: Skipping plugin "%s" (urls not loadable): %s' % (_plugin_name, e) ) + _logging.writeToFile(traceback.format_exc()) except Exception: pass diff --git a/pluginHolder/views.py b/pluginHolder/views.py index 066725ad5..166a4f0f5 100644 --- a/pluginHolder/views.py +++ b/pluginHolder/views.py @@ -1854,6 +1854,24 @@ def install_from_store(request, plugin_name): 'error': str(e) }, status=500) +@csrf_exempt +@require_http_methods(["GET"]) +def debug_loaded_plugins(request): + """Return which plugins have URL routes loaded and which failed (for diagnosing 404s).""" + try: + import pluginHolder.urls as urls_mod + loaded = list(getattr(urls_mod, '_loaded_plugins', [])) + failed = dict(getattr(urls_mod, '_failed_plugins', {})) + return JsonResponse({ + 'success': True, + 'loaded': loaded, + 'failed': failed, + 'loaded_count': len(loaded), + 'failed_count': len(failed), + }, json_dumps_params={'indent': 2}) + except Exception as e: + return JsonResponse({'success': False, 'error': str(e)}, status=500) + def plugin_help(request, plugin_name): """Plugin-specific help page - shows plugin information, version history, and help content""" mailUtilities.checkHome()