refactor(frontend): P1-P2 续 — API 子模块化、大组件拆分、Query 铺开
- api/server/ 与 api/cloud/ 子目录拆分,保持 @/api 导入不变 - hooks/api 新增 useCredentials、usePromDatasources,增强 useServers - 拆分 CredentialManager、DeploymentWorkload、PrometheusDatasourceManager - ServerManager、CredentialManager、PromDs 接入 TanStack Query
This commit is contained in:
1 parent
f45e203e21
commit
480e677042
32 files changed
+2280
-1940
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -39,7 +40,7 @@ import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { serverApi } from '@/api';
|
||||
import { useServers, useServerMutations, queryKeys } from '@/hooks/api';
|
||||
import { ServerStatsStrip } from './ServerStatsStrip';
|
||||
import { useConfirm } from '@/components/ui/confirm-dialog';
|
||||
import { toast } from 'sonner';
|
||||
@@ -55,9 +56,8 @@ import { usePermission } from '@/hooks/usePermission';
|
||||
|
||||
export function ServerManager() {
|
||||
const confirm = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const { hasPermission } = usePermission();
|
||||
const [servers, setServers] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedServers, setSelectedServers] = useState<string[]>([]);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState<string>('all');
|
||||
@@ -82,7 +82,18 @@ export function ServerManager() {
|
||||
// 分页状态
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
const queryParams = useMemo(() => {
|
||||
const params: Record<string, unknown> = { page, page_size: pageSize };
|
||||
if (searchQuery) params.keyword = searchQuery;
|
||||
if (statusFilter && statusFilter !== 'all') params.status = statusFilter;
|
||||
return params;
|
||||
}, [page, pageSize, searchQuery, statusFilter]);
|
||||
|
||||
const { data, isLoading: loading, refetch } = useServers(queryParams);
|
||||
const { createMutation, updateMutation, deleteMutation, batchDeleteMutation } = useServerMutations();
|
||||
const servers = data?.list ?? [];
|
||||
const total = data?.total ?? 0;
|
||||
|
||||
const openTerminal = ( server: any) => {
|
||||
setTerminalServer({ id: server.id, name: server.hostname, ip: server.ip, hasAuth: server.has_auth ?? true });
|
||||
@@ -94,25 +105,6 @@ export function ServerManager() {
|
||||
setFileBrowserOpen(true);
|
||||
};
|
||||
|
||||
const loadData = async (kw?: string, st?: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const q = kw ?? searchQuery;
|
||||
const s = st ?? statusFilter;
|
||||
const params: Record<string, any> = { page, page_size: pageSize };
|
||||
if (q) params.keyword = q;
|
||||
if (s && s !== 'all') params.status = s;
|
||||
const res: Record<string, any> = await serverApi.getServers(params);
|
||||
setServers(res?.list || []);
|
||||
setTotal(res?.total || 0);
|
||||
} catch (e) {
|
||||
console.error('加载服务器列表失败:', e);
|
||||
setServers([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索/筛选变化时重置到第1页
|
||||
const handleSearch = (val: string) => {
|
||||
setSearchQuery(val);
|
||||
@@ -123,10 +115,6 @@ export function ServerManager() {
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadData();
|
||||
}, [page, pageSize]);
|
||||
|
||||
// WebSocket 实时接收 Agent 指标推送 // Logan.Li
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -152,27 +140,30 @@ export function ServerManager() {
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === 'metrics_update') {
|
||||
setServers((prev) =>
|
||||
prev.map((s) => {
|
||||
// 通过 hostname 匹配(Agent 上报时使用 hostname)
|
||||
if (s.hostname === msg.hostname) {
|
||||
return {
|
||||
...s,
|
||||
cpu_usage: msg.cpu_usage,
|
||||
memory_usage: msg.memory_usage,
|
||||
disk_usage: msg.disk_usage,
|
||||
status: msg.status,
|
||||
tcp_established: msg.tcp_established,
|
||||
tcp_time_wait: msg.tcp_time_wait,
|
||||
net_rx_bps: msg.net_rx_bps,
|
||||
net_tx_bps: msg.net_tx_bps,
|
||||
disk_read_bps: msg.disk_read_bps,
|
||||
disk_write_bps: msg.disk_write_bps,
|
||||
};
|
||||
}
|
||||
return s;
|
||||
})
|
||||
);
|
||||
queryClient.setQueryData(queryKeys.servers(queryParams), (old: typeof data) => {
|
||||
if (!old) return old;
|
||||
return {
|
||||
...old,
|
||||
list: old.list.map((s) => {
|
||||
if (s.hostname === msg.hostname) {
|
||||
return {
|
||||
...s,
|
||||
cpu_usage: msg.cpu_usage,
|
||||
memory_usage: msg.memory_usage,
|
||||
disk_usage: msg.disk_usage,
|
||||
status: msg.status,
|
||||
tcp_established: msg.tcp_established,
|
||||
tcp_time_wait: msg.tcp_time_wait,
|
||||
net_rx_bps: msg.net_rx_bps,
|
||||
net_tx_bps: msg.net_tx_bps,
|
||||
disk_read_bps: msg.disk_read_bps,
|
||||
disk_write_bps: msg.disk_write_bps,
|
||||
};
|
||||
}
|
||||
return s;
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// 忽略非 JSON 消息
|
||||
@@ -201,14 +192,14 @@ export function ServerManager() {
|
||||
wsRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [queryClient, queryParams]);
|
||||
|
||||
// 导出 CSV
|
||||
const toggleSelectAll = () => {
|
||||
if (selectedServers.length === servers.length) {
|
||||
setSelectedServers([]);
|
||||
} else {
|
||||
setSelectedServers(servers.map((s) => s.id));
|
||||
setSelectedServers(servers.map((s) => String(s.id)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -230,9 +221,8 @@ export function ServerManager() {
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await serverApi.deleteServer(id);
|
||||
await deleteMutation.mutateAsync(id);
|
||||
toast.success('服务器已删除');
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error('删除服务器失败:', e);
|
||||
toast.error('删除失败');
|
||||
@@ -250,10 +240,9 @@ export function ServerManager() {
|
||||
});
|
||||
if (!ok) return;
|
||||
try {
|
||||
await serverApi.batchDelete(selectedServers.map((id) => Number(id)));
|
||||
await batchDeleteMutation.mutateAsync(selectedServers.map((id) => Number(id)));
|
||||
toast.success(`已删除 ${selectedServers.length} 台服务器`);
|
||||
setSelectedServers([]);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error('批量删除失败:', e);
|
||||
toast.error('批量删除失败');
|
||||
@@ -326,13 +315,12 @@ export function ServerManager() {
|
||||
}
|
||||
}
|
||||
if (editingId) {
|
||||
await serverApi.updateServer(editingId, payload);
|
||||
await updateMutation.mutateAsync({ id: editingId, data: payload });
|
||||
} else {
|
||||
await serverApi.createServer(payload);
|
||||
await createMutation.mutateAsync(payload);
|
||||
}
|
||||
toast.success(editingId ? '服务器已更新' : '服务器已创建');
|
||||
setFormOpen(false);
|
||||
loadData();
|
||||
} catch (e) {
|
||||
console.error('保存服务器失败:', e);
|
||||
toast.error('保存失败,请重试');
|
||||
@@ -349,7 +337,7 @@ export function ServerManager() {
|
||||
}
|
||||
const headers = ['主机名', 'IP地址', '公网IP', '端口', '操作系统', 'CPU(核)', '内存(GB)', '磁盘(GB)', '状态', '标签'];
|
||||
const rows = servers.map((s) => {
|
||||
const memGB = s.memory ? Math.round(s.memory / 1024) : 0;
|
||||
const memGB = s.memory ? Math.round(Number(s.memory) / 1024) : 0;
|
||||
const tags = getTags(s).join(';');
|
||||
return [s.hostname || '', s.ip || '', s.public_ip || '', s.port || 22, s.os || '',
|
||||
s.cpu || 0, memGB, s.disk || 0, s.status || '', tags];
|
||||
@@ -381,7 +369,7 @@ export function ServerManager() {
|
||||
<Button
|
||||
variant="outline"
|
||||
className="border-[var(--dg-border)] text-[var(--dg-text-secondary)] hover:text-[var(--dg-text-primary)] hover:bg-[var(--dg-bg-hover)]"
|
||||
onClick={() => loadData()}
|
||||
onClick={() => refetch()}
|
||||
>
|
||||
<RefreshCw className={cn('h-4 w-4 mr-2', loading && 'animate-spin')} />
|
||||
刷新
|
||||
@@ -527,7 +515,7 @@ export function ServerManager() {
|
||||
<ServerTableRow
|
||||
key={server.id}
|
||||
server={server}
|
||||
selected={selectedServers.includes(server.id)}
|
||||
selected={selectedServers.includes(String(server.id))}
|
||||
onToggleSelect={toggleSelect}
|
||||
onOpenTerminal={openTerminal}
|
||||
onOpenFiles={openFileBrowser}
|
||||
@@ -602,14 +590,14 @@ export function ServerManager() {
|
||||
open={cloudImportOpen}
|
||||
onClose={() => {
|
||||
setCloudImportOpen(false);
|
||||
loadData();
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
<ExcelServerImport
|
||||
open={excelImportOpen}
|
||||
onClose={() => {
|
||||
setExcelImportOpen(false);
|
||||
loadData();
|
||||
refetch();
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in new issue
Block a user