feat: Agent一键安装(loading/成功/失败状态无弹框) + 批量安装API

This commit is contained in:
attacker committed 2026-06-18 07:24:24 +00:00
1 parent 48b2d2281b
commit 2fc2a2ae07
5 files changed
+213 -5

No files matched your search

@@ -73,8 +73,75 @@ export function ServerManager() {
// Agent 安装向导状态
const [agentWizardOpen, setAgentWizardOpen] = useState(false);
const openInstallAgent = (server: any) => {
setAgentWizardOpen(true);
// Agent 安装状态: { [serverId]: 'installing' | 'success' | 'failed' }
const [installingIds, setInstallingIds] = useState<Set<number>>(new Set());
const [installResults, setInstallResults] = useState<Map<number, { success: boolean; message: string }>>(new Map());
const openInstallAgent = async (server: any) => {
const token = useUserStore.getState().token;
if (!token) { toast.error('请先登录'); return; }
setInstallingIds(prev => new Set(prev).add(server.id));
try {
const res: any = await serverApi.installAgent(server.id, token);
const installed = res?.installed || false;
setInstallResults(prev => {
const next = new Map(prev);
next.set(server.id, {
success: installed,
message: installed ? '安装成功 ✓' : '命令已执行,Agent 未检测到运行'
});
return next;
});
if (installed) toast.success(`${server.hostname} Agent 安装成功`);
else toast.warning(`${server.hostname}: Agent 安装命令已执行`);
} catch (e: any) {
setInstallResults(prev => {
const next = new Map(prev);
next.set(server.id, { success: false, message: e?.message || '安装失败' });
return next;
});
toast.error(`${server.hostname} 安装失败: ${e?.message || '未知错误'}`);
} finally {
setInstallingIds(prev => {
const next = new Set(prev);
next.delete(server.id);
return next;
});
}
};
const handleBatchInstallAgents = async () => {
const token = useUserStore.getState().token;
if (!token) { toast.error('请先登录'); return; }
const ids = selectedServers.map(id => Number(id));
setInstallingIds(prev => {
const next = new Set(prev);
ids.forEach(id => next.add(id));
return next;
});
try {
const res: any = await serverApi.batchInstallAgent(ids, token);
const results = res?.results || [];
setInstallResults(prev => {
const next = new Map(prev);
results.forEach((r: any) => {
next.set(r.server_id, { success: r.installed, message: r.message });
});
return next;
});
toast.success(`批量安装完成: ${res?.success || 0}/${ids.length} 成功`);
} catch (e: any) {
toast.error(`批量安装失败: ${e?.message || '未知错误'}`);
} finally {
setInstallingIds(prev => {
const next = new Set(prev);
ids.forEach(id => next.delete(id));
return next;
});
}
};
const openTerminal = (server: any) => {
@@ -360,6 +427,15 @@ export function ServerManager() {
{selectedServers.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-sm text-[var(--dg-text-secondary)]">已选择 {selectedServers.length} 项</span>
<Button
variant="outline"
size="sm"
onClick={handleBatchInstallAgents}
className="border-[var(--dg-cyan)]/30 text-[var(--dg-cyan)] hover:bg-[var(--dg-cyan)]/10"
>
<Cpu className="h-4 w-4 mr-2" />
批量安装Agent
</Button>
<Button
variant="outline"
size="sm"
@@ -421,6 +497,8 @@ export function ServerManager() {
onOpenEdit={openEditForm}
onDelete={handleDelete}
onInstallAgent={openInstallAgent}
installing={installingIds.has(server.id)}
installResult={installResults.get(server.id) || null}
/>
))
)}
@@ -12,7 +12,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TableCell, TableRow } from '@/components/ui/table';
import { Cpu, Edit, MoreHorizontal, Server as ServerIcon, Terminal, Trash2 } from "lucide-react";
import { Cpu, Edit, Loader2, MoreHorizontal, Server as ServerIcon, Terminal, Trash2, CheckCircle, XCircle } from "lucide-react";
import { getStatusBadge, getTags, getUsageColor } from './serverHelpers';
interface ServerTableRowProps {
@@ -23,6 +23,8 @@ interface ServerTableRowProps {
onOpenEdit: (server: any) => void;
onDelete: (id: string) => void;
onInstallAgent: (server: any) => void;
installing?: boolean;
installResult?: { success: boolean; message: string } | null;
}
export function ServerTableRow({
@@ -33,6 +35,8 @@ export function ServerTableRow({
onOpenEdit,
onDelete,
onInstallAgent,
installing = false,
installResult = null,
}: ServerTableRowProps) {
const cpuUsage = Number(server.cpu_usage || 0);
const memoryUsage = Number(server.memory_usage || 0);
@@ -154,9 +158,20 @@ export function ServerTableRow({
<DropdownMenuItem
className="text-[var(--dg-text-secondary)] focus:bg-[var(--dg-bg-hover)] focus:text-[var(--dg-text-primary)] cursor-pointer"
onClick={() => onInstallAgent(server)}
disabled={installing}
>
<Cpu className="h-4 w-4 mr-2" />
安装Agent
{installing ? (
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
) : installResult ? (
installResult.success ? (
<CheckCircle className="h-4 w-4 mr-2 text-emerald-400" />
) : (
<XCircle className="h-4 w-4 mr-2 text-red-400" />
)
) : (
<Cpu className="h-4 w-4 mr-2" />
)}
{installing ? '安装中...' : installResult ? installResult.message : '安装Agent'}
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-[var(--dg-border)]" />
<DropdownMenuItem
+4
View File
@@ -147,6 +147,10 @@ export const serverApi = {
installAgent: async (id: number | string, token: string) => {
return http.post<{ installed: boolean; arch: string; output: string }>(`/servers/${id}/install-agent?token=${encodeURIComponent(token)}`);
},
// 批量远程安装 Agent
batchInstallAgent: async (ids: number[], token: string) => {
return http.post<{ total: number; success: number; results: any[] }>(`/servers/install-agent?token=${encodeURIComponent(token)}`, { server_ids: ids });
},
};
// ==================== K8s 集群 API ====================