This commit is contained in:
@@ -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 || '-' },
|
||||
|
||||
@@ -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: '플랫폼',
|
||||
|
||||
Reference in New Issue
Block a user