feat: Deployment详情页重构 — ACK风格Tab布局 + CI/CD Webhook + 信息面板

DeploymentView 完全重写:
- Tab布局: 容器组|访问方式(Service/Ingress)|事件|容器伸缩|历史版本
- 顶部信息面板: 状态/副本/节点/IP/创建时间 + Pod条件时间线
- 容器组Tab: 卡片式列表(日志/终端/文件按钮), 展开显示容器详情
- 访问方式Tab: 自动加载匹配的Service+Ingress(selector过滤)
- 事件Tab: 加载Deployment相关K8s Events
- 容器伸缩Tab: 内联伸缩UI(+/-按钮)
- 历史版本Tab: 回滚功能

DeploymentWorkload 精简:
- 删除下拉菜单(元数据/运行时/服务/应用路由/YAML预览/日志终端)
- 操作按钮改为直接按钮行: 编辑|调整镜像|重启|暂停/恢复|CI/CD|删除
- CI/CD改为Webhook模态(生成URL+curl示例, 自动填充Token)
- AIOpsAssistant UUID fallback支持HTTP环境
- Dockerfile chmod a+r 修复favicon.svg 403
This commit is contained in:
attacker committed 2026-06-24 03:55:23 +00:00
1 parent 722587ab84
commit d581441fa8
5 files changed
+597 -391

No files matched your search

@@ -52,6 +52,7 @@ type ActionModal =
| { kind: 'podLogs'; pod: string }
| { kind: 'podYaml'; pod: string }
| { kind: 'delete' }
| { kind: 'cicd' }
| null;
export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mode, onBack, onModeChange }: DeploymentWorkloadProps) {
@@ -194,8 +195,8 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
};
const handleCICD = () => {
// 跳转到流水线页面,传递 namespace + deployment 上下文
navigate(`/pipelines?namespace=${encodeURIComponent(namespace)}&app=${encodeURIComponent(deploymentName)}`);
// 打开 Webhook 弹窗(不跳转流水线)
setModal({ kind: 'cicd' as any });
};
const handleUpdateImage = async () => {
@@ -327,68 +328,35 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
<RefreshCw className="h-4 w-4" />
</Button>
{/* 顶部操作菜单(Kuboard 风格下拉) */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="border-slate-700 text-slate-200">
操作 <ChevronDown className="h-3.5 w-3.5 ml-1" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-52 bg-slate-900 border-slate-700">
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={() => onModeChange('edit')}>
<Pencil className="h-4 w-4 mr-2 text-blue-400" /> 编辑
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={() => setModal({ kind: 'yaml' })}>
<FileCode className="h-4 w-4 mr-2 text-emerald-400" /> 预览 / 编辑 YAML
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={() => setModal({ kind: 'image' })}>
<ImageIcon className="h-4 w-4 mr-2 text-purple-400" /> 调整镜像版本
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={() => { setScaleValue(deployment.replicas || 0); setModal({ kind: 'scale' }); }}>
<Minus className="h-4 w-4 mr-2 text-amber-400" /> 伸缩
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={() => toast({ title: '日志 / 终端', description: '请在 Pod 列表中点击对应按钮' })}>
<Terminal className="h-4 w-4 mr-2 text-cyan-400" /> 日志 / 终端
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-200 hover:bg-slate-800 cursor-pointer" onClick={handleRestart}>
<RotateCw className="h-4 w-4 mr-2 text-blue-400" /> 重启
</DropdownMenuItem>
<DropdownMenuItem
className="text-slate-200 hover:bg-slate-800 cursor-pointer"
disabled={!!actionLoading}
onClick={handleTogglePause}
>
{deployment?.paused
? <><Play className="h-4 w-4 mr-2 text-green-400" /> 恢复更新</>
: <><Pause className="h-4 w-4 mr-2 text-orange-400" /> 暂停更新</>
}
</DropdownMenuItem>
<DropdownMenuItem
className="text-slate-200 hover:bg-slate-800 cursor-pointer"
onClick={handleCICD}
>
<Rocket className="h-4 w-4 mr-2 text-purple-400" /> CI/CD 发布
</DropdownMenuItem>
<DropdownMenuItem className="text-red-400 hover:bg-slate-800 cursor-pointer" onClick={() => setModal({ kind: 'delete' })}>
<Trash2 className="h-4 w-4 mr-2" /> 删除
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-slate-800" />
<DropdownMenuItem className="text-slate-300 hover:bg-slate-800 cursor-pointer" onClick={() => toast({ title: '元数据', description: 'Labels / Annotations / Selectors,集成在 YAML 视图中' })}>
<Info className="h-4 w-4 mr-2 text-slate-400" /> 元数据
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-300 hover:bg-slate-800 cursor-pointer" onClick={() => toast({ title: '运行时', description: 'Pod 运行状态请见 Pod 实例区' })}>
<Cpu className="h-4 w-4 mr-2 text-slate-400" /> 运行时
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-300 hover:bg-slate-800 cursor-pointer"
onClick={() => navigate(`${viewBase}/view/Service?selector=${encodeURIComponent(JSON.stringify(deployment?.spec?.selector?.matchLabels || deployment?.spec?.template?.metadata?.labels || {}))}`)}>
<Network className="h-4 w-4 mr-2 text-slate-400" /> 服务
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-300 hover:bg-slate-800 cursor-pointer"
onClick={() => navigate(`${viewBase}/view/Ingress?namespace=${namespace}`)}>
<GitBranch className="h-4 w-4 mr-2 text-slate-400" /> 应用路由
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* 操作按钮行 */}
<div className="flex items-center gap-1.5">
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={() => onModeChange('edit')}>
<Pencil className="h-3.5 w-3.5 mr-1" />编辑
</Button>
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={() => 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}>
<RotateCw className="h-3.5 w-3.5 mr-1" />重启
</Button>
<Button
variant="outline" size="sm"
className="border-slate-700 text-slate-300"
disabled={!!actionLoading}
onClick={handleTogglePause}
>
{deployment?.paused
? <><Play className="h-3.5 w-3.5 mr-1 text-green-400" />恢复</>
: <><Pause className="h-3.5 w-3.5 mr-1 text-orange-400" />暂停</>
}
</Button>
<Button variant="outline" size="sm" className="border-slate-700 text-slate-300" onClick={handleCICD}>
<Rocket className="h-3.5 w-3.5 mr-1 text-purple-400" />CI/CD
</Button>
<Button variant="outline" size="sm" className="border-slate-700 text-red-400 hover:bg-red-500/10" onClick={() => setModal({ kind: 'delete' })}>
<Trash2 className="h-3.5 w-3.5 mr-1" />删除
</Button>
</div>
</div>
</div>
@@ -521,6 +489,13 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod
</ModalShell>
)}
{/* ============ CI/CD Webhook 弹窗 ============ */}
{modal?.kind === 'cicd' && (
<ModalShell title="CI/CD Webhook" onClose={() => setModal(null)}>
<CICDWebhookContent clusterCode={clusterCode} namespace={namespace} deploymentName={deploymentName} />
</ModalShell>
)}
{/* ============ Pod 日志 ============ */}
{modal?.kind === 'podLogs' && (
<PodLogsModal
@@ -661,6 +636,45 @@ function EditModeContainer({ clusterCode, namespace, deploymentName, manifest, o
);
}
// ============ CI/CD Webhook 内容 ============
function CICDWebhookContent({ clusterCode, namespace, deploymentName }: { clusterCode: string; namespace: string; deploymentName: string }) {
const { toast } = useToast();
const token = useUserStore((s: any) => s.token) || localStorage.getItem('token') || '';
const webhookUrl = `${window.location.origin}/api/v1/clusters/${clusterCode}/namespaces/${namespace}/deployments/${deploymentName}/update-image`;
const curlCmd = `curl -X POST '${webhookUrl}' \\
-H 'Authorization: Bearer ${token ? token.substring(0, 16) + '...' : 'YOUR_TOKEN'}' \\
-H 'Content-Type: application/json' \\
-d '{"image": "harbor.example.com/app:v1.0", "container": "your-container-name"}'`;
const copy = (text: string) => {
navigator.clipboard.writeText(text).then(() => toast({ title: '已复制' })).catch(() => {});
};
return (
<div className="space-y-4">
<div>
<Label className="text-slate-400 text-sm">Webhook URL</Label>
<div className="flex items-center gap-2 mt-1">
<code className="flex-1 text-xs text-slate-300 bg-slate-800 rounded px-3 py-2 break-all font-mono">{webhookUrl}</code>
<Button variant="outline" size="sm" className="border-slate-700 text-slate-400 shrink-0" onClick={() => copy(webhookUrl)}>复制</Button>
</div>
</div>
<div>
<Label className="text-slate-400 text-sm">curl 示例</Label>
<div className="relative mt-1">
<pre className="text-xs text-slate-300 bg-slate-800 rounded p-3 overflow-x-auto font-mono whitespace-pre-wrap">{curlCmd}</pre>
<Button variant="outline" size="sm" className="absolute top-2 right-2 border-slate-700 text-slate-400" onClick={() => copy(curlCmd)}>复制</Button>
</div>
</div>
<div className="text-xs text-slate-500 space-y-1">
<p>• 使用 Bearer Token 认证(JWT 登录令牌)</p>
<p>• <code className="text-slate-400">image</code> 必填,指定新镜像地址</p>
<p>• <code className="text-slate-400">container</code> 可选,不填则更新第一个容器</p>
</div>
</div>
);
}
// ============ 通用 Modal 外壳 ============
function ModalShell({ title, children, onClose }: { title: string; children: React.ReactNode; onClose: () => void }) {
return (