From d746d9d0dc6bc4b2aaffcb6f475d13c1b55335a9 Mon Sep 17 00:00:00 2001 From: chyoo Date: Fri, 7 Aug 2026 10:24:27 +0900 Subject: [PATCH] add icon.. --- src/pages/RestoredPages.tsx | 55 +++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/pages/RestoredPages.tsx b/src/pages/RestoredPages.tsx index 43662d6..909e77d 100644 --- a/src/pages/RestoredPages.tsx +++ b/src/pages/RestoredPages.tsx @@ -1,4 +1,4 @@ -import { ChevronDown, ChevronUp, MoreHorizontal, Plus, RefreshCcw, Settings2 } from 'lucide-react'; +import { ChevronDown, ChevronUp, MoreHorizontal, Plus, RefreshCcw, Settings2, Trash2 } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { DataTableCard, type DataTableColumn } from '../components/data-table'; import { Badge, Button, Card, CardHeader, Modal, Progress } from '../components/ui'; @@ -331,6 +331,8 @@ export function TailscaleStatusPage() { const [source, setSource] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [deleteTarget, setDeleteTarget] = useState(null); + const [deleting, setDeleting] = useState(false); const online = nodes.filter((node) => node.status === '활성' || node.online).length; const subnetRouters = nodes.filter((node) => node.routes && node.routes !== '-').length; @@ -353,6 +355,20 @@ export function TailscaleStatusPage() { refresh(); }, []); + const deleteNode = () => { + if (!deleteTarget) return; + setDeleting(true); + setError(''); + apiRequest<{ deleted: boolean }>(`/tailscale/nodes/${encodeURIComponent(deleteTarget.id)}`, { method: 'DELETE' }) + .then(() => { + setNodes((current) => current.filter((node) => node.id !== deleteTarget.id)); + setDeleteTarget(null); + refresh(); + }) + .catch((requestError: Error) => setError(requestError.message)) + .finally(() => setDeleting(false)); + }; + return ( <> ) : null}
- +
+ !deleting && setDeleteTarget(null)} + footer={ + <> + + + + } + > +

+ {deleteTarget?.name} 노드를 Headscale에서 삭제합니다. 연결된 기기의 VPN 연결은 즉시 끊기며, 다시 사용하려면 재등록해야 합니다. +

+
); } -function TailscaleNodeTable({ nodes, loading }: { nodes: TailscaleNode[]; loading: boolean }) { +function TailscaleNodeTable({ nodes, loading, onDelete }: { nodes: TailscaleNode[]; loading: boolean; onDelete: (node: TailscaleNode) => void }) { const pageSize = 10; const [page, setPage] = useState(1); const totalPages = Math.max(1, Math.ceil(nodes.length / pageSize)); @@ -420,17 +453,18 @@ function TailscaleNodeTable({ nodes, loading }: { nodes: TailscaleNode[]; loadin Routes Last Seen 상태 + 관리 {loading && nodes.length === 0 ? ( - 노드 정보를 불러오는 중입니다. + 노드 정보를 불러오는 중입니다. ) : null} {!loading && nodes.length === 0 ? ( - 표시할 노드가 없습니다. + 표시할 노드가 없습니다. ) : null} {pageNodes.map((node) => ( @@ -442,6 +476,17 @@ function TailscaleNodeTable({ nodes, loading }: { nodes: TailscaleNode[]; loadin {node.routes} {formatTailscaleLastSeen(node.lastSeen)} + + + ))}