Files
aiops/frontend/src/services/api.ts
T
attacker 2e66908e20 feat(k8s): M5 Deployment 暂停更新 + CI/CD 集成
- 后端: PauseDeployment/ResumeDeployment 客户端方法 (PATCH spec.paused)
- 后端: ID路由 + Kuboard code路由 pause/resume handlers
- 后端: Deployment 列表 summary 增加 paused 字段
- 前端: 操作菜单增加「暂停更新/恢复更新」动态切换 + 暂停 Badge
- 前端: CI/CD 发布按钮跳转流水线页面 (namespace+deployment 上下文)
- 前端: api.ts 增加 pauseDeployment/resumeDeployment 方法
- 验证: 5项端到端测试全部通过 (Python urllib)
2026-06-23 03:06:13 +08:00

1234 lines
51 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// API Service for AIOps Platform
// 所有模块均调用真实后端 API;暂无后端的模块返回空数据优雅降级
import { http, type ListResponse } from './http';
import { useUserStore } from '@/store';
// ==================== 用户认证 API ====================
export const userApi = {
login: async (username: string, password: string) => {
return http.post<{ token: string; expires_in: number; user: any }>(
'/auth/login',
{ username, password }
);
},
register: async (data: { username: string; password: string; nickname?: string; email?: string }) => {
return http.post('/auth/register', data);
},
getProfile: async () => {
return http.get('/user/profile');
},
updateProfile: async (data: any) => {
return http.put('/user/profile', data);
},
changePassword: async (data: { old_password: string; new_password: string }) => {
return http.put('/user/password', data);
},
getUsers: async (params?: { page?: number; page_size?: number; keyword?: string }) => {
return http.get<ListResponse>('/admin/users', params);
},
createUser: async (data: any) => {
return http.post('/admin/users', data);
},
updateUser: async (id: number | string, data: any) => {
return http.put(`/admin/users/${id}`, data);
},
deleteUser: async (id: number | string) => {
return http.delete(`/admin/users/${id}`);
},
batchImport: async (data: { users: any[]; skip_existing?: boolean }) => {
return http.post('/admin/users/batch-import', data);
},
downloadTemplate: () => {
const token = useUserStore.getState().token;
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1';
window.open(`${baseUrl}/admin/users/import-template?token=${token}`, '_blank');
},
// 角色列表 // Logan.Li
getRoles: async () => {
return http.get<ListResponse>('/user/roles');
},
// 角色管理(管理员) // Logan.Li
createRole: async (data: { name: string; code: string; description?: string; permission_ids?: number[] }) => {
return http.post('/admin/roles', data);
},
updateRole: async (id: number | string, data: { name?: string; code?: string; description?: string; permission_ids?: number[] }) => {
return http.put(`/admin/roles/${id}`, data);
},
deleteRole: async (id: number | string) => {
return http.delete(`/admin/roles/${id}`);
},
// 权限列表(供角色权限选择)
getPermissions: async () => {
return http.get<ListResponse>('/admin/permissions');
},
};
// ==================== LDAP 配置 API ====================
export const ldapApi = {
getConfig: async () => {
return http.get('/admin/ldap-config');
},
updateConfig: async (data: any) => {
return http.put('/admin/ldap-config', data);
},
testConnection: async (data?: { test_user?: string; test_password?: string }) => {
return http.post('/admin/ldap-config/test', data || {});
},
listUsers: async (ouDn?: string) => {
return http.post('/admin/ldap-config/users', { ou_dn: ouDn || '' });
},
batchImport: async (users: any[]) => {
return http.post('/admin/ldap-config/import', { users });
},
listOUs: async () => {
return http.post('/admin/ldap-config/ou-list', {});
},
createOU: async (data: { ou_name: string; parent_dn?: string; description?: string }) => {
return http.post('/admin/ldap-config/create-ou', data);
},
createUser: async (data: { username: string; password: string; display_name?: string; email?: string; ou_dn?: string }) => {
return http.post('/admin/ldap-config/create-user', data);
},
deleteUser: async (dn: string) => {
return http.post('/admin/ldap-config/delete-user', { dn });
},
updateUser: async (data: { dn: string; cn?: string; mail?: string; phone?: string; title?: string }) => {
return http.post('/admin/ldap-config/update-user', data);
},
};
// ==================== 仪表盘 API ====================
export const dashboardStatsApi = {
getStats: async () => {
return http.get('/dashboard/stats');
},
getAlerts: async (limit?: number) => {
return http.get('/dashboard/recent-alerts', limit ? { limit } : undefined);
},
getReleases: async (limit?: number) => {
return http.get('/dashboard/recent-releases', limit ? { limit } : undefined);
},
getMetrics: async (hours?: number) => {
return http.get('/dashboard/metrics', hours ? { hours } : undefined);
},
};
// ==================== 服务器管理 API ====================
export const serverApi = {
getServers: async (params?: { page?: number; page_size?: number; keyword?: string; status?: string }) => {
return http.get<ListResponse>('/servers', params);
},
getServerDetail: async (id: number | string) => {
return http.get(`/servers/${id}`);
},
getServerStats: async () => {
return http.get('/servers/stats');
},
createServer: async (data: any) => {
return http.post('/servers', data);
},
updateServer: async (id: number | string, data: any) => {
return http.put(`/servers/${id}`, data);
},
deleteServer: async (id: number | string) => {
return http.delete(`/servers/${id}`);
},
batchDelete: async (ids: number[]) => {
return http.post('/servers/batch-delete', { ids });
},
batchCreate: async (data: { servers: any[]; region_id?: string; auth_type?: string; credential_id?: number; default_port?: number }) => {
return http.post('/servers/batch', data);
},
getMetrics: async (id: number | string) => {
// 后端暂无单机 metrics,用 dashboard metrics
return http.get('/dashboard/metrics');
},
// 远程安装 Agent(通过 SSH 执行安装脚本),可选传入凭据
installAgent: async (id: number | string, token: string, creds?: { username?: string; password?: string; ssh_key?: string; auth_type?: string }) => {
const body: any = { token };
if (creds) Object.assign(body, creds);
return http.post<{ installed: boolean; arch: string; output: string }>(`/servers/${id}/install-agent`, body);
},
// 批量远程安装 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 ====================
export const kubernetesApi = {
getClusters: async () => {
return http.get<ListResponse>('/kubernetes-clusters');
},
getClusterDetail: async (id: number | string) => {
return http.get(`/kubernetes-clusters/${id}/detail`);
},
createCluster: async (data: any) => {
return http.post('/kubernetes-clusters', data);
},
updateCluster: async (id: number | string, data: any) => {
return http.put(`/kubernetes-clusters/${id}`, data);
},
deleteCluster: async (id: number | string) => {
return http.delete(`/kubernetes-clusters/${id}`);
},
getNodes: async (clusterId: number | string) => {
return http.get(`/kubernetes-clusters/${clusterId}/nodes`);
},
getPods: async (clusterId: number | string) => {
return http.get(`/kubernetes-clusters/${clusterId}/pods`);
},
getDeployments: async (clusterId: number | string) => {
return http.get(`/kubernetes-clusters/${clusterId}/deployments`);
},
getServices: async (clusterId: number | string) => {
return http.get(`/kubernetes-clusters/${clusterId}/services`);
},
getIngresses: async (clusterId: number | string) => {
return http.get(`/kubernetes-clusters/${clusterId}/ingresses`);
},
syncCluster: async (clusterId: number | string) => {
return http.post(`/kubernetes-clusters/${clusterId}/sync`);
},
getTopology: async (clusterId: number | string) => { // Logan.Li
return http.get(`/kubernetes-clusters/${clusterId}/topology`);
},
saveTopology: async (clusterId: number | string, layoutData: string) => { // Logan.Li
return http.put(`/kubernetes-clusters/${clusterId}/topology`, { layout_data: layoutData });
},
// === 工作负载操作 (Logan.Li) - Kuboard-style routes ===
getPodLogs: async (clusterCode: string, namespace: string, podName: string, tail: number = 200, container?: string) => {
const params = new URLSearchParams({ tail: String(tail) });
if (container) params.set('container', container);
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/logs?${params}`);
},
// 🆕 增强版 Pod 日志查询 (Logan <admin@attacker.club>)
// 支持容器选择 / 行数限制 / previous 容器日志 / sinceTime 时间范围
getPodLogsEnhanced: async (
clusterCode: string,
namespace: string,
podName: string,
opts: {
container?: string;
tailLines?: number; // 0 / undefined => 全部
previous?: boolean;
sinceTime?: string; // RFC3339,如 2024-01-01T00:00:00Z
} = {}
) => {
const params = new URLSearchParams();
if (opts.container) params.set('container', opts.container);
if (opts.tailLines && opts.tailLines > 0) params.set('tailLines', String(opts.tailLines));
if (opts.previous) params.set('previous', 'true');
if (opts.sinceTime) params.set('sinceTime', opts.sinceTime);
const qs = params.toString();
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/logs${qs ? `?${qs}` : ''}`);
},
// 🆕 构建日志流式 URL(tail -f 效果,fetch ReadableStream)(Logan <admin@attacker.club>)
buildPodLogsStreamUrl: (
clusterCode: string,
namespace: string,
podName: string,
opts: {
container?: string;
tailLines?: number;
previous?: boolean;
sinceTime?: string;
} = {}
) => {
const params = new URLSearchParams();
params.set('stream', 'true');
if (opts.container) params.set('container', opts.container);
if (opts.tailLines && opts.tailLines > 0) params.set('tailLines', String(opts.tailLines));
if (opts.previous) params.set('previous', 'true');
if (opts.sinceTime) params.set('sinceTime', opts.sinceTime);
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${window.location.protocol}//${window.location.host}`;
return `${baseUrl}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/logs?${params}`;
},
execPod: async (clusterCode: string, namespace: string, podName: string, command: string[], container?: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/exec`, { command, container });
},
// 🆕 Pod 级 exec WebSocket URL (Logan <admin@attacker.club>)
// ws://host/ws/kubernetes-exec/:clusterId/namespaces/:ns/pods/:pod?container=&command=&token=
buildPodExecWsUrl: (
clusterId: number | string,
namespace: string,
podName: string,
container?: string,
command?: string
) => {
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${proto}//${window.location.host}`;
const wsBase = baseUrl.replace(/^http/, 'ws').replace('/api/v1', '');
const token = useUserStore.getState().token || '';
const params = new URLSearchParams();
if (container) params.set('container', container);
if (command) params.set('command', command);
if (token) params.set('token', token);
const qs = params.toString();
return `${wsBase}/ws/kubernetes-exec/${clusterId}/namespaces/${namespace}/pods/${podName}${qs ? `?${qs}` : ''}`;
},
// === 🆕 容器文件浏览器 (Logan <admin@attacker.club>) ===
// 后端 API 并行开发中,UI 先行;返回结构兼容 {entries:[{name,isDir,size,modTime}]} 或数组
listPodFiles: async (
clusterCode: string,
namespace: string,
podName: string,
path: string,
container?: string
) => {
return http.post(
`/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/files/list`,
{ path, container }
);
},
// 构建文件下载 URL(带 token,供 window.open / fetch 使用)
buildPodFileDownloadUrl: (
clusterCode: string,
namespace: string,
podName: string,
path: string,
container?: string
) => {
const params = new URLSearchParams({ path });
if (container) params.set('container', container);
const token = useUserStore.getState().token || '';
if (token) params.set('token', token);
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${window.location.protocol}//${window.location.host}`;
return `${baseUrl}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/files/download?${params}`;
},
// 读取容器内文本文件(小文件预览)
readPodFileText: async (
clusterCode: string,
namespace: string,
podName: string,
path: string,
container?: string
) => {
const url = kubernetesApi.buildPodFileDownloadUrl(clusterCode, namespace, podName, path, container);
const resp = await fetch(url);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.text();
},
// 上传文件到 Pod 容器内指定目录
uploadPodFile: async (
clusterCode: string,
namespace: string,
podName: string,
targetPath: string,
file: File,
container?: string,
) => {
const formData = new FormData();
formData.append('file', file);
formData.append('path', targetPath);
if (container) formData.append('container', container);
const token = useUserStore.getState().token || localStorage.getItem('token') || '';
const baseUrl = import.meta.env.VITE_API_BASE_URL || `${window.location.protocol}//${window.location.host}`;
const resp = await fetch(
`${baseUrl}/api/v1/kubernetes/${clusterCode}/namespace/${namespace}/pods/${podName}/files/upload`,
{
method: 'POST',
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: formData,
},
);
const json = await resp.json();
if (!resp.ok || json.code !== 200) throw new Error(json.message || `HTTP ${resp.status}`);
return json.data;
},
scaleDeployment: async (clusterCode: string, namespace: string, name: string, replicas: number) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/scale`, { replicas });
},
restartDeployment: async (clusterCode: string, namespace: string, name: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/restart`);
},
pauseDeployment: async (clusterCode: string, namespace: string, name: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/pause`);
},
resumeDeployment: async (clusterCode: string, namespace: string, name: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/resume`);
},
updateDeploymentImage: async (clusterCode: string, namespace: string, name: string, image: string, containerName?: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/update-image`, { image, container_name: containerName });
},
// === HPA 管理 (Logan.Li) ===
getHPAs: async (clusterCode: string, namespace?: string) => {
if (namespace) return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/hpas`);
return http.get(`/kubernetes/${clusterCode}/hpas`); // AllNS route
},
createHPA: async (clusterCode: string, data: { name: string; namespace: string; targetKind: string; targetName: string; minReplicas: number; maxReplicas: number; cpuPercent: number; memPercent: number }) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/hpas`, data);
},
deleteHPA: async (clusterCode: string, namespace: string, name: string) => {
return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/hpas/${name}`);
},
// === CronJob 管理 (Logan.Li) ===
getCronJobs: async (clusterCode: string, namespace?: string) => {
if (namespace) return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs`);
return http.get(`/kubernetes-clusters/${clusterCode}/cronjobs`);
},
createCronJob: async (clusterCode: string, data: { name: string; namespace: string; schedule: string; image: string; command: string[] }) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${data.namespace}/cronjobs`, data);
},
suspendCronJob: async (clusterCode: string, namespace: string, name: string, suspend: boolean) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/suspend`, { suspend });
},
triggerCronJob: async (clusterCode: string, namespace: string, name: string) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}/trigger`);
},
deleteCronJob: async (clusterCode: string, namespace: string, name: string) => {
return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/cronjobs/${name}`);
},
// === Namespace-scoped resources (Kuboard-style) ===
getNamespaceDeployments: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments`);
},
getNamespacePods: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pods`);
},
getNamespaceServices: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/services`);
},
getNamespaceIngresses: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/ingresses`);
},
getNamespaceConfigMaps: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/configmaps`);
},
getNamespaceSecrets: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/secrets`);
},
// === Namespace (Logan.Li) ===
getNamespaces: async (clusterCode: string) => {
return http.get(`/kubernetes/${clusterCode}/namespaces`);
},
// === All-namespace listing (Logan.Li) ===
getAllDeployments: async (clusterCode: string) => {
return http.get(`/kubernetes/${clusterCode}/deployments`);
},
getAllPods: async (clusterCode: string) => {
return http.get(`/kubernetes/${clusterCode}/pods`);
},
// === PVC (存储) ===
getPVCs: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/pvcs`);
},
deletePVC: async (clusterCode: string, namespace: string, name: string) => {
return http.delete(`/kubernetes/${clusterCode}/namespace/${namespace}/pvcs/${name}`);
},
// === StatefulSet / DaemonSet ===
getStatefulSets: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/statefulsets`);
},
getDaemonSets: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/daemonsets`);
},
// === YAML ===
getResourceYaml: async (clusterCode: string, namespace: string, resource: string, name: string) => {
return http.getRaw(`/kubernetes/${clusterCode}/namespace/${namespace}/${resource}/${name}/yaml`);
},
updateResourceYaml: async (clusterCode: string, namespace: string, resource: string, name: string, yaml: string) => {
return http.putRaw(`/kubernetes/${clusterCode}/namespace/${namespace}/${resource}/${name}/yaml`, yaml, 'application/yaml');
},
// === Export CSV ===
exportAsCSV: (filename: string, data: any[]) => {
if (data.length === 0) return;
const headers = Object.keys(data[0]);
const csv = [headers.join(','), ...data.map((row: any) => headers.map(h => JSON.stringify(row[h] ?? '')).join(','))].join('\n');
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click();
},
exportAsJSON: (filename: string, data: any[]) => {
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click();
},
// === 🆕 Pod Metrics (kubectl top pods) ===
getPodMetrics: async (clusterCode: string, namespace?: string) => {
const params = namespace ? { namespace } : undefined;
return http.get(`/kubernetes/${clusterCode}/metrics/pods`, params);
},
// === 🆕 Events ===
getEvents: async (clusterCode: string, namespace?: string, type?: string) => {
const params: any = {};
if (namespace) params.namespace = namespace;
if (type) params.type = type;
return http.get(`/kubernetes/${clusterCode}/events`, params);
},
// === 🆕 Capacity Analysis ===
getCapacity: async (clusterCode: string, namespace?: string) => {
const params = namespace ? { namespace } : undefined;
return http.get(`/kubernetes/${clusterCode}/capacity`, params);
},
// === 🆕 YAML Apply ===
applyYAML: async (clusterCode: string, yaml: string) => {
return http.post(`/kubernetes/${clusterCode}/yaml/apply`, { yaml });
},
// === 🆕 ReplicaSet / Revision / Rollback (Logan.Li — Kuboard-style) ===
// 后端并行开发中,前端按约定路径预先对接;后端就绪后自动生效。
getReplicaSets: async (clusterCode: string, namespace: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/replicasets`);
},
getDeploymentRevisions: async (clusterCode: string, namespace: string, name: string) => {
return http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/revisions`);
},
rollbackDeployment: async (clusterCode: string, namespace: string, name: string, revision: number) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/rollback`, { revision });
},
// 单个 Deployment 详情(如后端提供专用端点则用之,否则从列表过滤)
getDeployment: async (clusterCode: string, namespace: string, name: string) => {
try {
return await http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}`);
} catch {
// 降级:从列表中过滤
const res: any = await http.get(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments`);
const list = Array.isArray(res) ? res : (res?.list || res?.items || res?.data || []);
return list.find((d: any) => d.name === name) || null;
}
},
};
// ==================== 告警 API ====================
export const alertApi = {
getAlerts: async (params?: { page?: number; page_size?: number; status?: string; severity?: string }) => {
return http.get<ListResponse>('/alerts', params);
},
getRules: async () => {
return http.get('/alerts/rules');
},
createRule: async (data: any) => {
return http.post('/alerts/rules', data);
},
updateRule: async (id: number | string, data: any) => {
return http.put(`/alerts/rules/${id}`, data);
},
deleteRule: async (id: number | string) => {
return http.delete(`/alerts/rules/${id}`);
},
batchDeleteRules: async (ids: number[]) => {
return http.delete('/alerts/rules/batch-delete', { ids });
},
toggleRule: async (id: number | string) => {
return http.post(`/alerts/rules/${id}/toggle`);
},
acknowledge: async (id: number | string) => {
return http.post(`/alerts/${id}/ack`);
},
// 通知渠道管理
getNotifyChannels: async () => {
return http.get('/alerts/notify-channels');
},
createNotifyChannel: async (data: any) => {
return http.post('/alerts/notify-channels', data);
},
updateNotifyChannel: async (id: number | string, data: any) => {
return http.put(`/alerts/notify-channels/${id}`, data);
},
deleteNotifyChannel: async (id: number | string) => {
return http.delete(`/alerts/notify-channels/${id}`);
},
testNotifyChannel: async (id: number | string) => {
return http.post(`/alerts/notify-channels/${id}/test`);
},
};
// ==================== 流水线 API ====================
export const pipelineApi = {
getPipelines: async (params?: { page?: number; page_size?: number }) => {
return http.get<ListResponse>('/pipelines', params);
},
getPipelineDetail: async (id: number | string) => {
return http.get(`/pipelines/${id}`);
},
createPipeline: async (data: any) => {
return http.post('/pipelines', data);
},
updatePipeline: async (id: number | string, data: any) => {
return http.put(`/pipelines/${id}`, data);
},
deletePipeline: async (id: number | string) => {
return http.delete(`/pipelines/${id}`);
},
triggerPipeline: async (id: number | string) => {
return http.post(`/pipelines/${id}/trigger`);
},
getRuns: async (id: number | string) => {
return http.get(`/pipelines/${id}/runs`);
},
// 运行控制
stopRun: async (pipelineId: number | string, runId: number | string) => {
return http.post(`/pipelines/${pipelineId}/runs/${runId}/stop`);
},
getRunLog: async (pipelineId: number | string, runId: number | string) => {
return http.get(`/pipelines/${pipelineId}/runs/${runId}/log`);
},
// 成员管理
getMembers: async (pipelineId: number | string) => {
return http.get(`/pipelines/${pipelineId}/members`);
},
addMember: async (pipelineId: number | string, data: { username: string; role: string }) => {
return http.post(`/pipelines/${pipelineId}/members`, data);
},
removeMember: async (pipelineId: number | string, memberId: number | string) => {
return http.delete(`/pipelines/${pipelineId}/members/${memberId}`);
},
// 组授权
assignGroup: async (pipelineId: number | string, data: { group_id: number; role: string }) => {
return http.post(`/pipelines/${pipelineId}/groups`, data);
},
};
// ==================== 应用发布 API ====================
export const releaseApi = {
getReleases: async (params?: { page?: number; page_size?: number }) => {
return http.get<ListResponse>('/releases', params);
},
createRelease: async (data: any) => {
return http.post('/releases', data);
},
approveRelease: async (id: number | string) => {
return http.post(`/releases/${id}/approve`);
},
rejectRelease: async (id: number | string) => {
return http.post(`/releases/${id}/reject`);
},
deployRelease: async (id: number | string) => {
return http.post(`/releases/${id}/deploy`);
},
rollbackRelease: async (id: number | string) => {
return http.post(`/releases/${id}/rollback`);
},
};
// ==================== 定时任务 API ====================
export const cronJobApi = {
getJobs: async (params?: { page?: number; page_size?: number }) => {
return http.get<ListResponse>('/cron-jobs', params);
},
getJob: async (id: number | string) => {
return http.get(`/cron-jobs/${id}`);
},
createJob: async (data: any) => {
return http.post('/cron-jobs', data);
},
updateJob: async (id: number | string, data: any) => {
return http.put(`/cron-jobs/${id}`, data);
},
deleteJob: async (id: number | string) => {
return http.delete(`/cron-jobs/${id}`);
},
toggleJob: async (id: number | string) => {
return http.post(`/cron-jobs/${id}/toggle`);
},
executeJob: async (id: number | string) => {
return http.post(`/cron-jobs/${id}/execute`);
},
batchDelete: async (ids: number[]) => {
return http.delete('/cron-jobs/batch-delete', { ids });
},
};
// ==================== 脚本 API ====================
export const scriptApi = {
getScripts: async (params?: { page?: number; page_size?: number }) => {
return http.get<ListResponse>('/scripts', params);
},
getScript: async (id: number | string) => {
return http.get(`/scripts/${id}`);
},
createScript: async (data: any) => {
return http.post('/scripts', data);
},
updateScript: async (id: number | string, data: any) => {
return http.put(`/scripts/${id}`, data);
},
deleteScript: async (id: number | string) => {
return http.delete(`/scripts/${id}`);
},
batchDelete: async (ids: number[]) => {
return http.post('/scripts/batch-delete', { ids });
},
executeScript: async (id: number | string) => {
return http.post(`/scripts/${id}/execute`);
},
};
// ==================== 任务执行记录 API ====================
export const taskExecutionApi = {
getExecutions: async (params?: { task_type?: string; task_id?: string; page?: number; page_size?: number }) => {
return http.get<ListResponse>('/task-executions', params);
},
};
// ==================== 审批流程 API ====================
export const approvalApi = {
getTemplates: async () => {
return http.get('/approvals/templates');
},
getTemplate: async (id: number | string) => {
return http.get(`/approvals/templates/${id}`);
},
createTemplate: async (data: any) => {
return http.post('/approvals/templates', data);
},
updateTemplate: async (id: number | string, data: any) => {
return http.put(`/approvals/templates/${id}`, data);
},
deleteTemplate: async (id: number | string) => {
return http.delete(`/approvals/templates/${id}`);
},
getProcesses: async (params?: { page?: number; page_size?: number; status?: string }) => {
return http.get('/approvals/processes', params);
},
createProcess: async (data: any) => {
return http.post('/approvals/processes', data);
},
approveProcess: async (id: number | string, comment?: string) => {
return http.post(`/approvals/processes/${id}/approve`, { comment });
},
rejectProcess: async (id: number | string, comment?: string) => {
return http.post(`/approvals/processes/${id}/reject`, { comment });
},
};
// ==================== 云管理 API ====================
export const cloudApi = {
getAccounts: async () => {
return http.get<ListResponse>('/cloud-accounts');
},
createAccount: async (data: any) => {
return http.post('/cloud-accounts', data);
},
updateAccount: async (id: number | string, data: any) => {
return http.put(`/cloud-accounts/${id}`, data);
},
deleteAccount: async (id: number | string) => {
return http.delete(`/cloud-accounts/${id}`);
},
// ==================== 云同步 API ====================
// 同步云账号的区域列表到本地
syncRegions: async (id: number | string) => {
return http.post(`/cloud-accounts/${id}/sync-regions`);
},
// 获取云账号的区域列表
getAccountRegions: async (id: number | string) => {
return http.get<ListResponse>(`/cloud-accounts/${id}/regions`);
},
// 发现指定区域的云主机实例
discoverInstances: async (id: number | string, regions: string[]) => {
return http.post<{ list: any[]; total: number; errors: string[] }>(`/cloud-accounts/${id}/discover-instances`, { regions });
},
// 导入选中的云主机实例为本地服务器
importInstances: async (id: number | string, instances: any[], opts?: { default_port?: number; credential_id?: number }) => {
return http.post<{ imported: number; skipped: number }>(`/cloud-accounts/${id}/import-instances`, { instances, ...opts });
},
getDomains: async (params?: { page?: number; page_size?: number; keyword?: string }) => {
return http.get<ListResponse>('/domains', params);
},
createDomain: async (data: any) => {
return http.post('/domains', data);
},
updateDomain: async (id: number | string, data: any) => {
return http.put(`/domains/${id}`, data);
},
deleteDomain: async (id: number | string) => {
return http.delete(`/domains/${id}`);
},
};
// ==================== 云平台 API ====================
export const platformApi = {
list: async () => {
return http.get<ListResponse>('/platforms');
},
create: async (data: any) => {
return http.post('/platforms', data);
},
update: async (id: number | string, data: any) => {
return http.put(`/platforms/${id}`, data);
},
delete: async (id: number | string) => {
return http.delete(`/platforms/${id}`);
},
};
// ==================== 云账单 API ====================
export const billingApi = {
getStats: async () => {
return http.get('/cloud-billings/stats');
},
list: async (params?: { provider?: string; start_date?: string; end_date?: string; page?: number; page_size?: number }) => {
return http.get<ListResponse>('/cloud-billings', params);
},
create: async (data: any) => {
return http.post('/cloud-billings', data);
},
batchCreate: async (records: any[]) => {
return http.post('/cloud-billings/batch', { records });
},
remove: async (id: number | string) => {
return http.delete(`/cloud-billings/${id}`);
},
getProviders: async () => {
return http.get('/cloud-billings/providers');
},
// 预警规则
listAlertRules: async () => {
return http.get<ListResponse>('/billing-alerts');
},
createAlertRule: async (data: any) => {
return http.post('/billing-alerts', data);
},
updateAlertRule: async (id: number | string, data: any) => {
return http.put(`/billing-alerts/${id}`, data);
},
deleteAlertRule: async (id: number | string) => {
return http.delete(`/billing-alerts/${id}`);
},
toggleAlertRule: async (id: number | string) => {
return http.post(`/billing-alerts/${id}/toggle`);
},
};
// ==================== Agent 监控 API ====================
export const agentApi = {
getLatest: () => http.get('/agent/latest'),
getHistory: (hostname: string, hours = 1) => http.get(`/agent/history/${hostname}?hours=${hours}`),
createTask: (data: { hostname: string; type: string; command: string; timeout?: number }) => http.post('/agent/tasks', data),
listTasks: (params?: Record<string, string>) => http.get('/agent/tasks', params),
// 直连 SSH 执行命令(即时返回)
execCommand: (data: { hostname: string; command: string; timeout?: number }) => http.post('/agent/exec', data),
// 聚合查询
getAggregate: (params: { hostname: string; metric: string; interval: string; from?: string; to?: string }) => http.get('/agent/aggregate', params),
// 触发聚合
aggregate: (data: { hostname: string; metric: string }) => http.post('/agent/aggregate', data),
listRules: () => http.get('/agent/rules'),
createRule: (data: any) => http.post('/agent/rules', data),
updateRule: (id: number, data: any) => http.put(`/agent/rules/${id}`, data),
deleteRule: (id: number) => http.delete(`/agent/rules/${id}`),
quickSetup: (type?: string) => http.post('/agent/rules/quick-setup', { type: type || 'ecs' }),
// Agent 安装/卸载
uninstallAgent: (data: { hostname?: string; server_id?: number }) => http.post('/agent/uninstall', data),
batchUninstall: (serverIds: number[]) => http.post('/agent/batch-uninstall', { server_ids: serverIds }),
batchUpgrade: (serverIds: number[]) => http.post('/agent/batch-upgrade', { server_ids: serverIds }),
listAlerts: (params?: Record<string, string>) => http.get('/agent/alerts', params),
resolveAlert: (id: number) => http.post(`/agent/alerts/${id}/resolve`),
};
// ==================== 区域管理 API ====================
export const regionApi = {
list: async () => {
return http.get<ListResponse>('/regions');
},
create: async (data: any) => {
return http.post('/regions', data);
},
update: async (id: number | string, data: any) => {
return http.put(`/regions/${id}`, data);
},
delete: async (id: number | string) => {
return http.delete(`/regions/${id}`);
},
// 批量删除 // Logan.Li
batchDelete: async (ids: number[]) => {
return http.delete('/regions/batch-delete', { ids });
},
};
// ==================== 资源模型 API ====================
export const resourceModelApi = {
list: async () => {
return http.get<ListResponse>('/resource-models');
},
create: async (data: any) => {
return http.post('/resource-models', data);
},
update: async (id: number | string, data: any) => {
return http.put(`/resource-models/${id}`, data);
},
delete: async (id: number | string) => {
return http.delete(`/resource-models/${id}`);
},
};
// ==================== 审计日志 API ====================
export const auditApi = {
getLogs: async (params?: { page?: number; page_size?: number; module?: string; action?: string }) => {
return http.get<ListResponse>('/audit/logs', params);
},
getModules: async () => {
return http.get('/audit/modules');
},
getActions: async () => {
return http.get('/audit/actions');
},
getStatistics: async () => {
return http.get('/audit/statistics');
},
clearLogs: async () => {
return http.post('/audit/logs/clear');
},
};
// ==================== 终端会话审计 API(WebSSH 录制) ==================== // Logan.Li
export const terminalSessionApi = {
list: async (params?: { page?: number; page_size?: number; server_id?: number; username?: string; status?: string }) => {
return http.get<ListResponse>('/terminal-sessions', params);
},
get: async (id: number) => {
return http.get(`/terminal-sessions/${id}`);
},
getRecord: async (id: number) => {
return http.get<string>(`/terminal-sessions/${id}/record`);
},
delete: async (id: number) => {
return http.delete(`/terminal-sessions/${id}`);
},
batchDelete: async (ids: number[]) => {
return http.post('/terminal-sessions/batch-delete', { ids });
},
};
// ==================== 发布系统 API(仓库/镜像仓库认证) ====================
export const deployApi = {
// Git 仓库认证
getRepos: () => http.get<ListResponse>('/deploy/repos'),
getRepo: (id: number | string) => http.get(`/deploy/repos/${id}`),
createRepo: (data: any) => http.post('/deploy/repos', data),
updateRepo: (id: number | string, data: any) => http.put(`/deploy/repos/${id}`, data),
deleteRepo: (id: number | string) => http.delete(`/deploy/repos/${id}`),
// 镜像仓库认证
getRegistries: () => http.get<ListResponse>('/deploy/registries'),
getRegistry: (id: number | string) => http.get(`/deploy/registries/${id}`),
createRegistry: (data: any) => http.post('/deploy/registries', data),
updateRegistry: (id: number | string, data: any) => http.put(`/deploy/registries/${id}`, data),
deleteRegistry: (id: number | string) => http.delete(`/deploy/registries/${id}`),
// 环境管理
getEnvironments: () => http.get<ListResponse>('/deploy/environments'),
createEnvironment: (data: any) => http.post('/deploy/environments', data),
updateEnvironment: (id: number | string, data: any) => http.put(`/deploy/environments/${id}`, data),
deleteEnvironment: (id: number | string) => http.delete(`/deploy/environments/${id}`),
// Item Role 权限管理 (Jenkins Role Strategy 风格)
getItemRoles: () => http.get<ListResponse>('/admin/item-roles'),
createItemRole: (data: any) => http.post('/admin/item-roles', data),
updateItemRole: (id: number | string, data: any) => http.put(`/admin/item-roles/${id}`, data),
deleteItemRole: (id: number | string) => http.delete(`/admin/item-roles/${id}`),
getUserItemRoles: (userId: number | string) => http.get(`/admin/users/${userId}/item-roles`),
assignItemRole: (userId: number | string, data: { user_id: number; item_role_id: number }) => http.post(`/admin/users/${userId}/item-roles`, data),
removeItemRole: (userId: number | string, roleId: number | string) => http.delete(`/admin/users/${userId}/item-roles/${roleId}`),
// 用户组管理
getGroups: () => http.get<ListResponse>('/deploy/groups'),
createGroup: (data: any) => http.post('/deploy/groups', data),
updateGroup: (id: number | string, data: any) => http.put(`/deploy/groups/${id}`, data),
deleteGroup: (id: number | string) => http.delete(`/deploy/groups/${id}`),
getGroupMembers: (id: number | string) => http.get(`/deploy/groups/${id}/members`),
addGroupMember: (id: number | string, data: { username: string }) => http.post(`/deploy/groups/${id}/members`, data),
removeGroupMember: (id: number | string, memberId: number | string) => http.delete(`/deploy/groups/${id}/members/${memberId}`),
// 工具配置 // Logan.Li
getTools: () => http.get<ListResponse>('/deploy/tools'),
getTool: (id: number | string) => http.get(`/deploy/tools/${id}`),
updateTool: (id: number | string, data: any) => http.put(`/deploy/tools/${id}`, data),
installTool: (id: number | string) => http.post(`/deploy/tools/${id}/install`),
checkTool: (id: number | string) => http.post(`/deploy/tools/${id}/check`),
};
// ==================== Ansible 自动化 API ====================
export const ansibleApi = {
// Playbook 管理
getPlaybooks: async (params?: { page?: number; page_size?: number; keyword?: string }) => {
return http.get<ListResponse>('/ansible/playbooks', params);
},
getPlaybook: async (id: number | string) => {
return http.get(`/ansible/playbooks/${id}`);
},
createPlaybook: async (data: any) => {
return http.post('/ansible/playbooks', data);
},
updatePlaybook: async (id: number | string, data: any) => {
return http.put(`/ansible/playbooks/${id}`, data);
},
deletePlaybook: async (id: number | string) => {
return http.delete(`/ansible/playbooks/${id}`);
},
// 批量删除 Playbook // Logan.Li
batchDeletePlaybooks: async (ids: number[]) => {
return http.delete('/ansible/playbooks/batch-delete', { ids });
},
executePlaybook: async (id: number | string, targetHosts?: string[]) => {
return http.post(`/ansible/playbooks/${id}/execute`, targetHosts ? { target_hosts: targetHosts } : undefined);
},
// 克隆 Playbook 为自定义模板 // Logan.Li
clonePlaybook: async (id: number | string, name?: string) => {
return http.post(`/ansible/playbooks/${id}/clone`, name ? { name } : undefined);
},
// 主机匹配预览 // Logan.Li
matchHosts: async (params: { pattern?: string; match_type?: string; page?: number; page_size?: number }) => {
return http.get<ListResponse>('/ansible/match-hosts', params);
},
// 任务执行记录
getJobs: async (params?: { page?: number; page_size?: number; status?: string }) => {
return http.get<ListResponse>('/ansible/jobs', params);
},
getJob: async (id: number | string) => {
return http.get(`/ansible/jobs/${id}`);
},
// 清单(Inventory)管理
getInventories: async (params?: { page?: number; page_size?: number; keyword?: string }) => {
return http.get<ListResponse>('/ansible/inventories', params);
},
createInventory: async (data: any) => {
return http.post('/ansible/inventories', data);
},
updateInventory: async (id: number | string, data: any) => {
return http.put(`/ansible/inventories/${id}`, data);
},
deleteInventory: async (id: number | string) => {
return http.delete(`/ansible/inventories/${id}`);
},
};
// ==================== 告警中心 API(基于 /alerts 真实路由) ====================
// 说明:alertCenterApi 与 alertApi 共用同一套后端 /alerts 路由,此处独立封装以便告警中心页使用
export const alertCenterApi = {
getAlerts: async (params?: { page?: number; page_size?: number; status?: string; severity?: string }) => {
return http.get<ListResponse>('/alerts', params);
},
getRules: async () => {
return http.get('/alerts/rules');
},
getChannels: async () => {
return http.get('/alerts/notify-channels');
},
acknowledge: async (id: number | string) => {
return http.post(`/alerts/${id}/ack`);
},
resolve: async (id: number | string) => {
return http.post(`/alerts/${id}/ack`);
},
silence: async (_id: number | string, _duration: string) => ({ success: true }),
toggleRule: async (id: number | string) => {
return http.post(`/alerts/rules/${id}/toggle`);
},
createRule: async (data: any) => {
return http.post('/alerts/rules', data);
},
deleteRule: async (id: number | string) => {
return http.delete(`/alerts/rules/${id}`);
},
// 一键报警 — 后端批量创建默认规则
quickSetup: async (type: string = 'ecs') => {
return http.post('/alerts/quick-setup', { type });
},
};
// ==================== 站点拨测 API ====================
export const siteApi = {
getStats: async () => {
return http.get('/site/stats');
},
getTasks: async () => {
return http.get<{ list: any[]; total: number }>('/site/tasks');
},
createTask: async (data: { name: string; url: string; type: string; frequency_sec?: number; timeout_sec?: number; expected_status_code?: number }) => {
return http.post('/site/tasks', data);
},
deleteTask: async (id: number | string) => {
return http.delete(`/site/tasks/${id}`);
},
toggleTask: async (id: number | string, enabled: boolean) => {
return http.put(`/site/tasks/${id}`, { enabled });
},
};
// ==================== 链路追踪 API(暂无后端,返回空结果优雅降级) ====================
export const traceApi = {
getTraces: async (_params: any) => ({ data: [], total: 0 }),
getServices: async (_timeRange: string) => ({ data: [], total: 0 }),
getTraceDetail: async (_traceId: string) => ({ data: { spans: [] } }),
};
// ==================== Grafana 仪表盘 API(暂无后端,返回空结果优雅降级) ====================
export const dashboardApi = {
getDashboards: async () => ({ data: [], total: 0 }),
getDatasources: async () => ({ data: [], total: 0 }),
createDashboard: async (_data: any) => ({ success: false }),
updateDashboard: async (_id: number | string, _data: any) => ({ success: false }),
deleteDashboard: async (_id: number | string) => ({ success: false }),
createDatasource: async (_data: any) => ({ success: false }),
};
// ==================== Prometheus 数据源管理 API ==================== // Logan.Li
export const promDatasourceApi = {
list: () => http.get('/prometheus-datasources'),
get: (id: number | string) => http.get(`/prometheus-datasources/${id}`),
create: (data: any) => http.post('/prometheus-datasources', data),
update: (id: number | string, data: any) => http.put(`/prometheus-datasources/${id}`, data),
delete: (id: number | string) => http.delete(`/prometheus-datasources/${id}`),
setDefault: (id: number | string) => http.post(`/prometheus-datasources/${id}/default`),
testConnection: (data: any) => http.post('/prometheus-datasources/test', data),
};
// ==================== Zabbix 数据源管理 API ==================== // Logan.Li
export const zabbixDatasourceApi = {
list: () => http.get('/zabbix-datasources'),
get: (id: number | string) => http.get(`/zabbix-datasources/${id}`),
create: (data: any) => http.post('/zabbix-datasources', data),
update: (id: number | string, data: any) => http.put(`/zabbix-datasources/${id}`, data),
delete: (id: number | string) => http.delete(`/zabbix-datasources/${id}`),
setDefault: (id: number | string) => http.post(`/zabbix-datasources/${id}/default`),
testConnection: (data: any) => http.post('/zabbix-datasources/test', data),
};
// AI 智能助手 API(后端 LLM 配置 + 对话代理,OpenAI 兼容) // Logan.Li
export interface LLMConfigData {
configured?: boolean;
provider: string;
model: string;
api_key: string;
endpoint: string;
temperature: number;
max_tokens: number;
system_prompt: string;
enabled: boolean;
}
export interface ChatResult {
content: string;
model: string;
tokens_used: number;
}
// 对话历史条目(后端 ChatHistory 序列化) // Logan.Li
export interface ChatHistoryItem {
id: number;
user_id: number;
session_id: string;
role: 'user' | 'assistant';
content: string;
model?: string;
created_at: string;
}
export const aiApi = {
// 获取 LLM 配置(API Key 已脱敏)
getConfig: () => http.get<LLMConfigData>('/aiops/llm-config'),
// 保存 LLM 配置(api_key 若为脱敏值则后端保留原值)
saveConfig: (data: LLMConfigData) => http.put<LLMConfigData>('/aiops/llm-config', data),
// 测试 LLM 连接(发送 ping)
testConnection: () => http.post('/aiops/llm-config/test'),
// AI 对话(messages 含历史 + 当前用户消息,session_id 区分多轮对话)
chat: (messages: { role: 'user' | 'assistant'; content: string }[], sessionId?: string) =>
http.post<ChatResult>('/aiops/chat', { messages, session_id: sessionId || '' }),
// AI 流式对话(SSE)。逐块回调 onChunk,返回完整内容。错误抛出 Error。
// 使用裸 fetch 绕过 http.ts(后者会 await json() 一次性消费) // Logan.Li
chatStream: async (
messages: { role: 'user' | 'assistant'; content: string }[],
onChunk: (delta: string) => void,
signal?: AbortSignal,
sessionId?: string,
): Promise<string> => {
const token = useUserStore.getState().token;
const res = await fetch('/api/v1/aiops/chat/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ messages, session_id: sessionId || '' }),
signal,
});
if (res.status === 401) {
useUserStore.getState().logout();
window.location.href = '/login';
throw new Error('登录已过期,请重新登录');
}
if (!res.body) throw new Error('流式响应不可用');
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
let fullContent = '';
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE 事件之间以 \n\n 分隔
const events = buffer.split('\n\n');
buffer = events.pop() ?? '';
for (const evt of events) {
const lines = evt.split('\n');
let eventType = 'message';
let dataLines: string[] = [];
for (const ln of lines) {
if (ln.startsWith('event:')) eventType = ln.slice(6).trim();
else if (ln.startsWith('data:')) dataLines.push(ln.slice(5).trim());
}
const data = dataLines.join('');
if (!data) continue;
if (eventType === 'error') {
// {code, message}
try {
const obj = JSON.parse(data);
throw new Error(obj.message || 'LLM 流式响应错误');
} catch (parseErr) {
if (parseErr instanceof Error && parseErr.message) throw parseErr;
throw new Error('LLM 流式响应错误');
}
}
if (eventType === 'done') {
// 服务端结束(可能含 model 字段)
continue;
}
// message:data 是 {"content":"..."}
try {
const obj = JSON.parse(data);
if (typeof obj.content === 'string') {
fullContent += obj.content;
onChunk(obj.content);
}
} catch {
// 非 JSON 数据忽略(如 [DONE] 或注释)
}
}
}
return fullContent;
},
// 获取当前用户对话历史(按 session_id 过滤,默认最新 session)
getHistory: (sessionId?: string) => {
const params = sessionId ? `?session_id=${encodeURIComponent(sessionId)}` : '';
return http.get<{ list: ChatHistoryItem[]; total: number }>(`/aiops/chat-history${params}`);
},
// 清空当前用户对话历史
clearHistory: () => http.delete('/aiops/chat-history'),
};
// ==================== 凭据管理 API(SSH密钥/API Token/密码) ==================== // Logan.Li
export const credentialApi = {
list: () => http.get<{ list: any[]; total: number }>('/credentials'),
create: (data: { name: string; type: string; content: string; description?: string }) =>
http.post('/credentials', data),
update: (id: number | string, data: { name: string; type: string; content?: string; description?: string }) =>
http.put(`/credentials/${id}`, data),
delete: (id: number | string) => http.delete(`/credentials/${id}`),
};
// 服务器凭据关联 API
export const serverCredentialApi = {
assign: (serverId: number | string, credentialId: number) =>
http.post(`/servers/${serverId}/assign-credential`, { credential_id: credentialId }),
};