feat: 新增日志中心、功能优化与代码重构

- 新增 log_datasource.go / log_proxy.go / user_api_token.go 后端控制器
- 新增 log_datasource.go model
- 新增 monitor/log-center.ts API 模块
- 新增 LogCenter 前端组件及页面
- 重构 deployment 相关组件 (HPDA/UpdateImageModal/bundle-yaml 等)
- 新增 ResetPasswordDialog / code-snippet-block / copy-button 等 UI 组件
- 重构多个 k8s 相关组件 (TopologyView/ResourceVisualEditor/PodFileBrowser 等)
- 更新多个 API 和工具函数 (auth.ts/monitor/index.ts/request.ts 等)
- 更新静态资源 (index.html + 新 JS/CSS bundle)
- 删除废弃静态资源文件
This commit is contained in:
Logan committed 2026-06-26 23:18:05 +08:00
1 parent 5ca0e6d83a
commit 8d46e8f352
197 files changed
+5014 -815

No files matched your search

@@ -2,7 +2,7 @@
// - 顶部操作菜单: 编辑 | 预览/编辑YAML | 调整镜像版本 | 伸缩 | 日志/终端 | 重启 | 暂停/恢复更新 | CI/CD发布 | 删除 | 元数据 | 运行时 | 服务 | 应用路由
// - 三模式: view(只读增强看板)/ edit(可视化编辑器)/ yaml(YAML编辑器)
// 作者: Logan.Li
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
@@ -26,6 +26,7 @@ import { useToast } from '@/hooks/use-toast';
import { useConfirm } from '@/components/ui/confirm-dialog';
import { useUserStore } from '@/store';
import { YamlViewer } from './YamlViewer';
import { loadDeploymentBundleYaml, saveDeploymentBundleYaml } from './deployment-bundle-yaml';
import { DeploymentView } from './DeploymentView';
import { PodExecTerminal } from './PodExecTerminal';
import { PodFileBrowser } from './PodFileBrowser';
@@ -34,11 +35,15 @@ import { CICDWebhookContent } from './deployment-workload/CICDWebhookContent';
import { ModalShell } from './deployment-workload/ModalShell';
import { PodLogsModal } from './deployment-workload/PodLogsModal';
import { ActionModals } from './deployment-workload/ActionModals';
import { createEmptyManifest } from './resource-create-templates';
import { CREATE_DEPLOYMENT_SLUG, isCreateDeployment } from './deployment-constants';
import { extractContainers } from './deployment-detail/helpers';
import type { ActionModal, DeploymentWorkloadProps } from './deployment-workload/types';
export type { DeploymentWorkloadProps, WorkloadMode } from './deployment-workload/types';
export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mode, onBack, onModeChange }: DeploymentWorkloadProps) {
const isCreate = isCreateDeployment(deploymentName);
const navigate = useNavigate();
const { toast } = useToast();
const confirm = useConfirm();
@@ -58,9 +63,30 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
const [fileBrowserPod, setFileBrowserPod] = useState<string | null>(null);
const clusterId = deployment?.cluster_id || deployment?.clusterId || clusterCode;
// CI/CD curl 示例:镜像与主容器名与当前 Deployment 对齐 // Logan.Li
const cicdContainerName = useMemo(() => {
const fromManifest = manifest?.spec?.template?.spec?.containers?.[0]?.name;
if (fromManifest) return fromManifest as string;
if (pods.length > 0) {
const names = extractContainers(pods[0]);
if (names.length) return names[0];
}
return '';
}, [manifest, pods]);
const toArray = ( v: any) => (Array.isArray(v) ? v : (v?.list || v?.items || v?.data || []));
const loadData = useCallback(async () => {
if (isCreate) {
const empty = createEmptyManifest('deployments', namespace, 'my-app');
setDeployment({ name: 'my-app', namespace, replicas: 1 });
setManifest(empty);
setPods([]);
setReplicaSets([]);
setRevisions([]);
setLoading(false);
return;
}
setLoading(true);
try {
const [dep, podRes, rsRes, revRes] = await Promise.all([
@@ -94,10 +120,11 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
} finally {
setLoading(false);
}
}, [clusterCode, namespace, deploymentName]);
}, [clusterCode, namespace, deploymentName, isCreate]);
// 加载 manifest(用于 edit 模式 + YAML 编辑)
const loadManifest = useCallback(async () => {
if (isCreate) return;
try {
const yamlText = await kubernetesApi.getResourceYaml(clusterCode, namespace, 'deployments', deploymentName);
const { parseEditableManifest } = await import('./k8s-manifest-utils');
@@ -109,8 +136,21 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
}
}, [clusterCode, namespace, deploymentName, deployment]);
const loadBundleYaml = useCallback(
() => loadDeploymentBundleYaml(clusterCode, namespace, deploymentName),
[clusterCode, namespace, deploymentName],
);
const saveBundleYaml = useCallback(
(yaml: string) => saveDeploymentBundleYaml(clusterCode, namespace, yaml),
[clusterCode, namespace],
);
useEffect(() => { loadData(); }, [loadData]);
useEffect(() => { if (mode === 'edit' && !manifest) loadManifest(); }, [mode, manifest, loadManifest]);
useEffect(() => {
if (isCreate || mode !== 'edit' || manifest) return;
loadManifest();
}, [mode, manifest, loadManifest, isCreate]);
// ============ 操作 ============
const _handleScale = async (delta: number) => {
@@ -178,8 +218,9 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
} finally { setActionLoading(''); }
};
const handleCICD = () => {
// 打开 Webhook 弹窗(不跳转流水线)
const handleCICD = async () => {
// 打开 Webhook 弹窗前拉取 manifest,以获取准确容器名 // Logan.Li
if (!isCreate && !manifest) await loadManifest();
setModal({ kind: 'cicd' });
};
@@ -187,7 +228,9 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
if (!newImage.trim()) return;
setActionLoading('image');
try {
await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage);
await kubernetesApi.updateDeploymentImage(
clusterCode, namespace, deploymentName, newImage, cicdContainerName || undefined,
);
toast({ title: '镜像更新成功', description: `正在滚动更新为 ${newImage}` });
setModal(null);
loadData();
@@ -223,6 +266,22 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
setFileBrowserPod(pod.name);
};
const handleDeletePod = async (pod: any) => {
const ok = await confirm({
title: '删除 Pod',
description: `确认删除 Pod "${pod.name}"?操作不可恢复。`,
confirmText: '删除',
});
if (!ok) return;
try {
await kubernetesApi.deletePod(clusterCode, namespace, pod.name);
toast({ title: 'Pod 已删除' });
loadData();
} catch (e) {
toast({ title: '删除失败', description: (e as any)?.message || String(e), variant: 'destructive' });
}
};
// ============ 渲染 ============
if (loading) {
return (
@@ -235,7 +294,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
);
}
if (!deployment) {
if (!deployment && !isCreate) {
return (
<Card className="bg-slate-900 border-slate-800">
<CardContent className="p-12 text-center">
@@ -247,6 +306,37 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
);
}
if (isCreate) {
const handleCreated = (updated?: any) => {
const name = updated?.metadata?.name;
if (name) {
navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/Deployment/${name}`);
}
};
return (
<div className="space-y-4">
<div className="flex items-center gap-3">
<Button variant="ghost" size="sm" className="text-slate-400" onClick={onBack}>
<ArrowLeft className="h-4 w-4 mr-1" />返回
</Button>
<div>
<h1 className="text-xl font-bold text-slate-100">新建 Deployment</h1>
<p className="text-xs text-slate-500 mt-0.5">{namespace} · {clusterCode}</p>
</div>
</div>
<EditModeContainer
clusterCode={clusterCode}
namespace={namespace}
deploymentName={CREATE_DEPLOYMENT_SLUG}
manifest={manifest}
isCreate
onSaved={handleCreated}
onCancel={onBack}
/>
</div>
);
}
const ready = deployment.ready_replicas || deployment.readyReplicas || 0;
const total = deployment.replicas || 0;
const isHealthy = ready === total && total > 0;
@@ -314,7 +404,15 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
{/* 操作按钮行 */}
<div className="flex items-center gap-1.5">
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={() => setModal({ kind: 'image' })}>
<Button
variant="outline"
size="sm"
className="border-slate-700 text-slate-300"
onClick={() => {
setNewImage(deployment?.image || '');
setModal({ kind: 'image' });
}}
>
<ImageIcon className="h-3.5 w-3.5 mr-1" />调整镜像
</Button>
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={handleRestart}>
@@ -355,6 +453,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
onShowPodTerminal={handleShowPodTerminal}
onShowPodYaml={handleShowPodYaml}
onShowPodFileBrowser={handleShowPodFileBrowser}
onDeletePod={handleDeletePod}
/>
) : mode === 'yaml' ? (
/* YAML 编辑模式 — 内联 CodeMirror 编辑器 */
@@ -364,6 +463,9 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
resource="deployments"
name={deploymentName}
inline
resourceHint="含关联 Service"
loadYamlFn={loadBundleYaml}
saveYamlFn={saveBundleYaml}
onSaved={loadData}
onClose={() => onModeChange('view')}
/>
@@ -386,6 +488,9 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
namespace={namespace}
resource="deployments"
name={deploymentName}
resourceHint="含关联 Service"
loadYamlFn={loadBundleYaml}
saveYamlFn={saveBundleYaml}
onClose={() => setModal(null)}
onSaved={loadData}
/>
@@ -409,8 +514,14 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
{/* ============ CI/CD Webhook 弹窗 ============ */}
{modal?.kind === 'cicd' && (
<ModalShell title="CI/CD Webhook" onClose={() => setModal(null)}>
<CICDWebhookContent clusterCode={clusterCode} namespace={namespace} deploymentName={deploymentName} />
<ModalShell title="CI/CD Webhook" onClose={() => setModal(null)} size="lg">
<CICDWebhookContent
clusterCode={clusterCode}
namespace={namespace}
deploymentName={deploymentName}
image={deployment?.image || newImage || ''}
containerName={cicdContainerName}
/>
</ModalShell>
)}