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:
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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user