import { useState, useEffect } from 'react' import { Box, Typography, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, CircularProgress, Alert, IconButton, TextField, MenuItem, Tooltip, Snackbar, } from '@mui/material' import DeleteIcon from '@mui/icons-material/Delete' import EditIcon from '@mui/icons-material/Edit' import AddIcon from '@mui/icons-material/Add' import { useNamespaces } from '../../hooks/useNamespaces' import { getAge } from '../../utils/k8sHelpers' import CreateYamlDialog from '../../components/k8s/CreateYamlDialog' import DeleteConfirmDialog from '../../components/k8s/DeleteConfirmDialog' import EditResourceDialog from '../../components/k8s/EditResourceDialog' export default function StatefulSetPage() { const [resources, setResources] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [namespace, setNamespace] = useState('') const [nameFilter, setNameFilter] = useState('') const [createDialogOpen, setCreateDialogOpen] = useState(false) const [yamlContent, setYamlContent] = useState('') const [applyLoading, setApplyLoading] = useState(false) const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [deleteTarget, setDeleteTarget] = useState<{ name: string; namespace: string } | null>(null) const [deleting, setDeleting] = useState(false) const [editDialogOpen, setEditDialogOpen] = useState(false) const [editResource, setEditResource] = useState<{ name: string; namespace: string; yaml: string } | null>(null) const [editYaml, setEditYaml] = useState('') const [editing, setEditing] = useState(false) const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({ open: false, message: '', severity: 'success', }) const { namespaces } = useNamespaces() useEffect(() => { fetchResources() }, [namespace, nameFilter]) const fetchResources = async () => { setLoading(true) setError(null) try { const params = new URLSearchParams() if (namespace) params.append('namespace', namespace) if (nameFilter) params.append('name', nameFilter) const res = await fetch(`/api/v1/k8s/statefulset/list?${params}`) const result = await res.json() if (!res.ok) { throw new Error(result.err || 'Failed to fetch resources') } setResources(result.data?.items || []) } catch (e: any) { setError(e.message) } finally { setLoading(false) } } const handleApplyYaml = async (yaml: string) => { if (!yaml.trim()) { setSnackbar({ open: true, message: 'YAML 内容不能为空', severity: 'error' }) return } setApplyLoading(true) try { const res = await fetch('/api/v1/k8s/resource/apply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ yaml }), }) const result = await res.json() if (!res.ok) { throw new Error(result.err || 'Failed to apply resource') } setSnackbar({ open: true, message: 'StatefulSet 应用成功', severity: 'success' }) setCreateDialogOpen(false) setYamlContent('') fetchResources() } catch (e: any) { setSnackbar({ open: true, message: `应用失败: ${e.message}`, severity: 'error' }) } finally { setApplyLoading(false) } } const handleDeleteResource = async () => { if (!deleteTarget) return setDeleting(true) try { const res = await fetch('/api/v1/k8s/statefulset/delete', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: deleteTarget.name, namespace: deleteTarget.namespace, }), }) const result = await res.json() if (!res.ok) { throw new Error(result.err || 'Failed to delete StatefulSet') } setSnackbar({ open: true, message: 'StatefulSet 删除成功', severity: 'success' }) setDeleteDialogOpen(false) setDeleteTarget(null) fetchResources() } catch (e: any) { setSnackbar({ open: true, message: `删除失败: ${e.message}`, severity: 'error' }) } finally { setDeleting(false) } } const handleEditResource = async (name: string, ns: string) => { try { const res = await fetch(`/api/v1/k8s/resource/get?kind=StatefulSet&name=${name}&namespace=${ns}`) const result = await res.json() if (!res.ok) { throw new Error(result.err || 'Failed to fetch resource') } setEditResource({ name, namespace: ns, yaml: result.data?.yaml || '' }) setEditYaml(result.data?.yaml || '') setEditDialogOpen(true) } catch (e: any) { setSnackbar({ open: true, message: `获取资源失败: ${e.message}`, severity: 'error' }) } } const handleSaveEdit = async (yaml: string) => { if (!editResource) return setEditing(true) try { const res = await fetch('/api/v1/k8s/resource/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ yaml }), }) const result = await res.json() if (!res.ok) { throw new Error(result.err || 'Failed to update resource') } setSnackbar({ open: true, message: '更新成功', severity: 'success' }) setEditDialogOpen(false) setEditResource(null) fetchResources() } catch (e: any) { setSnackbar({ open: true, message: `更新失败: ${e.message}`, severity: 'error' }) } finally { setEditing(false) } } return ( StatefulSet setCreateDialogOpen(true)}> setNamespace(e.target.value)} size="small" sx={{ width: 200 }} SelectProps={{ displayEmpty: true, }} InputLabelProps={{ shrink: true }} > 所有命名空间 {namespaces.map((ns) => ( {ns} ))} setNameFilter(e.target.value)} size="small" sx={{ width: 200 }} /> {error && {error}} {loading && ( )} {!loading && !error && ( Name Namespace Replicas Age Actions {resources.length === 0 && ( 暂无数据 )} {resources.map((resource) => { const metadata = resource.metadata || {} const spec = resource.spec || {} const status = resource.status || {} return ( {metadata.name || '-'} {metadata.namespace || '-'} {`${status.readyReplicas || 0}/${spec.replicas || 0}`} {getAge(metadata.creationTimestamp)} handleEditResource(metadata.name, metadata.namespace)} > { setDeleteTarget({ name: metadata.name, namespace: metadata.namespace }) setDeleteDialogOpen(true) }} > ) })}
)} setCreateDialogOpen(false)} onApply={handleApplyYaml} yamlContent={yamlContent} onYamlChange={setYamlContent} loading={applyLoading} /> setDeleteDialogOpen(false)} onConfirm={handleDeleteResource} resourceType="StatefulSet" resourceName={deleteTarget?.name || ''} namespace={deleteTarget?.namespace} deleting={deleting} /> setEditDialogOpen(false)} onSave={handleSaveEdit} resourceType="StatefulSet" resourceName={editResource?.name || ''} namespace={editResource?.namespace || ''} yaml={editYaml} onYamlChange={setEditYaml} saving={editing} /> setSnackbar({ ...snackbar, open: false })} anchorOrigin={{ vertical: 'top', horizontal: 'center' }} > setSnackbar({ ...snackbar, open: false })}> {snackbar.message}
) }