import { lazy, useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Card, Col, ConfigProvider, Layout, message, Modal, Row, Space, Spin, Statistic, Tag, Tooltip, } from 'antd'; import { BarsOutlined, ControlOutlined, CloudServerOutlined, CloudDownloadOutlined, CloudUploadOutlined, ArrowUpOutlined, ArrowDownOutlined, AreaChartOutlined, GlobalOutlined, SwapOutlined, EyeOutlined, EyeInvisibleOutlined, ThunderboltOutlined, DesktopOutlined, DatabaseOutlined, ForkOutlined, CopyOutlined, } from '@ant-design/icons'; import { HttpUtil, SizeFormatter, TimeFormatter, ClipboardManager, FileManager } from '@/utils'; import { useTheme } from '@/hooks/useTheme'; import { useStatusQuery } from '@/api/queries/useStatusQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import AppSidebar from '@/components/AppSidebar'; import LazyMount from '@/components/LazyMount'; import { setMessageInstance } from '@/utils/messageBus'; import StatusCard from './StatusCard'; import XrayStatusCard from './XrayStatusCard'; import type { PanelUpdateInfo } from './PanelUpdateModal'; const JsonEditor = lazy(() => import('@/components/JsonEditor')); const PanelUpdateModal = lazy(() => import('./PanelUpdateModal')); const LogModal = lazy(() => import('./LogModal')); const BackupModal = lazy(() => import('./BackupModal')); const SystemHistoryModal = lazy(() => import('./SystemHistoryModal')); const XrayMetricsModal = lazy(() => import('./XrayMetricsModal')); const XrayLogModal = lazy(() => import('./XrayLogModal')); const VersionModal = lazy(() => import('./VersionModal')); import './IndexPage.css'; export default function IndexPage() { const { t } = useTranslation(); const { isDark, isUltra, antdThemeConfig } = useTheme(); const { status, fetched, refresh } = useStatusQuery(); const { isMobile } = useMediaQuery(); const [messageApi, messageContextHolder] = message.useMessage(); useEffect(() => { setMessageInstance(messageApi); }, [messageApi]); const [ipLimitEnable, setIpLimitEnable] = useState(false); const [panelUpdateInfo, setPanelUpdateInfo] = useState({ currentVersion: '', latestVersion: '', updateAvailable: false, }); const basePath = window.X_UI_BASE_PATH || ''; const [showIp, setShowIp] = useState(false); const [logsOpen, setLogsOpen] = useState(false); const [backupOpen, setBackupOpen] = useState(false); const [panelUpdateOpen, setPanelUpdateOpen] = useState(false); const [sysHistoryOpen, setSysHistoryOpen] = useState(false); const [xrayMetricsOpen, setXrayMetricsOpen] = useState(false); const [xrayLogsOpen, setXrayLogsOpen] = useState(false); const [versionOpen, setVersionOpen] = useState(false); const [configTextOpen, setConfigTextOpen] = useState(false); const [configText, setConfigText] = useState(''); const [loading, setLoading] = useState(false); const [loadingTip, setLoadingTip] = useState(t('loading')); useEffect(() => { HttpUtil.post('/panel/setting/defaultSettings').then((msg) => { if (msg?.success && msg.obj) setIpLimitEnable(!!msg.obj.ipLimitEnable); }); HttpUtil.get('/panel/api/server/getPanelUpdateInfo').then((msg) => { if (msg?.success && msg.obj) setPanelUpdateInfo(msg.obj); }); }, []); const displayVersion = useMemo( () => panelUpdateInfo.currentVersion || window.X_UI_CUR_VER || '?', [panelUpdateInfo.currentVersion], ); const setBusy = useCallback( ({ busy, tip }: { busy: boolean; tip?: string }) => { setLoading(busy); if (tip) setLoadingTip(tip); }, [], ); const stopXray = useCallback(async () => { await HttpUtil.post('/panel/api/server/stopXrayService'); await refresh(); }, [refresh]); const restartXray = useCallback(async () => { await HttpUtil.post('/panel/api/server/restartXrayService'); await refresh(); }, [refresh]); function openPanelVersion() { if (panelUpdateInfo.updateAvailable) { setPanelUpdateOpen(true); } else { window.open('https://github.com/MHSanaei/3x-ui/releases', '_blank', 'noopener,noreferrer'); } } function openTelegram() { window.open('https://t.me/XrayUI', '_blank', 'noopener,noreferrer'); } async function openConfig() { setLoading(true); try { const msg = await HttpUtil.get('/panel/api/server/getConfigJson'); if (!msg?.success) return; setConfigText(JSON.stringify(msg.obj, null, 2)); setConfigTextOpen(true); } finally { setLoading(false); } } async function copyConfig() { const ok = await ClipboardManager.copyText(configText || ''); if (ok) messageApi.success('Copied'); } function downloadConfig() { FileManager.downloadTextFile(configText, 'config.json'); } const pageClass = `index-page ${isDark ? 'is-dark' : ''} ${isUltra ? 'is-ultra' : ''}`.trim(); return ( {messageContextHolder} {!fetched ? (
) : ( setXrayLogsOpen(true)} onOpenLogs={() => setLogsOpen(true)} onOpenVersionSwitch={() => setVersionOpen(true)} /> setLogsOpen(true)}> {!isMobile && {t('pages.index.logs')}} , {!isMobile && {t('pages.index.config')}} , setBackupOpen(true)}> {!isMobile && {t('pages.index.backupTitle')}} , ]} /> 3X-UI {isMobile && displayVersion && ( {panelUpdateInfo.updateAvailable ? `v${panelUpdateInfo.latestVersion}` : `v${displayVersion}`} )} } hoverable actions={[ {!isMobile && @XrayUI} , {!isMobile && ( {panelUpdateInfo.updateAvailable ? `${t('update')} ${panelUpdateInfo.latestVersion}` : `v${displayVersion}`} )} , ]} /> setSysHistoryOpen(true)} > {!isMobile && {t('pages.index.systemHistoryTitle')}} , setXrayMetricsOpen(true)} > {!isMobile && {t('pages.index.xrayMetricsTitle')}} , ]} /> } /> } /> } /> } /> } suffix="/s" /> } suffix="/s" /> } /> } /> {showIp ? ( setShowIp(false)} /> ) : ( setShowIp(true)} /> )} } > } /> } /> } /> } /> )} setPanelUpdateOpen(false)} onBusy={setBusy} /> setLogsOpen(false)} /> setBackupOpen(false)} onBusy={setBusy} /> setSysHistoryOpen(false)} /> setXrayMetricsOpen(false)} /> setXrayLogsOpen(false)} /> setVersionOpen(false)} onBusy={setBusy} /> setConfigTextOpen(false)} footer={[ , , ]} > ); }