plugin system
This commit is contained in:
parent
31f54c49ad
commit
74fc294d04
2 changed files with 152 additions and 0 deletions
150
backend/app/plugins/builtin/ssh/debian_system_info.py
Normal file
150
backend/app/plugins/builtin/ssh/debian_system_info.py
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
"""
|
||||
Collects Debian/Linux system status over SSH:
|
||||
disk usage, RAM, CPU load, uptime, OS version, top processes.
|
||||
"""
|
||||
|
||||
from app.models.device import Device
|
||||
from app.models.object import Object
|
||||
from app.plugins.base import ActionResult, BaseAction, Emitter
|
||||
from app.services import ssh as ssh_service
|
||||
|
||||
# One-liner that collects all metrics in a single SSH session
|
||||
_CMD = r"""
|
||||
echo "=== OS ===" && (cat /etc/os-release 2>/dev/null | grep -E "^PRETTY_NAME|^VERSION_ID" || echo "unknown") && \
|
||||
echo "=== UPTIME ===" && uptime -p 2>/dev/null || uptime && \
|
||||
echo "=== CPU_LOAD ===" && cat /proc/loadavg && \
|
||||
echo "=== CPU_CORES ===" && nproc && \
|
||||
echo "=== MEMORY ===" && free -m && \
|
||||
echo "=== DISK ===" && df -h --output=target,size,used,avail,pcent -x tmpfs -x devtmpfs 2>/dev/null || df -h && \
|
||||
echo "=== TOP_PROCS ===" && ps aux --sort=-%cpu --no-headers 2>/dev/null | head -5 && \
|
||||
echo "=== TEMPS ===" && (cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null | awk '{printf "%.1f°C\n", $1/1000}' || echo "n/a")
|
||||
""".strip()
|
||||
|
||||
|
||||
class DebianSystemInfoAction(BaseAction):
|
||||
"""Collects disk, RAM, CPU, uptime and process info from a Debian/Linux host via SSH."""
|
||||
|
||||
name = "debian_system_info"
|
||||
display_name = "Debian: System Info"
|
||||
compatible_categories = ["server", "raspberry", "embedded", "other"]
|
||||
params_schema = []
|
||||
|
||||
async def execute(self, device: Device, obj: Object, params: dict, emit: Emitter) -> ActionResult:
|
||||
await emit(f"[{device.ip}] Collecting system info...", "info")
|
||||
|
||||
result = await ssh_service.run_command(device, obj, _CMD, timeout=30)
|
||||
|
||||
if result.error:
|
||||
await emit(f"[{device.ip}] Error: {result.error}", "error")
|
||||
return ActionResult(success=False, error=result.error)
|
||||
|
||||
data = _parse(result.stdout)
|
||||
|
||||
# Emit summary
|
||||
await emit(f"[{device.ip}] OS: {data.get('os', '?')}", "info")
|
||||
await emit(f"[{device.ip}] Uptime: {data.get('uptime', '?')}", "info")
|
||||
await emit(f"[{device.ip}] Load: {data.get('load_1m', '?')} / {data.get('load_5m', '?')} / {data.get('load_15m', '?')} (cores: {data.get('cpu_cores', '?')})", "info")
|
||||
await emit(f"[{device.ip}] RAM: used {data.get('mem_used_mb', '?')} MB / {data.get('mem_total_mb', '?')} MB ({data.get('mem_percent', '?')}%)", "info")
|
||||
for disk in data.get("disks", []):
|
||||
await emit(f"[{device.ip}] Disk {disk['mount']}: {disk['used']} / {disk['size']} ({disk['pct']} used, {disk['avail']} free)", "info")
|
||||
if data.get("temps"):
|
||||
await emit(f"[{device.ip}] Temps: {', '.join(data['temps'])}", "info")
|
||||
|
||||
return ActionResult(
|
||||
success=True,
|
||||
stdout=result.stdout,
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
def _parse(output: str) -> dict:
|
||||
sections: dict[str, list[str]] = {}
|
||||
current = None
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("===") and line.endswith("==="):
|
||||
current = line.strip("= ").strip()
|
||||
sections[current] = []
|
||||
elif current is not None and line:
|
||||
sections[current].append(line)
|
||||
|
||||
data: dict = {}
|
||||
|
||||
# OS
|
||||
os_lines = sections.get("OS", [])
|
||||
for line in os_lines:
|
||||
if line.startswith("PRETTY_NAME="):
|
||||
data["os"] = line.split("=", 1)[1].strip('"')
|
||||
break
|
||||
if "os" not in data and os_lines:
|
||||
data["os"] = os_lines[0]
|
||||
|
||||
# Uptime
|
||||
uptime_lines = sections.get("UPTIME", [])
|
||||
data["uptime"] = uptime_lines[0] if uptime_lines else "?"
|
||||
|
||||
# CPU load
|
||||
load_lines = sections.get("CPU_LOAD", [])
|
||||
if load_lines:
|
||||
parts = load_lines[0].split()
|
||||
data["load_1m"] = parts[0] if len(parts) > 0 else "?"
|
||||
data["load_5m"] = parts[1] if len(parts) > 1 else "?"
|
||||
data["load_15m"] = parts[2] if len(parts) > 2 else "?"
|
||||
|
||||
# CPU cores
|
||||
cores_lines = sections.get("CPU_CORES", [])
|
||||
data["cpu_cores"] = cores_lines[0] if cores_lines else "?"
|
||||
|
||||
# Memory (free -m output)
|
||||
# Line format: "Mem: total used free shared buff/cache available"
|
||||
mem_lines = sections.get("MEMORY", [])
|
||||
for line in mem_lines:
|
||||
if line.startswith("Mem:"):
|
||||
parts = line.split()
|
||||
if len(parts) >= 3:
|
||||
total = int(parts[1])
|
||||
used = int(parts[2])
|
||||
data["mem_total_mb"] = total
|
||||
data["mem_used_mb"] = used
|
||||
data["mem_free_mb"] = int(parts[3]) if len(parts) > 3 else total - used
|
||||
data["mem_percent"] = round(used / total * 100, 1) if total else 0
|
||||
break
|
||||
|
||||
# Disk
|
||||
disk_lines = sections.get("DISK", [])
|
||||
disks = []
|
||||
for line in disk_lines:
|
||||
# Skip header lines
|
||||
if line.startswith("Filesystem") or line.startswith("target"):
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) >= 5:
|
||||
disks.append({
|
||||
"mount": parts[0],
|
||||
"size": parts[1],
|
||||
"used": parts[2],
|
||||
"avail": parts[3],
|
||||
"pct": parts[4],
|
||||
})
|
||||
data["disks"] = disks
|
||||
|
||||
# Top processes
|
||||
proc_lines = sections.get("TOP_PROCS", [])
|
||||
procs = []
|
||||
for line in proc_lines:
|
||||
parts = line.split(None, 10)
|
||||
if len(parts) >= 11:
|
||||
procs.append({
|
||||
"user": parts[0],
|
||||
"pid": parts[1],
|
||||
"cpu": parts[2],
|
||||
"mem": parts[3],
|
||||
"cmd": parts[10],
|
||||
})
|
||||
data["top_processes"] = procs
|
||||
|
||||
# Temperatures
|
||||
temp_lines = sections.get("TEMPS", [])
|
||||
data["temps"] = [t for t in temp_lines if t != "n/a"]
|
||||
|
||||
return data
|
||||
|
|
@ -25,6 +25,7 @@ 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.debian_system_info import DebianSystemInfoAction
|
||||
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
|
||||
|
|
@ -37,6 +38,7 @@ BUILTIN_ACTIONS: dict[str, BaseAction] = {
|
|||
for a in [
|
||||
PingAction(),
|
||||
SSHCommandAction(),
|
||||
DebianSystemInfoAction(),
|
||||
GetTimedatectlAction(),
|
||||
GetFileAction(),
|
||||
MikrotikGetConfigAction(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue