utp_service/backend/app/plugins/loader.py
2026-04-07 17:14:19 +03:00

80 lines
3 KiB
Python

"""
PluginLoader — registers action plugins at startup and syncs them with the DB.
Startup flow:
1. For each builtin action, INSERT into `plugins` table (skip if already exists).
This preserves the `enabled` flag set by admins.
2. Load the list of enabled plugin names from DB.
3. Register only enabled actions in the registry.
Hot enable/disable (via Admin API):
- Disable: set DB.enabled=False, call action_registry.unregister(name)
- Enable: set DB.enabled=True, call action_registry.register(BUILTIN_ACTIONS[name])
"""
from datetime import datetime, timezone
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from app.models.plugin import Plugin
from app.plugins.base import BaseAction
from app.plugins.builtin.camera.get_status import CameraGetStatusAction
from app.plugins.builtin.camera.set_ntp import CameraSetNTPAction
from app.plugins.builtin.common.ping import PingAction
from app.plugins.builtin.mikrotik.get_config import MikrotikGetConfigAction
from app.plugins.builtin.mikrotik.get_ntp import MikrotikGetNTPAction
from app.plugins.builtin.mikrotik.set_ntp import MikrotikSetNTPAction
from app.plugins.builtin.ssh.get_file import GetFileAction
from app.plugins.builtin.ssh.get_timedatectl import GetTimedatectlAction
from app.plugins.builtin.ssh.ssh_command import SSHCommandAction
from app.plugins.registry import action_registry
from app.services.database import AsyncSessionLocal
# Public dict — used by Admin router for hot enable/disable
BUILTIN_ACTIONS: dict[str, BaseAction] = {
a.name: a
for a in [
PingAction(),
SSHCommandAction(),
GetTimedatectlAction(),
GetFileAction(),
MikrotikGetConfigAction(),
MikrotikGetNTPAction(),
MikrotikSetNTPAction(),
CameraGetStatusAction(),
CameraSetNTPAction(),
]
}
async def load_plugins() -> None:
now = datetime.now(timezone.utc)
async with AsyncSessionLocal() as db:
async with db.begin():
for action in BUILTIN_ACTIONS.values():
stmt = (
pg_insert(Plugin)
.values(
name=action.name,
display_name=action.display_name,
description=(action.__class__.__doc__ or "").strip() or None,
enabled=True,
registered_at=now,
)
.on_conflict_do_nothing(index_elements=["name"])
)
await db.execute(stmt)
# Load enabled plugins from DB
async with AsyncSessionLocal() as db:
result = await db.execute(select(Plugin.name).where(Plugin.enabled == True))
enabled_names = {row[0] for row in result.all()}
# Register only enabled actions
for name, action in BUILTIN_ACTIONS.items():
if name in enabled_names:
action_registry.register(action)
print(f"[plugins] Registered {len(enabled_names)}/{len(BUILTIN_ACTIONS)} plugin(s)")