fix: K8s集群编辑保存失败+凭据覆盖+去掉Kubectl死按钮

问题:
1. UpdateCluster 用 CreateK8sClusterRequest (code binding:"required"),
   编辑时不传 code → 校验失败 400 → 保存不成功
2. Token/KubeConfig json:"-" 不序列化, 编辑时拿不到原值,
   提交空字符串覆盖真实凭据
3. 操作菜单 Kubectl 是死按钮

修复:
- UpdateCluster 改用 map[string]interface{} 接收, 逐字段更新,
  空值不覆盖 (防止凭据清空)
- 新增 GET /:id/detail 返回 has_token/has_kube_config 脱敏标记
- 编辑弹框拉取详情 API 回填, Token/KubeConfig 留空显示 '已配置' 提示
- 去掉 Kubectl 死按钮, 保留 详情/配置/删除 三项
This commit is contained in:
Logan authored and attacker committed 2026-06-19 15:08:50 +08:00
1 parent 33e58ce25a
commit abf2917fad
5 files changed
+119 -33

No files matched your search

@@ -37,21 +37,42 @@ export function K8sClusterDialog({
}) {
const [formData, setFormData] = useState({ ...emptyForm });
const [submitting, setSubmitting] = useState(false);
const [hasToken, setHasToken] = useState(false); // 已配置 Token 标记 // Logan.Li
const [hasKubeConfig, setHasKubeConfig] = useState(false); // 已配置 Kubeconfig 标记
// 打开时同步表单(编辑时填充,新建时重置)
// 打开时同步表单(编辑时拉取详情,新建时重置)
useEffect(() => {
if (!open) return;
if (editingCluster) {
setFormData({
name: editingCluster.name || '',
version: editingCluster.version || '',
description: editingCluster.description || '',
config_type: Number(editingCluster.config_type ?? 1),
api_server: editingCluster.api_server || '',
token: editingCluster.token || '',
kube_config: editingCluster.kube_config || '',
if (editingCluster && editingCluster.id) {
// 编辑模式:拉取详情获取 has_token/has_kube_config 标记 // Logan.Li
k8sApi.getClusterDetail(editingCluster.id).then((res: any) => {
const d = res || {};
setHasToken(!!d.has_token);
setHasKubeConfig(!!d.has_kube_config);
setFormData({
name: d.name || '',
version: d.version || '',
description: d.description || '',
config_type: Number(d.config_type ?? 1),
api_server: d.api_server || '',
token: '', // 不回填原文,留空不修改
kube_config: '', // 不回填原文,留空不修改
});
}).catch(() => {
// 降级:用列表数据
setFormData({
name: editingCluster.name || '',
version: editingCluster.version || '',
description: editingCluster.description || '',
config_type: Number(editingCluster.config_type ?? 1),
api_server: editingCluster.api_server || '',
token: '',
kube_config: '',
});
});
} else {
setHasToken(false);
setHasKubeConfig(false);
setFormData({ ...emptyForm });
}
}, [open, editingCluster]);
@@ -171,28 +192,34 @@ export function K8sClusterDialog({
{formData.config_type === 0 ? (
<div className="space-y-2">
<Label className="text-[var(--dg-text-secondary)]">Token</Label>
<Label className="text-[var(--dg-text-secondary)]">
Token {hasToken && <span className="text-emerald-400 text-xs ml-1">✓ 已配置(留空不修改)</span>}
</Label>
<Textarea
value={formData.token}
onChange={(e) => setFormData({ ...formData, token: e.target.value })}
placeholder="粘贴 Service Account Token..."
placeholder={hasToken ? '已配置,输入新值覆盖...' : '粘贴 Service Account Token...'}
rows={4}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-xs"
/>
</div>
) : (
<div className="space-y-2">
<Label className="text-[var(--dg-text-secondary)]">Kubeconfig</Label>
<Label className="text-[var(--dg-text-secondary)]">
Kubeconfig {hasKubeConfig && <span className="text-emerald-400 text-xs ml-1">✓ 已配置(留空不修改)</span>}
</Label>
<Textarea
value={formData.kube_config}
onChange={(e) => handleKubeConfigChange(e.target.value)}
placeholder={'apiVersion: v1\nkind: Config\nclusters:\n- cluster:\n server: https://10.0.0.1:6443\n ...'}
placeholder={hasKubeConfig ? '已配置,输入新值覆盖...' : 'apiVersion: v1\nkind: Config\nclusters:\n- cluster:\n server: https://10.0.0.1:6443\n ...'}
rows={10}
className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] font-mono text-xs"
/>
<p className="text-xs text-[var(--dg-text-muted)]">
粘贴 kubeconfig 内容后将自动解析 API Server 地址
</p>
{!hasKubeConfig && (
<p className="text-xs text-[var(--dg-text-muted)]">
粘贴 kubeconfig 内容后将自动解析 API Server 地址
</p>
)}
</div>
)}
</div>
@@ -25,7 +25,6 @@ import {
Settings,
Eye,
Trash2,
Terminal,
Search,
} from 'lucide-react';
import { k8sApi } from '@/services/api';
@@ -263,10 +262,6 @@ export function K8sClusters() {
<Eye className="h-4 w-4 mr-2" />
查看详情
</DropdownMenuItem>
<DropdownMenuItem className="text-slate-300 focus:bg-slate-700 focus:text-slate-100">
<Terminal className="h-4 w-4 mr-2" />
Kubectl
</DropdownMenuItem>
<DropdownMenuItem
className="text-slate-300 focus:bg-slate-700 focus:text-slate-100"
onClick={(e) => {