feat: display platform agent status and version
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
@@ -11,6 +11,9 @@ export type PlatformDevice = {
|
|||||||
os?: string | null;
|
os?: string | null;
|
||||||
arch?: string | null;
|
arch?: string | null;
|
||||||
distro?: string | null;
|
distro?: string | null;
|
||||||
|
agentVersion?: string | null;
|
||||||
|
agentUpdatedAt?: string | null;
|
||||||
|
agentLastSeenAt?: string | null;
|
||||||
installedAt: string;
|
installedAt: string;
|
||||||
lastProvisionedAt: string;
|
lastProvisionedAt: string;
|
||||||
status: string;
|
status: string;
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ type ImageTags = {
|
|||||||
|
|
||||||
function releaseArtifacts(release?: AppRelease | null) {
|
function releaseArtifacts(release?: AppRelease | null) {
|
||||||
const imageTags = release?.imageTags as ImageTags | null | undefined;
|
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 (entries.length) return entries;
|
||||||
if (release?.artifact) return [[release.app.name, release.artifact]];
|
if (release?.artifact) return [[release.app.name, release.artifact]];
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type HeadscaleNode = {
|
|||||||
type AclRule = { id: string; action: string; sources: string[]; destinations: string[] };
|
type AclRule = { id: string; action: string; sources: string[]; destinations: string[] };
|
||||||
type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[]; no: number };
|
type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[]; no: number };
|
||||||
|
|
||||||
|
type DeviceKind = 'agent' | 'launcher' | 'unknown';
|
||||||
|
|
||||||
function normalize(value?: string | null) {
|
function normalize(value?: string | null) {
|
||||||
return (value ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
return (value ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,17 @@ function findControllers(node: HeadscaleNode, rules: AclRule[]) {
|
|||||||
.flatMap((rule) => rule.sources))];
|
.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() {
|
export function RuntimeDeviceControlPage() {
|
||||||
const [rows, setRows] = useState<PlatformRow[]>([]);
|
const [rows, setRows] = useState<PlatformRow[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -91,6 +104,19 @@ export function RuntimeDeviceControlPage() {
|
|||||||
width: 180,
|
width: 180,
|
||||||
cell: (row) => <span className="font-mono text-xs text-muted-foreground">{row.ip || '-'}</span>,
|
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',
|
key: 'controllers',
|
||||||
header: '제어 사용자',
|
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">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">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?.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>
|
</dl>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user