fix: K8s 集群添加缺少 code 必填字段

- KubernetesClusterDialog: 新增 code 表单字段
- 名称变更自动生成 code(小写+连字符)
- handleSubmit 增加 code 校验 + toast 提示
- payload 增加 code 字段(后端 binding:required)
This commit is contained in:
attacker committed 2026-06-21 09:50:07 +00:00
1 parent f24fe43ac0
commit ce438c9f05
18 files changed
+3941 -144

No files matched your search

+126
View File
@@ -203,9 +203,112 @@ export const kubernetesApi = {
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=&token=
buildPodExecWsUrl: (
clusterId: number | string,
namespace: string,
podName: string,
container?: 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 (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();
},
scaleDeployment: async (clusterCode: string, namespace: string, name: string, replicas: number) => {
return http.post(`/kubernetes/${clusterCode}/namespace/${namespace}/deployments/${name}/scale`, { replicas });
},
@@ -340,6 +443,29 @@ export const kubernetesApi = {
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 ====================