import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Card, Col, ConfigProvider, Layout, Modal, Row, Spin, message } from 'antd'; import { CheckCircleOutlined, CloseCircleOutlined, CloudServerOutlined, ThunderboltOutlined, } from '@ant-design/icons'; import { useTheme } from '@/hooks/useTheme'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useNodes } from '@/hooks/useNodes'; import type { NodeRecord } from '@/hooks/useNodes'; import { useWebSocket } from '@/hooks/useWebSocket'; import AppSidebar from '@/components/AppSidebar'; import CustomStatistic from '@/components/CustomStatistic'; import NodeList from './NodeList'; import NodeFormModal from './NodeFormModal'; import { setMessageInstance } from '@/utils/messageBus'; import '@/styles/page-cards.css'; import './NodesPage.css'; const basePath = window.X_UI_BASE_PATH || ''; const requestUri = window.location.pathname; export default function NodesPage() { 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 { nodes, loading, fetched, totals, applyNodesEvent, create, update, remove, setEnable, testConnection, probe, } = useNodes(); useWebSocket({ nodes: applyNodesEvent }); const [formOpen, setFormOpen] = useState(false); const [formMode, setFormMode] = useState<'add' | 'edit'>('add'); const [formNode, setFormNode] = useState(null); const onAdd = useCallback(() => { setFormMode('add'); setFormNode(null); setFormOpen(true); }, []); const onEdit = useCallback((node: NodeRecord) => { setFormMode('edit'); setFormNode({ ...node }); setFormOpen(true); }, []); const onSave = useCallback(async (payload: Partial) => { if (formMode === 'edit' && formNode?.id) { return update(formNode.id, payload); } return create(payload); }, [formMode, formNode, update, create]); const onDelete = useCallback((node: NodeRecord) => { modal.confirm({ title: t('pages.nodes.deleteConfirmTitle', { name: node.name }), content: t('pages.nodes.deleteConfirmContent'), okText: t('delete'), okType: 'danger', cancelText: t('cancel'), onOk: async () => { const msg = await remove(node.id); if (msg?.success) messageApi.success(t('pages.nodes.toasts.deleted')); }, }); }, [modal, t, remove, messageApi]); const onProbe = useCallback(async (node: NodeRecord) => { const msg = await probe(node.id); if (msg?.success && msg.obj) { if (msg.obj.status === 'online') { messageApi.success(t('pages.nodes.connectionOk', { ms: msg.obj.latencyMs })); } else { messageApi.error(msg.obj.error || t('pages.nodes.toasts.probeFailed')); } } }, [probe, t, messageApi]); const onToggleEnable = useCallback(async (node: NodeRecord, next: boolean) => { await setEnable(node.id, next); }, [setEnable]); const pageClass = useMemo(() => { const classes = ['nodes-page']; if (isDark) classes.push('is-dark'); if (isUltra) classes.push('is-ultra'); return classes.join(' '); }, [isDark, isUltra]); return ( {messageContextHolder} {modalContextHolder} {!fetched ? (
) : ( } /> } /> } /> 0 ? `${totals.avgLatency} ms` : '-'} prefix={} /> )} ); }