import { memo, useCallback, useEffect, useRef, useState } from 'react' import { Monitor, Plus } from 'lucide-react' import { useTranslation } from 'react-i18next' import { Button } from '@/components/ui/button' import { hostApi, deploymentApi, collectorApi } from '@/api' import { PageContainer } from '@/components/PageContainer' import { LoadingSpinner } from '@/components/LoadingSpinner' import { EmptyState } from '@/components/EmptyState' import { ErrorState } from '@/components/ErrorState' import { PaginationBar } from '@/components/PaginationBar' import { ConfirmDialog } from '@/components/ConfirmDialog' import { toast } from 'sonner' import { useListQuery } from '@/hooks/useListQuery' import type { Host } from '@/types' import i18n from '@/i18n' import zhHosts from '@/i18n/locales/zh/hosts' import enHosts from '@/i18n/locales/en/hosts' import { HostCard } from './components/HostCard' import { HostFormDialog } from './components/HostFormDialog' import { HostDetailDialog } from './components/HostDetailDialog' // 注册本域命名空间(i18n/index.ts 只负责 common,域词典就近注册;页面为懒加载,渲染前必然已注册) i18n.addResourceBundle('zh', 'hosts', zhHosts) i18n.addResourceBundle('en', 'hosts', enHosts) /** * 主机管理页 — 只做编排:数据走 useListQuery(取消+版本号), * 卡片/表单/详情/删除确认均为域内子组件。成功操作静默(列表刷新即反馈)。 */ const HostsPage = memo(function HostsPage() { const { t } = useTranslation('hosts') const query = useListQuery((params, signal) => hostApi.list(params, { signal }) ) const [formOpen, setFormOpen] = useState(false) const [detailHost, setDetailHost] = useState(null) const [deleteTarget, setDeleteTarget] = useState(null) const [editingTarget, setEditingTarget] = useState(null) const [deleting, setDeleting] = useState(false) const [validating, setValidating] = useState(null) // 验证后延时刷新列表的定时器(卸载清理) const timeoutsRef = useRef[]>([]) useEffect(() => { const timers = timeoutsRef.current return () => { timers.forEach((id) => clearTimeout(id)) } }, []) const [refCount, setRefCount] = useState<{ dep: number; col: number }>({ dep: 0, col: 0 }) useEffect(() => { if (!deleteTarget) return Promise.all([ deploymentApi.list({ page: 1, pageSize: 100 }), collectorApi.list({ page: 1, pageSize: 100 }), ]).then(([d, c]) => { setRefCount({ dep: d.list.filter((x) => x.hostID === deleteTarget.id).length, col: c.list.filter((x) => x.hostID === deleteTarget.id).length, }) }).catch(() => setRefCount({ dep: 0, col: 0 })) }, [deleteTarget]) const handleDelete = useCallback(async () => { const id = deleteTarget?.id if (!id) return setDeleting(true) try { await hostApi.remove(id) setDeleteTarget(null) query.refresh() } catch (err) { toast.error(err instanceof Error ? err.message : t('toast.deleteFailed')) } finally { setDeleting(false) } }, [deleteTarget, query, t]) const handleValidate = useCallback( async (id: number) => { setValidating(id) try { await hostApi.validate(id) timeoutsRef.current.push(setTimeout(() => query.refresh(), 3000)) } catch (err) { toast.error(err instanceof Error ? err.message : t('toast.validateFailed')) } finally { timeoutsRef.current.push(setTimeout(() => setValidating(null), 3000)) } }, [query, t] ) return ( setFormOpen(true)}> {t('actions.add')} } > {query.loading ? ( ) : query.error ? ( ) : query.list.length === 0 ? ( ) : ( <>
{query.list.map((host) => ( ))}
)} { if (!o) { setFormOpen(false) setEditingTarget(null) } }} onCreated={() => (editingTarget ? query.refresh() : query.reload())} editing={editingTarget} /> setDetailHost(null)} /> { if (!open) setDeleteTarget(null) }} name={deleteTarget?.name} loading={deleting} description={ refCount.dep + refCount.col > 0 ? t('delete.warning', { name: deleteTarget?.name ?? '', dep: refCount.dep, col: refCount.col, }) : undefined } onConfirm={handleDelete} />
) }) export default HostsPage