From 4a79dab2f674d42d31b039395b578622b9212a18 Mon Sep 17 00:00:00 2001 From: chyoo Date: Thu, 6 Aug 2026 16:01:33 +0900 Subject: [PATCH] feat: display platform agent status and version --- src/pages/agents/platform-api.ts | 3 ++ .../applications/DeploymentManagementPage.tsx | 4 ++- .../runtime/RuntimeDeviceControlPage.tsx | 29 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/pages/agents/platform-api.ts b/src/pages/agents/platform-api.ts index f998411..3ba6175 100644 --- a/src/pages/agents/platform-api.ts +++ b/src/pages/agents/platform-api.ts @@ -11,6 +11,9 @@ export type PlatformDevice = { os?: string | null; arch?: string | null; distro?: string | null; + agentVersion?: string | null; + agentUpdatedAt?: string | null; + agentLastSeenAt?: string | null; installedAt: string; lastProvisionedAt: string; status: string; diff --git a/src/pages/applications/DeploymentManagementPage.tsx b/src/pages/applications/DeploymentManagementPage.tsx index 9969d03..accac08 100644 --- a/src/pages/applications/DeploymentManagementPage.tsx +++ b/src/pages/applications/DeploymentManagementPage.tsx @@ -38,7 +38,9 @@ type ImageTags = { function releaseArtifacts(release?: AppRelease | null) { const imageTags = release?.imageTags as ImageTags | null | undefined; - const entries = Object.entries(imageTags?.images ?? {}); + const entries = Object.entries(imageTags?.images ?? {}).filter(([, image]) => ( + !/^(postgres|mysql|mariadb|mongo)(:|@)/i.test(image.trim()) + )); if (entries.length) return entries; if (release?.artifact) return [[release.app.name, release.artifact]]; return []; diff --git a/src/pages/runtime/RuntimeDeviceControlPage.tsx b/src/pages/runtime/RuntimeDeviceControlPage.tsx index fc100b9..8971c8f 100644 --- a/src/pages/runtime/RuntimeDeviceControlPage.tsx +++ b/src/pages/runtime/RuntimeDeviceControlPage.tsx @@ -22,6 +22,8 @@ type HeadscaleNode = { type AclRule = { id: string; action: string; sources: string[]; destinations: string[] }; type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[]; no: number }; +type DeviceKind = 'agent' | 'launcher' | 'unknown'; + function normalize(value?: string | null) { return (value ?? '').toLowerCase().replace(/[^a-z0-9]/g, ''); } @@ -41,6 +43,17 @@ function findControllers(node: HeadscaleNode, rules: AclRule[]) { .flatMap((rule) => rule.sources))]; } +function getDeviceKind(row: PlatformRow): DeviceKind { + if (!row.device) return 'unknown'; + return row.device.agentVersion || row.device.agentLastSeenAt ? 'agent' : 'launcher'; +} + +function formatDateTime(value?: string | null) { + if (!value) return '-'; + const date = new Date(value); + return Number.isNaN(date.getTime()) ? '-' : date.toLocaleString('ko-KR', { hour12: false }); +} + export function RuntimeDeviceControlPage() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); @@ -91,6 +104,19 @@ export function RuntimeDeviceControlPage() { width: 180, cell: (row) => {row.ip || '-'}, }, + { + key: 'deviceKind', + header: '유형', + width: 150, + cell: (row) => { + const kind = getDeviceKind(row); + if (kind === 'agent') { + return 에이전트; + } + if (kind === 'launcher') return 런처 클라이언트; + return 미확인; + }, + }, { key: 'controllers', header: '제어 사용자', @@ -155,6 +181,9 @@ export function RuntimeDeviceControlPage() {
Workspace
{selected?.device?.workspaceRoot || '-'}
SSH 사용자
{selected?.device?.sshUser || '-'}
사용 유형
{selected?.device?.usageType || '-'}
+
장비 유형
{selected ? ({ agent: '에이전트', launcher: '런처 클라이언트', unknown: '미확인' }[getDeviceKind(selected)]) : '-'}
+
에이전트 버전
{selected?.device?.agentVersion || '-'}
+
마지막 에이전트 확인
{formatDateTime(selected?.device?.agentLastSeenAt)}