From a322b612f8436e479e5f57e9ec5cf87c09fe9445 Mon Sep 17 00:00:00 2001 From: dv Date: Tue, 14 Apr 2026 13:49:56 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BA=D0=B8=20=D1=83=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B0=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/builtin/ssh/debian_system_info.py | 4 +- frontend/src/pages/DeviceDetail.tsx | 125 +++++++++++------- 2 files changed, 81 insertions(+), 48 deletions(-) diff --git a/backend/app/plugins/builtin/ssh/debian_system_info.py b/backend/app/plugins/builtin/ssh/debian_system_info.py index d2dd326..98aef5e 100644 --- a/backend/app/plugins/builtin/ssh/debian_system_info.py +++ b/backend/app/plugins/builtin/ssh/debian_system_info.py @@ -111,8 +111,8 @@ def _parse(output: str) -> dict: disk_lines = sections.get("DISK", []) disks = [] for line in disk_lines: - # Skip header lines - if line.startswith("Filesystem") or line.startswith("target"): + # Skip header lines ("Filesystem ...", "target ...", "Mounted on ...") + if line.startswith(("Filesystem", "target", "Mounted")): continue parts = line.split() if len(parts) >= 5: diff --git a/frontend/src/pages/DeviceDetail.tsx b/frontend/src/pages/DeviceDetail.tsx index be2274d..b782b9b 100644 --- a/frontend/src/pages/DeviceDetail.tsx +++ b/frontend/src/pages/DeviceDetail.tsx @@ -38,7 +38,7 @@ import dayjs from 'dayjs' import { useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useAlerts, useAcknowledgeAlert, useResolveAlert, type AlertItem } from '../api/alerts' -import { useDevice, useDeleteDevice, useObject, useDeviceCredentials, type DeviceItem } from '../api/objects' +import { useDevice, useDeleteDevice, useObject, useDeviceCredentials, type DeviceItem, type ObjectItem } from '../api/objects' import { getDeviceWebUrl } from '../utils/deviceUrl' import { elapsedAgo } from '../utils/time' import { useSnapshots, useSnapshotDiff } from '../api/snapshots' @@ -101,13 +101,29 @@ function getSnapshotConfig(device: DeviceItem) { // ─── SSH / Credentials block ────────────────────────────────────────────────── -function SshBlock({ device, objId }: { device: DeviceItem; objId: number }) { +function SshBlock({ device, objId, obj }: { device: DeviceItem; objId: number; obj: ObjectItem | undefined }) { const [showPassword, setShowPassword] = useState(false) const { data: creds, isFetching: credsFetching } = useDeviceCredentials(objId, device.id, showPassword) - const hasOverride = !!(device.ssh_user_override || device.ssh_port_override || device.ssh_key_id_override) const lastAuth = device.ssh_last_auth + // Resolve effective values: device override → object default → fallback + const effectiveUser = + lastAuth?.username + ? String(lastAuth.username) + : device.ssh_user_override ?? obj?.ssh_user ?? null + + const effectivePort = device.ssh_port_override ?? 22 + + // Determine auth method from last_auth + const authType = lastAuth?.type ? String(lastAuth.type) : null + const usesKey = authType === 'key' || device.ssh_key_id_override != null + const credSource: 'device' | 'object' | 'unknown' = + lastAuth?.cred_id != null ? 'object' + : lastAuth?.key_id != null || device.ssh_key_id_override != null ? 'device' + : device.ssh_user_override ? 'device' + : 'object' + return ( SSH / Авторизация} > + {/* Effective login */} - {device.ssh_user_override - ? <>{device.ssh_user_override} override - : С объекта} + + {effectiveUser + ? {effectiveUser} + : Не задан} + {device.ssh_user_override + ? device + : obj?.ssh_user + ? объект + : null} + - - {device.ssh_key_id_override != null ? ( - — (используется ключ) + {/* Effective auth method + password */} + + {usesKey ? ( + + SSH ключ + {(lastAuth?.key_id != null || device.ssh_key_id_override != null) && ( + + key_id={String(lastAuth?.key_id ?? device.ssh_key_id_override)} + + )} + ) : ( - {showPassword ? ( - credsFetching - ? - : creds?.ssh_password - ? {creds.ssh_password} - : Не задан (используется с объекта) + Пароль + {credSource === 'object' ? ( + + с объекта{lastAuth?.cred_id != null ? ` (cred_id=${String(lastAuth.cred_id)})` : ''} + ) : ( - •••••••• + <> + {showPassword ? ( + credsFetching + ? + : creds?.ssh_password + ? {creds.ssh_password} + : Не задан + ) : ( + •••••••• + )} + + )} - )} + {/* Port */} - {device.ssh_port_override - ? <>{device.ssh_port_override} override - : 22 (по умолчанию)} + + {effectivePort} + {device.ssh_port_override && device} + - {device.ssh_key_id_override != null && ( - - key_id={device.ssh_key_id_override} - - )} - - {!hasOverride && ( - - Используются настройки объекта - - )} - + {/* Last successful connection */} {lastAuth && ( - + - {String(lastAuth.type ?? '—')} - {lastAuth.username && {String(lastAuth.username)}} - {lastAuth.key_id != null && key_id={String(lastAuth.key_id)}} - {lastAuth.cred_id != null && cred_id={String(lastAuth.cred_id)}} + успешно + {authType && {authType === 'key' ? 'ключ' : 'пароль'}} + {lastAuth.username && ( + {String(lastAuth.username)} + )} )} @@ -372,7 +405,7 @@ function SystemInfoRender({ data }: { data: Record }) { {disks && disks.length > 0 && (
Диски: - {disks.map((d) => { + {disks.filter((d) => !isNaN(parseInt(d.pct))).map((d) => { const pctNum = parseInt(d.pct) return (
@@ -1021,7 +1054,7 @@ export function DeviceDetailPage() { {/* SSH block — always shown */} - + {/* System info (Linux-like devices) */} {isLinuxLike && }