"use client" import { useState } from "react" import { IconPlus, IconServer, IconTerminal2, IconTrash, IconEdit, IconCloud, IconCloudOff, IconClock, } from "@tabler/icons-react" import { Button } from "@/components/ui/button" import { Status, StatusIndicator, StatusLabel } from "@/components/ui/shadcn-io/status" import { Badge } from "@/components/ui/badge" import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { Banner, BannerIcon, BannerTitle, BannerAction, BannerClose, } from "@/components/ui/shadcn-io/banner" import { Skeleton } from "@/components/ui/skeleton" import { useWorkers, useDeleteWorker } from "@/hooks/use-workers" import type { WorkerNode, WorkerStatus } from "@/types/worker.types" import { WorkerDialog } from "./worker-dialog" import { DeployTerminalDialog } from "./deploy-terminal-dialog" import { Rocket } from "lucide-react" // 后端状态 -> shadcn 状态映射 const STATUS_MAP: Record = { online: 'online', offline: 'offline', pending: 'maintenance', deploying: 'degraded', updating: 'degraded', outdated: 'offline', } // 状态中文标签 const STATUS_LABEL: Record = { online: '运行中', offline: '离线', pending: '等待部署', deploying: '部署中', updating: '更新中', outdated: '版本过低', } // 统计卡片组件 function StatsCards({ workers }: { workers: WorkerNode[] }) { const total = workers.length const online = workers.filter(w => w.status === 'online').length const offline = workers.filter(w => w.status === 'offline').length const pending = workers.filter(w => w.status === 'pending').length const stats = [ { label: '总节点', value: total, icon: IconServer, color: 'text-foreground' }, { label: '在线', value: online, icon: IconCloud, color: 'text-emerald-600' }, { label: '离线', value: offline, icon: IconCloudOff, color: 'text-red-500' }, { label: '等待部署', value: pending, icon: IconClock, color: 'text-amber-500' }, ] return (
{stats.map((stat) => (

{stat.value}

{stat.label}

))}
) } // 快速开始引导横幅 function QuickStartBanner() { const [helpOpen, setHelpOpen] = useState(false) const steps = [ { step: 1, title: '添加扫描节点', desc: '点击"添加节点"按钮,填写 VPS 服务器的 SSH 连接信息(IP、端口、用户名、密码)' }, { step: 2, title: '部署扫描环境', desc: '点击"管理部署"按钮,系统会自动通过 SSH 在远程服务器上安装 Docker 和心跳agent' }, { step: 3, title: '自动任务分发', desc: '部署完成后节点会自动上报心跳,扫描任务将根据负载自动分发到各节点并行执行' }, ] return ( <> 分布式扫描: 添加 VPS 节点 → 一键部署 → 任务自动分发 setHelpOpen(true)}> 了解更多 什么是分布式扫描?

分布式扫描允许你将扫描任务分发到多个远程服务器(扫描节点)上并行执行,显著提高扫描效率。

{steps.map((item) => (
{item.step}

{item.title}

{item.desc}

))}
知道了
) } // Worker 卡片视图组件 function WorkerCardView({ workers, onEdit, onManage, onDelete }: { workers: WorkerNode[] onEdit: (w: WorkerNode) => void onManage: (w: WorkerNode) => void onDelete: (w: WorkerNode) => void }) { return (
{workers.map((worker) => (
{worker.name} {worker.isLocal && ( 本地 )}
{STATUS_LABEL[worker.status]}
{/* 本地节点不显示编辑和删除按钮 */} {!worker.isLocal && (
)}
{/* 所有节点都显示 CPU 和内存 */}

CPU

{worker.info?.cpuPercent != null ? `${worker.info.cpuPercent.toFixed(1)}%` : '-'}

内存

{worker.info?.memoryPercent != null ? `${worker.info.memoryPercent.toFixed(1)}%` : '-'}

{/* 远程节点:额外显示连接信息和管理按钮 */} {!worker.isLocal && ( <>
{worker.ipAddress}:{worker.sshPort} {worker.username}
)}
))}
) } // 空状态组件 function EmptyState({ onAdd }: { onAdd: () => void }) { return (

暂无扫描节点

添加远程 VPS 服务器作为扫描节点,开始使用分布式扫描功能,提升扫描效率

) } export function WorkerList() { const [page, setPage] = useState(1) const [pageSize] = useState(10) const [workerDialogOpen, setWorkerDialogOpen] = useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [deployDialogOpen, setDeployDialogOpen] = useState(false) const [selectedWorker, setSelectedWorker] = useState(null) const [workerToDeploy, setWorkerToDeploy] = useState(null) const [workerToDelete, setWorkerToDelete] = useState(null) const { data, isLoading, refetch } = useWorkers(page, pageSize) const deleteWorker = useDeleteWorker() const workers = data?.results || [] const hasWorkers = workers.length > 0 const handleAdd = () => { setSelectedWorker(null) setWorkerDialogOpen(true) } const handleEdit = (worker: WorkerNode) => { setSelectedWorker(worker) setWorkerDialogOpen(true) } const handleManage = (worker: WorkerNode) => { setWorkerToDeploy(worker) setDeployDialogOpen(true) } const handleDeleteClick = (worker: WorkerNode) => { setWorkerToDelete(worker) setDeleteDialogOpen(true) } const handleDeleteConfirm = () => { if (workerToDelete) { deleteWorker.mutate(workerToDelete.id) setDeleteDialogOpen(false) setWorkerToDelete(null) } } return (
{/* 快速开始引导横幅 */} {/* 统计卡片 - 只在有 Worker 时显示 */} {hasWorkers && } {/* 主内容卡片 */}
Worker 节点 管理分布式扫描节点,支持远程部署和监控
{isLoading ? (
{[...Array(3)].map((_, i) => )}
) : !hasWorkers ? ( ) : ( )}
{/* 弹窗 */} refetch()} /> 确认删除 确定要删除 Worker 节点 "{workerToDelete?.name}" 吗?此操作不可恢复。 取消 删除
) }