refactor(frontend): P0 API 层收口与 http 统一

- 统一 http.ts baseURL:get/post/download/upload 均走 resolveApiUrl
- agentUtils 下沉至 lib/agent-utils,API 层移除对 components 的依赖
- 扩展 metricsApi/snmpApi/prometheusProxyApi/zabbixProxyApi
- 扩展 pipelineApi/credentialApi 覆盖流水线与凭证场景
- 14 个组件/页面禁止直连 http,统一走 @/services/api
This commit is contained in:
Logan.Li committed 2026-06-25 23:49:46 +08:00
1 parent bfd6431b9d
commit 7fb2236b4d
27 files changed
+194 -150

No files matched your search

+2 -32
View File
@@ -1,32 +1,2 @@
// Agent 监控相关工具函数(从 AgentMonitor.tsx 提取,供多组件共享) // Logan.Li
/** 在线判定:3 分钟内有上报视为在线 */
export function isOnline( lastReport: any): boolean {
if (!lastReport) return false;
const t = new Date(lastReport).getTime();
if (Number.isNaN(t)) return false;
return Date.now() - t < 3 * 60 * 1000;
}
/** 相对时间格式化("3分钟前" / "2小时前") */
export function timeAgo( ts: any): string {
if (!ts) return '--';
const t = new Date(ts).getTime();
if (Number.isNaN(t)) return '--';
const diff = Math.floor((Date.now() - t) / 1000);
if (diff < 60) return `${diff}秒前`;
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
return new Date(ts).toLocaleString();
}
/** 兼容多种列表响应包装:数组 / {list} / {items} / {data} */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function asList( data: any): any[] {
if (!data) return [];
if (Array.isArray(data)) return data;
if (Array.isArray(data.list)) return data.list;
if (Array.isArray(data.items)) return data.items;
if (Array.isArray(data.data)) return data.data;
return [];
}
// 兼容旧路径,统一从 lib 导出 // Logan.Li
export { isOnline, timeAgo, asList } from '@/lib/agent-utils';
@@ -8,8 +8,7 @@ import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Plus, Key, Globe, Container } from 'lucide-react';
import { toast } from 'sonner';
import { deployApi } from '@/services/api';
import http from '@/services/http';
import { credentialApi, deployApi } from '@/services/api';
interface Repo {
id: number; name: string; url: string; credential_id: number | null;
@@ -48,7 +47,7 @@ export default function CredentialFormDialog({ open, onOpenChange, type, editId,
const loadCredentials = async () => {
try {
const res: Record<string, any> = await http.get('/credentials?page_size=100');
const res: Record<string, any> = await credentialApi.list({ page_size: 100 });
setCredentials(res?.list || []);
} catch { /* silent */ }
};
@@ -34,7 +34,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Di
import { toast } from 'sonner';
import { useConfirm } from '@/components/ui/confirm-dialog';
import http from '@/services/http';
import { credentialApi } from '@/services/api';
// ────── Types ──────
@@ -85,7 +85,7 @@ function CredDetailDialog({ open, onClose, credId }: { open: boolean; onClose: (
useEffect(() => {
if (!credId || !open) return;
setLoading(true);
http.get(`/credentials/${credId}`).then(( res: any) => {
credentialApi.get(credId).then(( res: any) => {
setDetail(res);
}).catch(() => toast.error('加载详情失败')).finally(() => setLoading(false));
}, [credId, open]);
@@ -372,10 +372,10 @@ function CredFormDialog({ open, onClose, onSaved, editData }: {
}
if (isEdit) {
await http.put(`/credentials/${editData!.id}`, payload);
await credentialApi.update(editData!.id, payload);
toast.success('已更新');
} else {
await http.post('/credentials', payload);
await credentialApi.create(payload);
toast.success('已创建');
}
onSaved();
@@ -643,11 +643,11 @@ export default function CredentialManager() {
const fetchAll = useCallback(async () => {
setLoading(true);
try {
const params = new URLSearchParams();
if (filterType) params.set('secret_type', filterType);
if (filterSearch) params.set('search', filterSearch);
params.set('page_size', '100');
const res: Record<string, any> = await http.get(`/credentials?${params.toString()}`);
const res: Record<string, any> = await credentialApi.list({
page_size: 100,
...(filterType ? { secret_type: filterType } : {}),
...(filterSearch ? { search: filterSearch } : {}),
});
setCredentials(res?.list || []);
} catch { toast.error('获取凭证列表失败'); }
finally { setLoading(false); }
@@ -664,7 +664,7 @@ export default function CredentialManager() {
});
if (!ok) return;
try {
await http.delete(`/credentials/${cred.id}`);
await credentialApi.delete(cred.id);
toast.success('已删除');
fetchAll();
} catch { toast.error('删除失败'); }
@@ -672,7 +672,7 @@ export default function CredentialManager() {
const handleToggleActive = async (cred: Credential) => {
try {
await http.put(`/credentials/${cred.id}`, { is_active: !cred.is_active });
await credentialApi.toggleActive(cred.id, !cred.is_active);
toast.success(cred.is_active ? '已禁用' : '已启用');
fetchAll();
} catch { toast.error('操作失败'); }
@@ -17,7 +17,7 @@ import {
Minimize2,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import http from '@/services/http';
import { pipelineApi } from '@/services/api';
import { useUserStore } from '@/store';
interface FileItem {
@@ -69,8 +69,7 @@ export function FileBrowserDialog({
setLoading(true);
setError('');
try {
const params = path ? `?path=${encodeURIComponent(path)}` : '';
const data = await http.get(`/pipelines/runs/${runId}/files${params}`);
const data = await pipelineApi.listRunFiles(runId, path || undefined);
setListing(data);
setCurrentPath(data?.current || '');
} catch (err: any) {
@@ -40,8 +40,7 @@ import {
Package,
} from 'lucide-react';
import { pipelineApi, deployApi } from '@/services/api';
import http from '@/services/http';
import { credentialApi, deployApi, pipelineApi } from '@/services/api';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { toast } from 'sonner';
import { nameToCode, getStatusBadge } from './pipeline-helpers';
@@ -140,7 +139,7 @@ export function PipelineManager() {
deployApi.getRepos().catch(() => ({ list: [] })),
deployApi.getRegistries().catch(() => ({ list: [] })),
deployApi.getEnvironments().catch(() => ({ list: [] })),
http.get('/credentials', { page_size: 100 }).catch(() => ({ list: [] })),
credentialApi.list({ page_size: 100 }).catch(() => ({ list: [] })),
] as const);
setRepos(Array.isArray((rR as Record<string, any>)?.list) ? (rR as Record<string, any>).list : []);
setRegistries(Array.isArray((rReg as Record<string, any>)?.list) ? (rReg as Record<string, any>).list : []);
@@ -891,7 +890,7 @@ function PipelineQueuePanel({ pipeline, clusterId: _cid }: { pipeline: any; clus
if (!code) return;
setLoading(true);
try {
const res: Record<string, any> = await http.get(`/pipelines/${code}/queue`);
const res: Record<string, any> = await pipelineApi.getQueue(code);
setQueue(res?.data || res);
} catch { setQueue(null); }
finally { setLoading(false); }
@@ -901,7 +900,7 @@ function PipelineQueuePanel({ pipeline, clusterId: _cid }: { pipeline: any; clus
const handleCancel = async (runId: number) => {
try {
await http.delete(`/pipelines/${code}/queue/${runId}`);
await pipelineApi.removeFromQueue(code, runId);
toast.success('已取消');
loadQueue();
} catch { toast.error('取消失败'); }
@@ -10,7 +10,7 @@ import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { toast } from 'sonner';
import http from '@/services/http';
import { pipelineApi } from '@/services/api';
import { FileBrowserDialog } from './FileBrowserDialog';
// ═══════════ 阶段定义 (与 PipelineManager 共用) ═══════════
@@ -147,7 +147,7 @@ export default function PipelineRunDetail() {
// 加载流水线详情
const fetchPipeline = useCallback(async () => {
try {
const data: Record<string, any> = await http.get(`/pipelines/${pipelineCode}`);
const data: Record<string, any> = await pipelineApi.getByCode(pipelineCode);
setPipeline(data?.data || data);
} catch { /* silent, pipeline info optional */ }
}, [pipelineCode]);
@@ -156,7 +156,7 @@ export default function PipelineRunDetail() {
const fetchRuns = useCallback(async () => {
setLoading(true);
try {
const data: Record<string, any> = await http.get(`/pipelines/${pipelineCode}/runs?page_size=${pageSize}`);
const data: Record<string, any> = await pipelineApi.getRunsByCode(pipelineCode, pageSize);
const list = data?.list || [];
setRuns(list);
if (!selectedRun && list.length > 0) {
@@ -180,7 +180,7 @@ export default function PipelineRunDetail() {
// 加载日志
const fetchLogs = useCallback(async (run: PipelineRun) => {
try {
const data: Record<string, any> = await http.get(`/pipelines/runs/${run.id}/log-stream`);
const data: Record<string, any> = await pipelineApi.getRunLogStream(run.id);
if (data?.logs) {
const parsed = typeof data.logs === 'string' ? JSON.parse(data.logs) : data.logs;
setLogs(Array.isArray(parsed) ? parsed : []);
@@ -232,7 +232,7 @@ export default function PipelineRunDetail() {
// 触发
const triggerRun = async () => {
try {
await http.post(`/pipelines/${pipelineCode}/trigger`, {});
await pipelineApi.triggerByCode(pipelineCode);
toast.success('已触发');
fetchRuns();
} catch { toast.error('触发失败'); }
@@ -241,7 +241,7 @@ export default function PipelineRunDetail() {
// 停止
const stopRun = async (runId: number) => {
try {
await http.post(`/pipelines/${pipelineCode}/runs/${runId}/stop`, {});
await pipelineApi.stopRunByCode(pipelineCode, runId);
toast.success('已停止');
fetchRuns();
} catch { toast.error('停止失败'); }
@@ -252,7 +252,7 @@ export default function PipelineRunDetail() {
if (selectedIds.size === 0) return;
setDeleting(true);
try {
await http.post(`/pipelines/${pipelineCode}/runs/batch-delete`, { ids: Array.from(selectedIds) });
await pipelineApi.batchDeleteRuns(pipelineCode, Array.from(selectedIds));
toast.success(`已删除 ${selectedIds.size} 条记录`);
setSelectedIds(new Set());
fetchRuns();
@@ -11,7 +11,7 @@ import 'xterm/css/xterm.css';
import { Maximize2, Minimize2, X, FolderOpen } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useUserStore } from '@/store';
import http from '@/services/http';
import { pipelineApi } from '@/services/api';
interface WorkspaceTermDialogProps {
open: boolean;
@@ -40,7 +40,7 @@ export function WorkspaceTermDialog({
// 获取 workspace 路径
useEffect(() => {
if (!open || !runId) return;
http.get(`/pipelines/runs/${runId}/workspace`)
pipelineApi.getWorkspace(runId)
.then(( data: any) => setWorkspace(data?.workspace_path || ''))
.catch(() => {});
}, [open, runId]);
@@ -39,8 +39,7 @@ import { dashboardApi, promDatasourceApi, zabbixDatasourceApi, agentApi, serverA
import { GrafanaPanelContent } from './GrafanaPanelContent';
import { ZabbixDashboard } from './ZabbixDashboard';
import { PrometheusDashboard } from './PrometheusDashboard';
import { http } from '@/services/http';
import { asList } from '@/components/agent/agentUtils';
import { asList } from '@/lib/agent-utils';
import { AgentWizard } from '@/components/agent/AgentWizard';
import { Checkbox } from '@/components/ui/checkbox';
import { useConfirm } from '@/components/ui/confirm-dialog';
@@ -94,7 +93,7 @@ const AgentTab: React.FC = () => {
finally { setBatchUninstalling(false); }
};
const totalPages = Math.max(1, Math.ceil(hosts.length / pageSize));
const fetch = async () => { try { setHosts(asList(await http.get('/agent/latest'))); } catch {} finally { setLoading(false); } };
const fetch = async () => { try { setHosts(asList(await agentApi.getLatest())); } catch {} finally { setLoading(false); } };
useEffect(() => { fetch(); const t = setInterval(fetch, 30000); return () => clearInterval(t); }, []);
const toggle = (f: SortField) => { if (sf === f) setSd(d => d === 'asc' ? 'desc' : 'asc'); else { setSf(f); setSd('desc'); } };
const sorted = [...hosts].sort((a, b) => {
@@ -6,7 +6,7 @@ import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { StatStrip } from '@/components/ui/stat-strip';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { http } from '@/services/http';
import { prometheusProxyApi } from '@/services/api';
interface Props { dsUrl?: string; }
@@ -139,7 +139,7 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
const doConnect = async (u: string) => {
setLoading(true); setError('');
try {
const data: Record<string, any> = await http.post('/prometheus/connect', { url: u });
const data: Record<string, any> = await prometheusProxyApi.connect(u);
setConnected(true); setTargets(data.targets||0);
loadAll(u);
} catch (e: any) { setConnected(false); setError((e as Record<string, any>).message||'连接失败'); }
@@ -149,7 +149,7 @@ export const PrometheusDashboard: React.FC<Props> = ({ dsUrl }) => {
const loadAll = async (u: string) => {
for (const t of ['kubernetes','node','pod','jvm']) {
try {
const data: Record<string, any> = await http.post('/prometheus/dashboard', { url: u, type: t });
const data: Record<string, any> = await prometheusProxyApi.dashboard(u, t);
const items: Record<string, any> = {};
for (const [k, v] of Object.entries(data||{}))
items[k] = Array.isArray(v) ? v : ((v as Record<string, any>)?.data?.result ? parsePrune((v as Record<string, any>).data.result) : v);
@@ -16,7 +16,7 @@ import {
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { http } from '@/services/http';
import { zabbixProxyApi } from '@/services/api';
interface ZabbixHost {
hostid: string; hostname: string; name: string; ip: string; available: string;
@@ -45,7 +45,7 @@ export const ZabbixDashboard: React.FC<Props> = ({ dsUrl, dsUsername, dsPassword
const doConnect = async (u: string, un: string, pw: string) => {
setLoading(true); setError('');
try {
const data: Record<string, any> = await http.post('/zabbix/connect', { url: u, username: un, password: pw });
const data: Record<string, any> = await zabbixProxyApi.connect(u, un, pw);
setHosts(data.hosts || []);
setVersion(data.version || '');
setConnected(true);
@@ -25,7 +25,7 @@ import {
SelectValue,
} from '@/components/ui/select';
import { Server, ChevronDown, Loader2, Save, Key, Shield } from 'lucide-react';
import http from '@/services/http';
import { credentialApi } from '@/services/api';
// 表单数据结构
export interface ServerFormData {
@@ -101,9 +101,9 @@ export function ServerForm({
// 加载凭证列表
useEffect(() => {
if (open) {
http.get('/credentials')
credentialApi.list()
.then(( res: any) => {
const list = Array.isArray(res) ? res : (res?.data || []);
const list = Array.isArray(res) ? res : (res?.list || res?.data || []);
setCredentials(list);
})
.catch(() => setCredentials([]));