фикс категорий задач
This commit is contained in:
parent
6bbd4b6dab
commit
af1a14a6a5
3 changed files with 79 additions and 4 deletions
|
|
@ -50,3 +50,39 @@ class BaseAction(ABC):
|
||||||
|
|
||||||
def is_compatible(self, category: str) -> bool:
|
def is_compatible(self, category: str) -> bool:
|
||||||
return not self.compatible_categories or category in self.compatible_categories
|
return not self.compatible_categories or category in self.compatible_categories
|
||||||
|
|
||||||
|
|
||||||
|
class CompositeAction(BaseAction):
|
||||||
|
"""Dispatches to different sub-actions based on device category.
|
||||||
|
|
||||||
|
Define routing as {category: action_instance}. The composite action's
|
||||||
|
compatible_categories is derived automatically from the routing keys.
|
||||||
|
Devices with a category not in routing are skipped with an error result.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
display_name: str,
|
||||||
|
routing: dict[str, "BaseAction"],
|
||||||
|
params_schema: list[dict] | None = None,
|
||||||
|
) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.display_name = display_name
|
||||||
|
self._routing = routing
|
||||||
|
self.compatible_categories = list(routing.keys())
|
||||||
|
self.params_schema = params_schema or []
|
||||||
|
|
||||||
|
async def execute(
|
||||||
|
self,
|
||||||
|
device: Device,
|
||||||
|
obj: Object,
|
||||||
|
params: dict,
|
||||||
|
emit: Emitter,
|
||||||
|
) -> ActionResult:
|
||||||
|
action = self._routing.get(device.category)
|
||||||
|
if action is None:
|
||||||
|
msg = f"[{device.ip}] Нет обработчика для категории '{device.category}'"
|
||||||
|
await emit(msg, "error")
|
||||||
|
return ActionResult(success=False, error=msg)
|
||||||
|
return await action.execute(device, obj, params, emit)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from sqlalchemy import select
|
||||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||||
|
|
||||||
from app.models.plugin import Plugin
|
from app.models.plugin import Plugin
|
||||||
from app.plugins.base import BaseAction
|
from app.plugins.base import BaseAction, CompositeAction
|
||||||
from app.plugins.builtin.camera.get_status import CameraGetStatusAction
|
from app.plugins.builtin.camera.get_status import CameraGetStatusAction
|
||||||
from app.plugins.builtin.camera.set_ntp import CameraSetNTPAction
|
from app.plugins.builtin.camera.set_ntp import CameraSetNTPAction
|
||||||
from app.plugins.builtin.common.ping import PingAction
|
from app.plugins.builtin.common.ping import PingAction
|
||||||
|
|
@ -32,6 +32,27 @@ from app.plugins.builtin.ssh.ssh_command import SSHCommandAction
|
||||||
from app.plugins.registry import action_registry
|
from app.plugins.registry import action_registry
|
||||||
from app.services.database import AsyncSessionLocal
|
from app.services.database import AsyncSessionLocal
|
||||||
|
|
||||||
|
# Shared sub-action instances reused inside composite actions
|
||||||
|
_timedatectl = GetTimedatectlAction()
|
||||||
|
_mikrotik_get_ntp = MikrotikGetNTPAction()
|
||||||
|
_camera_get_status = CameraGetStatusAction()
|
||||||
|
|
||||||
|
# Composite: Get NTP Info — routes by device category
|
||||||
|
_get_ntp_info = CompositeAction(
|
||||||
|
name="get_ntp_info",
|
||||||
|
display_name="Get NTP Info",
|
||||||
|
routing={
|
||||||
|
"main_server": _timedatectl,
|
||||||
|
"vm": _timedatectl,
|
||||||
|
"server": _timedatectl,
|
||||||
|
"raspberry": _timedatectl,
|
||||||
|
"embedded": _timedatectl,
|
||||||
|
"router": _mikrotik_get_ntp,
|
||||||
|
"mikrotik": _mikrotik_get_ntp,
|
||||||
|
"camera": _camera_get_status,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
# Public dict — used by Admin router for hot enable/disable
|
# Public dict — used by Admin router for hot enable/disable
|
||||||
BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
||||||
a.name: a
|
a.name: a
|
||||||
|
|
@ -39,13 +60,14 @@ BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
||||||
PingAction(),
|
PingAction(),
|
||||||
SSHCommandAction(),
|
SSHCommandAction(),
|
||||||
DebianSystemInfoAction(),
|
DebianSystemInfoAction(),
|
||||||
GetTimedatectlAction(),
|
_timedatectl,
|
||||||
GetFileAction(),
|
GetFileAction(),
|
||||||
MikrotikGetConfigAction(),
|
MikrotikGetConfigAction(),
|
||||||
MikrotikGetNTPAction(),
|
_mikrotik_get_ntp,
|
||||||
MikrotikSetNTPAction(),
|
MikrotikSetNTPAction(),
|
||||||
CameraGetStatusAction(),
|
_camera_get_status,
|
||||||
CameraSetNTPAction(),
|
CameraSetNTPAction(),
|
||||||
|
_get_ntp_info,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,23 @@ async def _run_task(task_id: str) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
targets = await _resolve_scope(scope)
|
targets = await _resolve_scope(scope)
|
||||||
|
|
||||||
|
# Filter devices by action compatibility (compatible_categories)
|
||||||
|
if action.compatible_categories:
|
||||||
|
compatible = [(d, o) for d, o in targets if action.is_compatible(d.category)]
|
||||||
|
skipped = [(d, o) for d, o in targets if not action.is_compatible(d.category)]
|
||||||
|
if skipped:
|
||||||
|
skipped_info = ", ".join(
|
||||||
|
f"{d.ip} ({d.category})" for d, _ in skipped[:10]
|
||||||
|
)
|
||||||
|
suffix = f" и ещё {len(skipped) - 10}" if len(skipped) > 10 else ""
|
||||||
|
await _emit(
|
||||||
|
task_id,
|
||||||
|
f"Пропущено {len(skipped)} несовместимых устройств: {skipped_info}{suffix}",
|
||||||
|
"warn",
|
||||||
|
)
|
||||||
|
targets = compatible
|
||||||
|
|
||||||
if not targets:
|
if not targets:
|
||||||
async with AsyncSessionLocal() as db:
|
async with AsyncSessionLocal() as db:
|
||||||
async with db.begin():
|
async with db.begin():
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue