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