Added plugins manager

This commit is contained in:
Donald Zou
2025-08-13 21:41:28 +08:00
parent 2aa2b15234
commit 39d01015e5
4 changed files with 124 additions and 6 deletions

View File

@@ -37,6 +37,7 @@ from client import createClientBlueprint
from logging.config import dictConfig
from modules.DashboardClients import DashboardClients
from modules.DashboardPlugin import DashboardPlugin
dictConfig({
'version': 1,
@@ -1442,12 +1443,11 @@ WireguardConfigurations: dict[str, WireguardConfiguration] = {}
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig, WireguardConfigurations)
AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations)
DashboardLogger: DashboardLogger = DashboardLogger()
DashboardPlugin: DashboardPlugin = DashboardPlugin(app, WireguardConfigurations)
InitWireguardConfigurationsList(startup=True)
import plugins.rrd_data.main as rrd_data
with app.app_context():
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations)
@@ -1458,12 +1458,10 @@ def startThreads():
bgThread.start()
scheduleJobThread = threading.Thread(target=peerJobScheduleBackgroundThread, daemon=True)
scheduleJobThread.start()
t = threading.Thread(target=rrd_data.main, args=(WireguardConfigurations,), daemon=True)
t.start()
if __name__ == "__main__":
startThreads()
DashboardPlugin.startThreads()
# logging.getLogger().addHandler(logging.StreamHandler())
app.logger.addHandler(logging.StreamHandler())
app.run(host=app_ip, debug=False, port=app_port)

View File

@@ -7,6 +7,7 @@ date = datetime.today().strftime('%Y_%m_%d_%H_%M_%S')
def post_worker_init(worker):
dashboard.startThreads()
dashboard.DashboardPlugin.startThreads()
worker_class = 'gthread'
workers = 1
@@ -23,4 +24,4 @@ pythonpath = "., ./modules"
print(f"[Gunicorn] WGDashboard w/ Gunicorn will be running on {bind}", flush=True)
print(f"[Gunicorn] Access log file is at {accesslog}", flush=True)
print(f"[Gunicorn] Error log file is at {errorlog}", flush=True)
print(f"[Gunicorn] Error log file is at {errorlog}", flush=True)

View File

@@ -0,0 +1,117 @@
import os
import sys
import importlib.util
from pathlib import Path
from typing import Dict, Callable, List, Optional
import threading
class DashboardPlugin:
def __init__(self, app, WireguardConfigurations, directory: str = 'plugins'):
self.directory = Path('plugins')
self.loadedPlugins: dict[str, Callable] = {}
self.errorPlugins: List[str] = []
self.logger = app.logger
self.WireguardConfigurations = WireguardConfigurations
def startThreads(self):
self.loadAllPlugins()
self.executeAllPlugins()
def preparePlugins(self) -> list[Path]:
readyPlugins = []
if not self.directory.exists():
self.logger.error("Failed to load ./plugins directory")
return []
for plugin in self.directory.iterdir():
if plugin.is_dir():
codeFile = plugin / "main.py"
if codeFile.exists():
self.logger.info(f"Prepared plugin: {plugin.name}")
readyPlugins.append(plugin)
return readyPlugins
def loadPlugin(self, path: Path) -> Optional[Callable]:
pluginName = path.name
codeFile = path / "main.py"
try:
spec = importlib.util.spec_from_file_location(
f"WGDashboardPlugin_{pluginName}",
codeFile
)
if spec is None or spec.loader is None:
raise ImportError(f"Failed to create spec for {pluginName}")
module = importlib.util.module_from_spec(spec)
plugin_dir_str = str(path)
if plugin_dir_str not in sys.path:
sys.path.insert(0, plugin_dir_str)
try:
spec.loader.exec_module(module)
finally:
if plugin_dir_str in sys.path:
sys.path.remove(plugin_dir_str)
if hasattr(module, 'main'):
main_func = getattr(module, 'main')
if callable(main_func):
self.logger.info(f"Successfully loaded plugin [{pluginName}]")
return main_func
else:
raise AttributeError(f"'main' in {pluginName} is not callable")
else:
raise AttributeError(f"Plugin {pluginName} does not have a 'main' function")
except Exception as e:
self.logger.error(f"Failed to load the plugin [{pluginName}]. Reason: {str(e)}")
self.errorPlugins.append(pluginName)
return None
def loadAllPlugins(self):
self.loadedPlugins.clear()
self.errorPlugins.clear()
preparedPlugins = self.preparePlugins()
for plugin in preparedPlugins:
pluginName = plugin.name
mainFunction = self.loadPlugin(plugin)
if mainFunction:
self.loadedPlugins[pluginName] = mainFunction
if self.errorPlugins:
self.logger.warning(f"Failed to load {len(self.errorPlugins)} plugin(s): {self.errorPlugins}")
def executePlugin(self, pluginName: str):
if pluginName not in self.loadedPlugins.keys():
self.logger.error(f"Failed to execute plugin [{pluginName}]. Reason: Not loaded")
return False
plugin = self.loadedPlugins.get(pluginName)
try:
t = threading.Thread(target=plugin, args=(self.WireguardConfigurations,), daemon=True)
t.name = f'WGDashboardPlugin_{pluginName}'
t.start()
if t.is_alive():
self.logger.info(f"Execute plugin [{pluginName}] success. PID: {t.native_id}")
except Exception as e:
self.logger.error(f"Failed to execute plugin [{pluginName}]. Reason: {str(e)}")
return False
return True
def executeAllPlugins(self):
for plugin in self.loadedPlugins.keys():
self.executePlugin(plugin)

View File

@@ -0,0 +1,2 @@
def main(WireguardConfigurations):
print("This is a plugin")