fix: K8s 集群添加缺少 code 必填字段
- KubernetesClusterDialog: 新增 code 表单字段 - 名称变更自动生成 code(小写+连字符) - handleSubmit 增加 code 校验 + toast 提示 - payload 增加 code 字段(后端 binding:required)
This commit is contained in:
1 parent
f24fe43ac0
commit
ce438c9f05
18 files changed
+3941
-144
No files matched your search
@@ -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 ====================
|
||||
|
||||
Reference in new issue
Block a user