style: refine platform management table layouts
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-15 14:10:02 +09:00
parent b3119aba9e
commit 616a5e2412
3 changed files with 35 additions and 10 deletions
+27 -5
View File
@@ -800,6 +800,10 @@ export function RuntimeDevicesPage() {
);
}
type NumberedPlatformDevice = PlatformDevice & { no: number };
type NumberedDeviceAuthorization = DeviceAuthorization & { no: number };
type NumberedPlatformSshKey = PlatformSshKey & { no: number };
function AgentTable({ title, description }: { title: string; description: string }) {
const [devices, setDevices] = useState<PlatformDevice[]>([]);
const [loading, setLoading] = useState(true);
@@ -816,8 +820,14 @@ function AgentTable({ title, description }: { title: string; description: string
void refresh();
}, [refresh]);
const columns = useMemo<Array<DataTableColumn<PlatformDevice>>>(
const rows = useMemo<NumberedPlatformDevice[]>(
() => devices.map((device, index) => ({ ...device, no: devices.length - index })),
[devices]
);
const columns = useMemo<Array<DataTableColumn<NumberedPlatformDevice>>>(
() => [
{ key: 'no', header: 'No.', className: 'w-14 px-3 py-3 text-center font-medium', cell: (row) => <span>{row.no}</span> },
{
key: 'platform',
header: '플랫폼',
@@ -842,7 +852,7 @@ function AgentTable({ title, description }: { title: string; description: string
<DataTableCard
title={title}
description={description}
rows={devices}
rows={rows}
columns={columns}
searchPlaceholder="플랫폼명, 계정, 워크스페이스 검색"
getSearchText={(row) => `${row.hostname} ${row.tailscaleHostname ?? ''} ${row.accountEmail} ${row.workspaceRoot ?? ''} ${row.sshUser ?? ''}`}
@@ -916,8 +926,14 @@ export function AgentRegistrationPage() {
}
};
const columns = useMemo<Array<DataTableColumn<DeviceAuthorization>>>(
const rows = useMemo<NumberedDeviceAuthorization[]>(
() => authorizations.map((authorization, index) => ({ ...authorization, no: authorizations.length - index })),
[authorizations]
);
const columns = useMemo<Array<DataTableColumn<NumberedDeviceAuthorization>>>(
() => [
{ key: 'no', header: 'No.', className: 'w-14 px-3 py-3 text-center font-medium', cell: (row) => <span>{row.no}</span> },
{
key: 'user',
header: '대상 사용자',
@@ -987,7 +1003,7 @@ export function AgentRegistrationPage() {
<DataTableCard
title="등록 키 목록"
description="활성 키와 최근 24시간 내 발급된 키를 확인합니다."
rows={authorizations}
rows={rows}
columns={columns}
searchPlaceholder="사용자명, 이메일, 등록 키 검색"
getSearchText={(row) => `${row.authUserName ?? ''} ${row.accountEmail ?? ''} ${row.userCode} ${row.status}`}
@@ -1036,14 +1052,20 @@ export function AgentSshKeysPage() {
.finally(() => setLoading(false));
}, []);
const rows = useMemo<NumberedPlatformSshKey[]>(
() => keys.map((key, index) => ({ ...key, no: keys.length - index })),
[keys]
);
return (
<>
<PageTitle eyebrow="플랫폼 관리" title="SSH 키" description="저장소와 원격 실행에 사용할 SSH 키 배포 상태를 관리합니다." />
<DataTableCard
title="SSH 키 목록"
description="등록 플랫폼 계정에 연결된 SSH 키입니다."
rows={keys}
rows={rows}
columns={[
{ key: 'no', header: 'No.', className: 'w-14 px-3 py-3 text-center font-medium', cell: (row) => <span>{row.no}</span> },
{ key: 'account', header: '계정', flexible: true, cell: (row) => row.accountEmail },
{ key: 'label', header: '이름', width: 180, cell: (row) => row.label || '-' },
{ key: 'type', header: '유형', width: 140, cell: (row) => row.keyType || '-' },
+3 -3
View File
@@ -441,15 +441,15 @@ export function RuntimeConfigPage() {
</Card>
<Card className="p-5">
<p className="text-sm text-muted-foreground">구성요소</p>
<p className="mt-4 text-2xl font-medium text-foreground">{components.length}개</p>
<p className="mt-4 text-2xl font-medium text-foreground">{components.length}</p>
</Card>
<Card className="p-5">
<p className="text-sm text-muted-foreground">환경변수</p>
<p className="mt-4 text-2xl font-medium text-foreground">{envCount}개</p>
<p className="mt-4 text-2xl font-medium text-foreground">{envCount}</p>
</Card>
<Card className="p-5">
<p className="text-sm text-muted-foreground">시크릿</p>
<p className="mt-4 text-2xl font-medium text-foreground">{secretCount}개</p>
<p className="mt-4 text-2xl font-medium text-foreground">{secretCount}</p>
</Card>
</div>
@@ -20,7 +20,7 @@ type HeadscaleNode = {
};
type AclRule = { id: string; action: string; sources: string[]; destinations: string[] };
type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[] };
type PlatformRow = HeadscaleNode & { device?: PlatformDevice; controllers: string[]; no: number };
function normalize(value?: string | null) {
return (value ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -55,8 +55,10 @@ export function RuntimeDeviceControlPage() {
])
.then(([nodeResponse, aclResponse, devices]) => {
const rules = aclResponse.policy?.acls ?? [];
setRows((nodeResponse.nodes ?? []).map((node) => ({
const nodes = nodeResponse.nodes ?? [];
setRows(nodes.map((node, index) => ({
...node,
no: nodes.length - index,
device: findDevice(node, devices),
controllers: findControllers(node, rules),
})));
@@ -71,6 +73,7 @@ export function RuntimeDeviceControlPage() {
const columns = useMemo<Array<DataTableColumn<PlatformRow>>>(
() => [
{ key: 'no', header: 'No.', className: 'w-14 px-3 py-3 text-center font-medium', cell: (row) => <span>{row.no}</span> },
{
key: 'platform',
header: '플랫폼',