фикс категорий задач
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:
|
||||
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 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.set_ntp import CameraSetNTPAction
|
||||
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.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
|
||||
BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
||||
a.name: a
|
||||
|
|
@ -39,13 +60,14 @@ BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
|||
PingAction(),
|
||||
SSHCommandAction(),
|
||||
DebianSystemInfoAction(),
|
||||
GetTimedatectlAction(),
|
||||
_timedatectl,
|
||||
GetFileAction(),
|
||||
MikrotikGetConfigAction(),
|
||||
MikrotikGetNTPAction(),
|
||||
_mikrotik_get_ntp,
|
||||
MikrotikSetNTPAction(),
|
||||
CameraGetStatusAction(),
|
||||
_camera_get_status,
|
||||
CameraSetNTPAction(),
|
||||
_get_ntp_info,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -233,6 +233,23 @@ async def _run_task(task_id: str) -> None:
|
|||
return
|
||||
|
||||
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:
|
||||
async with AsyncSessionLocal() as db:
|
||||
async with db.begin():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue