From be1e98e18a0c924c28f2264ab01d08714c0d30d8 Mon Sep 17 00:00:00 2001 From: dv Date: Fri, 10 Apr 2026 16:19:31 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B0=D1=87=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/plugins/base.py | 8 ++++++-- backend/app/plugins/builtin/mikrotik/get_config.py | 4 ++-- backend/app/plugins/builtin/mikrotik/get_ntp.py | 4 ++-- backend/app/plugins/builtin/mikrotik/set_ntp.py | 6 +++--- backend/app/plugins/builtin/ssh/debian_system_info.py | 4 ++-- backend/app/plugins/builtin/ssh/get_file.py | 4 ++-- backend/app/plugins/builtin/ssh/get_timedatectl.py | 4 ++-- backend/app/plugins/builtin/ssh/ssh_command.py | 4 ++-- backend/app/services/task.py | 4 +++- 9 files changed, 24 insertions(+), 18 deletions(-) diff --git a/backend/app/plugins/base.py b/backend/app/plugins/base.py index deaf893..cbf1686 100644 --- a/backend/app/plugins/base.py +++ b/backend/app/plugins/base.py @@ -7,7 +7,9 @@ and an emit() callback to write progress messages to the SSE stream. from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import Any, Awaitable, Callable +from typing import Any, Awaitable, Callable, Optional + +from sqlalchemy.ext.asyncio import AsyncSession from app.models.device import Device from app.models.object import Object @@ -46,6 +48,7 @@ class BaseAction(ABC): obj: Object, params: dict, emit: Emitter, + db: Optional[AsyncSession] = None, ) -> ActionResult: ... def is_compatible(self, category: str) -> bool: @@ -79,10 +82,11 @@ class CompositeAction(BaseAction): obj: Object, params: dict, emit: Emitter, + db: Optional[AsyncSession] = None, ) -> 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) + return await action.execute(device, obj, params, emit, db=db) diff --git a/backend/app/plugins/builtin/mikrotik/get_config.py b/backend/app/plugins/builtin/mikrotik/get_config.py index d81c850..93ecb5c 100644 --- a/backend/app/plugins/builtin/mikrotik/get_config.py +++ b/backend/app/plugins/builtin/mikrotik/get_config.py @@ -19,12 +19,12 @@ class MikrotikGetConfigAction(BaseAction): params_schema = [] async def execute( - self, device: Device, obj: Object, params: dict, emit: Emitter + self, device: Device, obj: Object, params: dict, emit: Emitter, db=None ) -> ActionResult: await emit(f"[{device.ip}] Running /export compact", "info") result = await ssh_service.run_command( - device, obj, "/export compact", timeout=60 + device, obj, "/export compact", timeout=60, db=db ) if not result.success and not result.stdout: diff --git a/backend/app/plugins/builtin/mikrotik/get_ntp.py b/backend/app/plugins/builtin/mikrotik/get_ntp.py index 19e6be1..6639ce5 100644 --- a/backend/app/plugins/builtin/mikrotik/get_ntp.py +++ b/backend/app/plugins/builtin/mikrotik/get_ntp.py @@ -29,12 +29,12 @@ class MikrotikGetNTPAction(BaseAction): params_schema = [] async def execute( - self, device: Device, obj: Object, params: dict, emit: Emitter + self, device: Device, obj: Object, params: dict, emit: Emitter, db=None ) -> ActionResult: await emit(f"[{device.ip}] /system ntp client print", "info") result = await ssh_service.run_command( - device, obj, "/system ntp client print" + device, obj, "/system ntp client print", db=db ) if result.error: diff --git a/backend/app/plugins/builtin/mikrotik/set_ntp.py b/backend/app/plugins/builtin/mikrotik/set_ntp.py index 62f699f..4a737a1 100644 --- a/backend/app/plugins/builtin/mikrotik/set_ntp.py +++ b/backend/app/plugins/builtin/mikrotik/set_ntp.py @@ -23,7 +23,7 @@ class MikrotikSetNTPAction(BaseAction): ] async def execute( - self, device: Device, obj: Object, params: dict, emit: Emitter + self, device: Device, obj: Object, params: dict, emit: Emitter, db=None ) -> ActionResult: server = params.get("server", "").strip() if not server: @@ -37,7 +37,7 @@ class MikrotikSetNTPAction(BaseAction): for cmd in commands: await emit(f"[{device.ip}] {cmd}", "info") - result = await ssh_service.run_command(device, obj, cmd) + result = await ssh_service.run_command(device, obj, cmd, db=db) if result.error: await emit(f"[{device.ip}] Error: {result.error}", "error") return ActionResult( @@ -49,7 +49,7 @@ class MikrotikSetNTPAction(BaseAction): ) # Verify - verify = await ssh_service.run_command(device, obj, "/system ntp client print") + verify = await ssh_service.run_command(device, obj, "/system ntp client print", db=db) await emit(f"[{device.ip}] Verify: {verify.stdout[:200]}", "info") return ActionResult( diff --git a/backend/app/plugins/builtin/ssh/debian_system_info.py b/backend/app/plugins/builtin/ssh/debian_system_info.py index c705405..d2dd326 100644 --- a/backend/app/plugins/builtin/ssh/debian_system_info.py +++ b/backend/app/plugins/builtin/ssh/debian_system_info.py @@ -26,10 +26,10 @@ class DebianSystemInfoAction(BaseAction): compatible_categories = ["server", "raspberry", "embedded", "other"] params_schema = [] - async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter) -> ActionResult: + async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter, db=None) -> ActionResult: await emit(f"[{device.ip}] Collecting system info...", "info") - result = await ssh_service.run_command(device, obj, _CMD, timeout=30) + result = await ssh_service.run_command(device, obj, _CMD, timeout=30, db=db) if result.error: await emit(f"[{device.ip}] Error: {result.error}", "error") diff --git a/backend/app/plugins/builtin/ssh/get_file.py b/backend/app/plugins/builtin/ssh/get_file.py index 4a9c357..0ce247a 100644 --- a/backend/app/plugins/builtin/ssh/get_file.py +++ b/backend/app/plugins/builtin/ssh/get_file.py @@ -26,14 +26,14 @@ class GetFileAction(BaseAction): ] async def execute( - self, device: Device, obj: Object, params: dict, emit: Emitter + self, device: Device, obj: Object, params: dict, emit: Emitter, db=None ) -> ActionResult: path = params.get("path", "").strip() if not path: return ActionResult(success=False, error="No file path specified") await emit(f"[{device.ip}] Reading {path}", "info") - result = await ssh_service.run_command(device, obj, f"cat {path}") + result = await ssh_service.run_command(device, obj, f"cat {path}", db=db) if not result.success: msg = result.error or result.stderr or f"exit code {result.exit_code}" diff --git a/backend/app/plugins/builtin/ssh/get_timedatectl.py b/backend/app/plugins/builtin/ssh/get_timedatectl.py index 4edff0a..79bbdbb 100644 --- a/backend/app/plugins/builtin/ssh/get_timedatectl.py +++ b/backend/app/plugins/builtin/ssh/get_timedatectl.py @@ -10,9 +10,9 @@ class GetTimedatectlAction(BaseAction): compatible_categories = ["raspberry", "server"] params_schema = [] - async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter) -> ActionResult: + async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter, db=None) -> ActionResult: await emit(f"[{device.ip}] Running timedatectl...", "info") - result = await ssh_service.run_command(device, obj, "timedatectl show --no-pager 2>/dev/null || timedatectl") + result = await ssh_service.run_command(device, obj, "timedatectl show --no-pager 2>/dev/null || timedatectl", db=db) if result.error: await emit(f"[{device.ip}] Error: {result.error}", "error") diff --git a/backend/app/plugins/builtin/ssh/ssh_command.py b/backend/app/plugins/builtin/ssh/ssh_command.py index 82a55e7..0fa55a7 100644 --- a/backend/app/plugins/builtin/ssh/ssh_command.py +++ b/backend/app/plugins/builtin/ssh/ssh_command.py @@ -12,13 +12,13 @@ class SSHCommandAction(BaseAction): {"name": "command", "type": "string", "label": "Command", "required": True}, ] - async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter) -> ActionResult: + async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter, db=None) -> ActionResult: command = params.get("command", "").strip() if not command: return ActionResult(success=False, error="No command specified") await emit(f"[{device.ip}] $ {command}", "info") - result = await ssh_service.run_command(device, obj, command) + result = await ssh_service.run_command(device, obj, command, db=db) if result.error: await emit(f"[{device.ip}] Error: {result.error}", "error") diff --git a/backend/app/services/task.py b/backend/app/services/task.py index 0c741c1..a30a108 100644 --- a/backend/app/services/task.py +++ b/backend/app/services/task.py @@ -134,7 +134,9 @@ async def _run_on_device( result_id = tr.id try: - action_result = await action.execute(device, obj, params, emit) + async with AsyncSessionLocal() as action_db: + async with action_db.begin(): + action_result = await action.execute(device, obj, params, emit, db=action_db) except Exception as exc: action_result_success = False action_stdout = ""