feat: display platform agent status and version
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-08-06 16:01:33 +09:00
parent 4fce1c21a3
commit 4a79dab2f6
3 changed files with 35 additions and 1 deletions
+3
View File
@@ -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;
@@ -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 [];
@@ -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<PlatformRow[]>([]);
const [loading, setLoading] = useState(true);
@@ -91,6 +104,19 @@ export function RuntimeDeviceControlPage() {
width: 180,
cell: (row) => <span className="font-mono text-xs text-muted-foreground">{row.ip || '-'}</span>,
},
{
key: 'deviceKind',
header: '유형',
width: 150,
cell: (row) => {
const kind = getDeviceKind(row);
if (kind === 'agent') {
return <Badge tone="green">에이전트</Badge>;
}
if (kind === 'launcher') return <Badge tone="neutral">런처 클라이언트</Badge>;
return <Badge tone="neutral">미확인</Badge>;
},
},
{
key: 'controllers',
header: '제어 사용자',
@@ -155,6 +181,9 @@ export function RuntimeDeviceControlPage() {
<dt className="text-muted-foreground">Workspace</dt><dd className="min-w-0 break-all">{selected?.device?.workspaceRoot || '-'}</dd>
<dt className="text-muted-foreground">SSH 사용자</dt><dd>{selected?.device?.sshUser || '-'}</dd>
<dt className="text-muted-foreground">사용 유형</dt><dd>{selected?.device?.usageType || '-'}</dd>
<dt className="text-muted-foreground">장비 유형</dt><dd>{selected ? ({ agent: '에이전트', launcher: '런처 클라이언트', unknown: '미확인' }[getDeviceKind(selected)]) : '-'}</dd>
<dt className="text-muted-foreground">에이전트 버전</dt><dd>{selected?.device?.agentVersion || '-'}</dd>
<dt className="text-muted-foreground">마지막 에이전트 확인</dt><dd>{formatDateTime(selected?.device?.agentLastSeenAt)}</dd>
</dl>
</Modal>
</>