import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Card, Col, ConfigProvider, Layout, Modal, Result, Row, Spin, Statistic, message, } from 'antd'; import { CheckCircleOutlined, GlobalOutlined, StopOutlined } from '@ant-design/icons'; import { useTheme } from '@/hooks/useTheme'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useHostsQuery, type HostRecord } from '@/api/queries/useHostsQuery'; import { useHostMutations } from '@/api/queries/useHostMutations'; import { useInboundOptions } from '@/api/queries/useInboundOptions'; import AppSidebar from '@/layouts/AppSidebar'; import { setMessageInstance } from '@/utils/messageBus'; import type { BulkAddHostValues } from '@/schemas/api/host'; import HostList, { sortHosts } from './HostList'; import HostFormModal from './HostFormModal'; export default function HostsPage() { const { t } = useTranslation(); const { isDark, isUltra, antdThemeConfig } = useTheme(); const { isMobile } = useMediaQuery(); const [modal, modalContextHolder] = Modal.useModal(); const [messageApi, messageContextHolder] = message.useMessage(); useEffect(() => { setMessageInstance(messageApi); }, [messageApi]); const { hosts, loading, fetched, fetchError, refetch } = useHostsQuery(); const { bulkCreate, update, remove, setEnable, reorder, bulkSetEnable, bulkDel } = useHostMutations(); const { data: inboundOptions = [] } = useInboundOptions(); const [formOpen, setFormOpen] = useState(false); const [formMode, setFormMode] = useState<'add' | 'edit'>('add'); const [formHost, setFormHost] = useState(null); const [selectedGroupIds, setSelectedGroupIds] = useState([]); const onAdd = useCallback(() => { setFormMode('add'); setFormHost(null); setFormOpen(true); }, []); const onEdit = useCallback((host: HostRecord) => { setFormMode('edit'); setFormHost({ ...host }); setFormOpen(true); }, []); const onSave = useCallback( async (payload: BulkAddHostValues) => { if (formMode === 'edit' && formHost?.groupId) { return update(formHost.groupId, payload); } return bulkCreate(payload); }, [formMode, formHost, update, bulkCreate], ); const onDelete = useCallback( (host: HostRecord) => { modal.confirm({ title: t('pages.hosts.deleteConfirmTitle', { name: host.remark }), okText: t('delete'), okType: 'danger', cancelText: t('cancel'), onOk: async () => { const msg = await remove(host.groupId); if (msg?.success) messageApi.success(t('pages.hosts.toasts.delete')); }, }); }, [modal, t, remove, messageApi], ); const onToggleEnable = useCallback( async (host: HostRecord, next: boolean) => { await setEnable(host.groupId, next); }, [setEnable], ); const onMove = useCallback( async (host: HostRecord, dir: 'up' | 'down') => { const sorted = sortHosts(hosts); const idx = sorted.findIndex((h) => h.groupId === host.groupId); const swapWith = dir === 'up' ? idx - 1 : idx + 1; if (idx < 0 || swapWith < 0 || swapWith >= sorted.length) return; const groupIds = sorted.map((h) => h.groupId); [groupIds[idx], groupIds[swapWith]] = [groupIds[swapWith], groupIds[idx]]; await reorder(groupIds); }, [hosts, reorder], ); const onBulkEnable = useCallback( async (enable: boolean) => { if (selectedGroupIds.length === 0) return; const msg = await bulkSetEnable(selectedGroupIds, enable); if (msg?.success) setSelectedGroupIds([]); }, [selectedGroupIds, bulkSetEnable], ); const onBulkDelete = useCallback(() => { if (selectedGroupIds.length === 0) return; modal.confirm({ title: t('pages.hosts.bulkDeleteConfirm', { count: selectedGroupIds.length }), okText: t('delete'), okType: 'danger', cancelText: t('cancel'), onOk: async () => { const msg = await bulkDel(selectedGroupIds); if (msg?.success) { messageApi.success(t('pages.hosts.toasts.delete')); setSelectedGroupIds([]); } }, }); }, [selectedGroupIds, modal, t, bulkDel, messageApi]); const summary = useMemo(() => { const total = hosts.length; const enabled = hosts.filter((h) => !h.isDisabled).length; return { total, enabled, disabled: total - enabled }; }, [hosts]); const pageClass = useMemo(() => { const classes = ['hosts-page']; if (isDark) classes.push('is-dark'); if (isUltra) classes.push('is-ultra'); return classes.join(' '); }, [isDark, isUltra]); return ( {messageContextHolder} {modalContextHolder} {!fetched ? (
) : fetchError ? ( refetch()}> {t('refresh')} } /> ) : ( } /> } /> } /> )} ); }