connect tailscale node status through platform gateway
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-08 17:51:16 +09:00
parent 90a22c098c
commit 38fe8bf098
17 changed files with 655 additions and 649 deletions
-107
View File
@@ -5,10 +5,8 @@ import { Badge, Button, Card, CardHeader, Modal, Progress } from '../components/
import {
agentDevices,
alerts,
authApiEndpoints,
gatewayConnections,
k3sWorkloads,
launcherCommonSettings,
launcherVersions,
runtimeCommands,
tailscaleSettings
@@ -315,111 +313,6 @@ export function LauncherAppsPage() {
);
}
export function LauncherCommonSettingsPage() {
const [apiOpen, setApiOpen] = useState(false);
return (
<>
<PageTitle
eyebrow="런처 운영"
title="공통 설정"
description="런처에서 공통으로 사용하는 연결값과 기본 설정을 관리합니다."
actions={
<>
<Button variant="outline" className="h-8 px-3 text-[13px]" onClick={() => setApiOpen(true)}>
<SlidersHorizontal className="size-4" />
공통 인증 API 보기
</Button>
<Button className="h-8 px-3 text-[13px]">
<Plus className="size-4" />
설정 추가
</Button>
</>
}
/>
<div className="grid gap-4 md:grid-cols-4">
{[
['설정 항목', `${launcherCommonSettings.length}개`],
['Endpoint', `${launcherCommonSettings.filter((item) => item.type === 'endpoint').length}개`],
['Database', `${launcherCommonSettings.filter((item) => item.type === 'database').length}개`],
['활성 상태', `${launcherCommonSettings.filter((item) => item.status === '활성').length}개`]
].map(([label, value]) => (
<Card className="p-5" key={label}>
<p className="text-sm text-muted-foreground">{label}</p>
<p className="mt-3 text-2xl font-semibold">{value}</p>
</Card>
))}
</div>
<Card className="mt-4 min-w-0 overflow-hidden">
<CardHeader title="런처 연결 설정" description="여러 기능에서 함께 쓰는 주소, 인증, 저장소 관련 값을 정리합니다." />
<div className="overflow-x-auto">
<table className="w-full min-w-[1120px] text-left text-sm">
<thead className="border-b border-border text-xs text-muted-foreground">
<tr>
<th className="w-[280px] px-5 py-3 font-medium">설정</th>
<th className="px-5 py-3 font-medium">Key</th>
<th className="px-5 py-3 font-medium">Type</th>
<th className="px-5 py-3 font-medium">Value</th>
<th className="px-5 py-3 font-medium" />
</tr>
</thead>
<tbody className="divide-y divide-border">
{launcherCommonSettings.map((item) => (
<tr className="hover:bg-muted/20" key={item.key}>
<td className="px-5 py-3">
<p className="inline-flex items-center gap-2 font-medium">
<span className={item.status === '활성' ? 'size-2 rounded-full bg-emerald-400' : 'size-2 rounded-full bg-muted-foreground'} />
{item.name}
</p>
</td>
<td className="px-5 py-3 font-medium">{item.key}</td>
<td className="px-5 py-3">{item.type}</td>
<td className="px-5 py-3 text-muted-foreground">{item.value}</td>
<td className="px-5 py-3 text-right">
<Button variant="ghost" className="size-8 px-0" aria-label="상세">
<MoreHorizontal className="size-4" />
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
<Modal open={apiOpen} title="공통 인증 API" onClose={() => setApiOpen(false)} className="!max-w-[1280px]" footer={<Button variant="ghost" onClick={() => setApiOpen(false)}>닫기</Button>}>
<div className="max-h-[70vh] overflow-x-auto overflow-y-auto">
<table className="w-full min-w-[980px] text-left text-sm">
<thead className="sticky top-0 z-10 border-b border-border bg-card text-xs text-muted-foreground">
<tr>
<th className="px-5 py-3 font-medium">그룹</th>
<th className="px-5 py-3 font-medium">Method</th>
<th className="px-5 py-3 font-medium">Path</th>
<th className="px-5 py-3 font-medium">용도</th>
<th className="px-5 py-3 font-medium">권한</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{authApiEndpoints.map((endpoint) => (
<tr className="hover:bg-muted/20" key={`${endpoint.method}-${endpoint.path}`}>
<td className="px-5 py-3">{endpoint.group}</td>
<td className="px-5 py-3 font-semibold">{endpoint.method}</td>
<td className="px-5 py-3 font-medium">{endpoint.path}</td>
<td className="px-5 py-3 text-muted-foreground">{endpoint.purpose}</td>
<td className="px-5 py-3">
<Badge tone={endpoint.auth === '없음' ? 'neutral' : endpoint.auth === 'ADMIN' ? 'amber' : 'blue'}>{endpoint.auth}</Badge>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Modal>
</>
);
}
export function TailscaleStatusPage() {
const [nodes, setNodes] = useState<TailscaleNode[]>([]);
const [source, setSource] = useState('');