/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useParams, useNavigate, useLocation } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { RefreshCw, Search, Loader2, Download, Upload, Plus } from 'lucide-react'; import { YamlViewer } from './YamlViewer'; import { ResourceVisualEditor } from './ResourceVisualEditor'; import { YamlImportDialog } from './YamlImportDialog'; import { PodExecTerminal } from './PodExecTerminal'; import { PodFileBrowser } from './PodFileBrowser'; import { PodDetailDialog } from './PodDetailDialog'; import { RESOURCE_CONFIG, VISUAL_RESOURCE_TYPES, DEPLOYMENT_LIST_TYPES, EXPORT_YAML_TYPES, BULK_EXPORT_TYPES, type ResourceType, } from './namespace-resource-config'; import { kubernetesApi } from '@/api'; import { useToast } from '@/hooks/use-toast'; import { useConfirm } from '@/components/ui/confirm-dialog'; import { PageShell } from '@/components/layout/PageShell'; import { ContentPanel } from '@/components/layout/ContentPanel'; import i18n from '@/i18n'; export default function NamespaceResourcePage() { const { clusterCode, namespace } = useParams<{ clusterCode: string; namespace: string }>(); const location = useLocation(); const navigate = useNavigate(); const { toast } = useToast(); const confirm = useConfirm(); const type = useMemo(() => { const segments = location.pathname.split('/'); const last = segments[segments.length - 1]; if (last && RESOURCE_CONFIG[last as ResourceType]) return last as ResourceType; if (segments.length >= 2) { const secondLast = segments[segments.length - 2]; if (secondLast && RESOURCE_CONFIG[secondLast as ResourceType]) return secondLast as ResourceType; } return 'deployments'; }, [location.pathname]); const config = RESOURCE_CONFIG[type]; const [clusters, setClusters] = useState([]); const [namespaces, setNamespaces] = useState([]); const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [search, setSearch] = useState(''); const [yamlItem, setYamlItem] = useState<{resource: string; name: string; editMode?: boolean} | null>(null); const [visualItem, setVisualItem] = useState<{ resource: string; name: string; isCreate?: boolean } | null>(null); const [showYamlImport, setShowYamlImport] = useState(false); const [terminalPod, setTerminalPod] = useState(null); const [fileBrowserPod, setFileBrowserPod] = useState(null); const [podDetail, setPodDetail] = useState(null); useEffect(() => { kubernetesApi.getClusters().then((res: any) => { const list = res?.list || []; setClusters(list); if (!clusterCode && list.length > 0) { const firstCode = list[0].code || String(list[0].id); navigate(`/kubernetes/${firstCode}/namespace/${namespace || 'default'}/${type}`, { replace: true }); } }).catch(() => setClusters([])); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!clusterCode) return; kubernetesApi.getNamespaces(clusterCode).then((res: any) => { const nsList: string[] = Array.isArray(res) ? res : (res?.list || res?.data || []); setNamespaces(nsList); if (!namespace && nsList.length > 0) { navigate(`/kubernetes/${clusterCode}/namespace/${nsList[0]}/${type}`, { replace: true }); } }).catch(() => setNamespaces([])); // eslint-disable-next-line react-hooks/exhaustive-deps }, [clusterCode]); const loadData = useCallback(async () => { if (!clusterCode || !namespace) return; setLoading(true); try { let res: Record = []; switch (type) { case 'deployments': res = await kubernetesApi.getNamespaceDeployments(clusterCode, namespace).catch(() => []); break; case 'pods': res = await kubernetesApi.getNamespacePods(clusterCode, namespace).catch(() => []); break; case 'services': res = await kubernetesApi.getNamespaceServices(clusterCode, namespace).catch(() => []); break; case 'ingresses': res = await kubernetesApi.getNamespaceIngresses(clusterCode, namespace).catch(() => []); break; case 'configmaps': res = await kubernetesApi.getNamespaceConfigMaps(clusterCode, namespace).catch(() => []); break; case 'secrets': res = await kubernetesApi.getNamespaceSecrets(clusterCode, namespace).catch(() => []); break; case 'hpas': res = await kubernetesApi.getHPAs(clusterCode, namespace).catch(() => []); break; case 'cronjobs': res = await kubernetesApi.getCronJobs(clusterCode, namespace).catch(() => []); break; case 'pvcs': res = await kubernetesApi.getPVCs(clusterCode, namespace).catch(() => []); break; case 'statefulsets': res = await kubernetesApi.getStatefulSets(clusterCode, namespace).catch(() => []); break; case 'daemonsets': res = await kubernetesApi.getDaemonSets(clusterCode, namespace).catch(() => []); break; case 'serviceaccounts': res = await kubernetesApi.getServiceAccounts(clusterCode, namespace).catch(() => []); break; case 'roles': res = await kubernetesApi.getRoles(clusterCode, namespace).catch(() => []); break; case 'rolebindings': res = await kubernetesApi.getRoleBindings(clusterCode, namespace).catch(() => []); break; } setData(Array.isArray(res) ? res : (res?.list || res?.data || [])); } catch { setData([]); } finally { setLoading(false); } }, [clusterCode, namespace, type]); useEffect(() => { loadData(); }, [loadData]); const filtered = data.filter((d: any) => (d.name || '').toLowerCase().includes(search.toLowerCase())); const handleDetail = (item: any) => { if (type === 'deployments') { navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/Deployment/${item.name}`); } else if (type === 'statefulsets') { navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/StatefulSet/${item.name}`); } else if (type === 'daemonsets') { navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/DaemonSet/${item.name}`); } }; const handleYaml = (name: string) => setYamlItem({ resource: type, name }); // 网络/配置/存储走可视化编辑器,YAML 作为高级入口 // Logan.Li const handleEdit = (name: string) => { if (VISUAL_RESOURCE_TYPES.includes(type)) { setVisualItem({ resource: type, name }); } else { setYamlItem({ resource: type, name, editMode: true }); } }; const handleCreate = () => { if (type === 'deployments') { navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/edit/Deployment/_create`); return; } setVisualItem({ resource: type, name: '', isCreate: true }); }; const handleExportCSV = () => kubernetesApi.exportAsCSV(`${type}-${clusterCode}-${namespace}.csv`, filtered); const handleExportJSON = () => kubernetesApi.exportAsJSON(`${type}-${clusterCode}-${namespace}.json`, filtered); const handleExportYaml = async () => { if (!clusterCode || !namespace) return; try { await kubernetesApi.exportNamespaceResources(clusterCode, namespace); toast({ title: i18n.t('common.exportComplete'), description: i18n.t('k8s.toast.yamlDownloadStarted', { namespace }) }); } catch (e) { toast({ title: i18n.t('kubernetes.msg.exportFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' }); } }; // Pod 删除 // Logan.Li const handleDeletePod = async (p: any) => { const ok = await confirm({ title: i18n.t('kubernetes.pod.deletePod'), description: i18n.t('k8s.namespaceResource.delete1') + (p.name || '') + i18n.t('k8s.namespaceResource.actions'), confirmText: i18n.t('kubernetes.storage.delete2'), }); if (!ok) return; try { await kubernetesApi.deletePod(clusterCode || '', namespace || '', p.name); toast({ title: i18n.t('kubernetes.msg.podDeleted') }); loadData(); } catch (e) { toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: (e as any)?.message || String(e), variant: 'destructive' }); } }; if (!config) return
未知资源类型: {type}
; const searchToolbar = (
setSearch(e.target.value)} className="pl-9 h-9 dg-input" />
); const ResourceIcon = config.icon; const subtitleParts = [clusterCode, namespace].filter(Boolean); const pageSubtitle = subtitleParts.length > 0 ? `${subtitleParts.join(' / ')} · ${config.subtitle}` : config.subtitle; return ( } actions={( <> {BULK_EXPORT_TYPES.includes(type) && ( <> )} {EXPORT_YAML_TYPES.includes(type) && ( )} {(VISUAL_RESOURCE_TYPES.includes(type) || DEPLOYMENT_LIST_TYPES.includes(type)) && ( <> )} )} > {yamlItem && ( setYamlItem(null)} onSaved={loadData} autoEdit={yamlItem.editMode === true} /> )} {showYamlImport && ( setShowYamlImport(false)} onApplied={loadData} /> )} {visualItem && ( setVisualItem(null)} onSaved={loadData} /> )} {config.columns.map((col) => {col})} {loading ? ( {i18n.t('monitor.logPanel.logResults.loading')} ) : filtered.length === 0 ? ( 暂无 {config.title} ) : ( filtered.map((item) => config.renderRow(item, { clusterCode: clusterCode || '', namespace: namespace || '', onDetail: handleDetail, onYaml: handleYaml, onEdit: handleEdit, onTerminal: (p: any) => setTerminalPod(p), onFileBrowser: (p: any) => setFileBrowserPod(p), onDeletePod: handleDeletePod, onPodDetail: (p: any) => setPodDetail(p) })) )}
{/* ============ Pod 终端 ============ */} { if (!open) setTerminalPod(null); }} clusterId={clusters[0]?.id || clusters[0]?.code || clusterCode || ''} namespace={namespace || ''} podName={terminalPod?.name || ''} containers={(terminalPod?.containers || []).map((c: any) => c.name).filter(Boolean)} /> {/* ============ Pod 文件浏览器 ============ */} { if (!open) setFileBrowserPod(null); }} clusterCode={clusterCode || ''} namespace={namespace || ''} podName={fileBrowserPod?.name || ''} containers={(fileBrowserPod?.containers || []).map((c: any) => c.name).filter(Boolean)} /> {/* ============ Pod 详情弹窗 ============ */} setPodDetail(null)} />
); }