feat: K8s→Kubernetes命名统一 + 工具安装bug修复 + 资源可视化编辑器

1. 工具安装bug修复 (P0 critical):
   - 根因: ToolConfigHandler.InstallTool的goroutine只打了日志,
     从未调用实际安装逻辑
   - 修复: 注入ToolInstaller到handler, 新增ForceInstall方法绕过sync.Once
   - 影响: 流水线工具配置页「安装」按钮现在能真正触发下载安装

2. K8s→Kubernetes命名统一:
   - Dashboard/Prometheus/AIOpsAssistant等8个文件12处用户可见文案
   - 内部变量名/文件名/API group保持不变

3. 资源可视化编辑器 (新增ResourceVisualEditor.tsx):
   - ConfigMap: 键值对表格编辑器(增删改)
   - Secret: 键值对编辑器(自动Base64编解码)
   - Service: 端口/类型/选择器可视化配置
   - Ingress: Host/Path路由规则可视化编辑
   - PVC: 存储大小/访问模式/StorageClass配置
   - 双模式: 可视化编辑 ↔ YAML编辑一键切换
   - NamespaceResourcePage: 所有资源类型添加编辑按钮入口
This commit is contained in:
attacker committed 2026-06-25 01:32:10 +08:00
1 parent e29dfb5868
commit aaab923f4e
14 files changed
+748 -20

No files matched your search

@@ -16,6 +16,7 @@ import {
Download, FileCode, Pencil, Terminal, HardDrive,
} from 'lucide-react';
import { YamlViewer } from './YamlViewer';
import { ResourceVisualEditor } from './ResourceVisualEditor';
import { PodExecTerminal } from './PodExecTerminal';
import { PodFileBrowser } from './PodFileBrowser';
import { kubernetesApi } from '@/services/api';
@@ -104,7 +105,12 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge className="bg-blue-500/20 text-blue-400 border-blue-500/30">{s.type || '-'}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{s.cluster_ip || '-'}</TableCell>
<TableCell className="text-sm text-slate-400">{s.ports || '-'}</TableCell>
<TableCell><Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(s.name)}><FileCode className="h-3.5 w-3.5" /></Button></TableCell>
<TableCell>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(s.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-emerald-400 hover:text-emerald-300" title="编辑" onClick={() => opts.onEdit?.(s.name)}><Pencil className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
</TableRow>
),
},
@@ -119,7 +125,12 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge variant="outline" className="border-slate-700 text-slate-400">{i.namespace}</Badge></TableCell>
<TableCell className="text-sm text-slate-400 max-w-[250px] truncate">{hosts.length > 0 ? hosts.join(', ') : '-'}</TableCell>
<TableCell><Badge className={i.tls ? 'bg-emerald-500/20 text-emerald-400' : 'bg-slate-500/20 text-slate-400'}>{i.tls ? 'HTTPS' : 'HTTP'}</Badge></TableCell>
<TableCell><Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(i.name)}><FileCode className="h-3.5 w-3.5" /></Button></TableCell>
<TableCell>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(i.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-emerald-400 hover:text-emerald-300" title="编辑" onClick={() => opts.onEdit?.(i.name)}><Pencil className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
</TableRow>
);
},
@@ -191,7 +202,7 @@ const RESOURCE_CONFIG: Record<ResourceType, {
},
pvcs: {
title: '存储卷声明', subtitle: 'PersistentVolumeClaim', icon: Boxes,
columns: ['名称', '命名空间', '状态', '大小', 'StorageClass'],
columns: ['名称', '命名空间', '状态', '大小', 'StorageClass', '操作'],
renderRow: (p, opts) => (
<TableRow key={p.name} className="border-slate-800 hover:bg-slate-800/30">
<TableCell className="font-medium text-slate-200">{p.name}</TableCell>
@@ -199,6 +210,12 @@ const RESOURCE_CONFIG: Record<ResourceType, {
<TableCell><Badge className={p.phase === 'Bound' ? 'bg-emerald-500/20 text-emerald-400' : 'bg-yellow-500/20 text-yellow-400'}>{p.phase || '-'}</Badge></TableCell>
<TableCell className="text-sm text-slate-400">{p.size || '-'}</TableCell>
<TableCell className="text-sm text-slate-400">{p.storage_class || '-'}</TableCell>
<TableCell>
<div className="flex items-center gap-0.5">
<Button variant="ghost" size="icon" className="h-7 w-7 text-slate-400 hover:text-blue-400" title="查看YAML" onClick={() => opts.onYaml?.(p.name)}><FileCode className="h-3.5 w-3.5" /></Button>
<Button variant="ghost" size="icon" className="h-7 w-7 text-emerald-400 hover:text-emerald-300" title="编辑" onClick={() => opts.onEdit?.(p.name)}><Pencil className="h-3.5 w-3.5" /></Button>
</div>
</TableCell>
</TableRow>
),
},
@@ -336,6 +353,7 @@ export default function NamespaceResourcePage() {
const [loading, setLoading] = useState(false);
const [search, setSearch] = useState('');
const [yamlItem, setYamlItem] = useState<{resource: string; name: string; editMode?: boolean} | null>(null);
const [visualEditItem, setVisualEditItem] = useState<{resource: string; name: string} | null>(null);
const [terminalPod, setTerminalPod] = useState<any>(null);
const [fileBrowserPod, setFileBrowserPod] = useState<any>(null);
@@ -402,7 +420,14 @@ export default function NamespaceResourcePage() {
};
const handleYaml = (name: string) => setYamlItem({ resource: type, name });
const handleEdit = (name: string) => setYamlItem({ resource: type, name, editMode: true });
const handleEdit = (name: string) => {
// 使用可视化编辑器的资源类型
if (['configmaps', 'secrets', 'services', 'ingresses', 'pvcs'].includes(type)) {
setVisualEditItem({ resource: type, name });
} else {
setYamlItem({ resource: type, name, editMode: true });
}
};
const handleExportCSV = () => kubernetesApi.exportAsCSV(`${type}-${clusterCode}-${namespace}.csv`, filtered);
const handleExportJSON = () => kubernetesApi.exportAsJSON(`${type}-${clusterCode}-${namespace}.json`, filtered);
@@ -439,6 +464,16 @@ export default function NamespaceResourcePage() {
autoEdit={yamlItem.editMode === true}
/>
)}
{visualEditItem && (
<ResourceVisualEditor
clusterCode={clusterCode || ''}
namespace={namespace || ''}
resource={visualEditItem.resource}
name={visualEditItem.name}
onClose={() => setVisualEditItem(null)}
onSaved={loadData}
/>
)}
<div className="flex items-center gap-2 text-sm text-slate-500">
<Link to="/kubernetes" className="hover:text-slate-300 transition-colors">集群</Link>
{clusterCode && <><ChevronRight className="h-3 w-3" /><Link to={`/kubernetes/${clusterCode}`} className="hover:text-slate-300 transition-colors">{clusterCode}</Link></>}