import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Checkbox, Form, Input, Modal, Select, Tag } from 'antd'; import { DownloadOutlined, SyncOutlined } from '@ant-design/icons'; import { HttpUtil, FileManager, IntlUtil, PromiseUtil } from '@/utils'; import { useDatepicker } from '@/hooks/useDatepicker'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import './XrayLogModal.css'; interface XrayLogModalProps { open: boolean; onClose: () => void; } interface XrayLogEntry { DateTime?: string | number; FromAddress?: string; ToAddress?: string; Inbound?: string; Outbound?: string; Email?: string; Event?: number; } const EVENT_LABELS: Record = { 0: 'DIRECT', 1: 'BLOCKED', 2: 'PROXY' }; const EVENT_COLORS: Record = { 0: 'green', 1: 'red', 2: 'blue' }; function eventLabel(ev?: number): string { return EVENT_LABELS[ev ?? -1] ?? String(ev ?? ''); } function eventColor(ev?: number): string { return EVENT_COLORS[ev ?? -1] ?? 'default'; } function shortTime(value?: string | number): string { if (!value) return ''; const d = new Date(value); if (isNaN(d.getTime())) return ''; const hh = String(d.getHours()).padStart(2, '0'); const mm = String(d.getMinutes()).padStart(2, '0'); const ss = String(d.getSeconds()).padStart(2, '0'); return `${hh}:${mm}:${ss}`; } export default function XrayLogModal({ open, onClose }: XrayLogModalProps) { const { t } = useTranslation(); const { datepicker } = useDatepicker(); const { isMobile } = useMediaQuery(); const [rows, setRows] = useState('20'); const [filter, setFilter] = useState(''); const [showDirect, setShowDirect] = useState(true); const [showBlocked, setShowBlocked] = useState(true); const [showProxy, setShowProxy] = useState(true); const [loading, setLoading] = useState(false); const [logs, setLogs] = useState([]); const openRef = useRef(open); const orderedLogs = useMemo(() => [...logs].reverse(), [logs]); const refresh = useCallback(async () => { setLoading(true); try { const msg = await HttpUtil.post(`/panel/api/server/xraylogs/${rows}`, { filter, showDirect, showBlocked, showProxy, }); if (msg?.success) setLogs(msg.obj || []); await PromiseUtil.sleep(300); } finally { setLoading(false); } }, [rows, filter, showDirect, showBlocked, showProxy]); useEffect(() => { openRef.current = open; if (open) refresh(); }, [open, refresh]); useEffect(() => { if (openRef.current) refresh(); }, [rows, showDirect, showBlocked, showProxy, refresh]); function fullDate(value?: string | number): string { return IntlUtil.formatDate(value, datepicker); } function download() { if (!Array.isArray(logs) || logs.length === 0) { FileManager.downloadTextFile('', 'x-ui.log'); return; } const lines = logs.map((l) => { try { const dt = l.DateTime ? new Date(l.DateTime) : null; const dateStr = dt && !isNaN(dt.getTime()) ? dt.toISOString() : ''; const eventText = eventLabel(l.Event); const emailPart = l.Email ? ` Email=${l.Email}` : ''; return `${dateStr} FROM=${l.FromAddress || ''} TO=${l.ToAddress || ''} INBOUND=${l.Inbound || ''} OUTBOUND=${l.Outbound || ''}${emailPart} EVENT=${eventText}`.trim(); } catch { return JSON.stringify(l); } }).join('\n'); FileManager.downloadTextFile(lines, 'x-ui.log'); } return ( {t('pages.index.logs')} } >
setFilter(e.target.value)} onKeyUp={(e) => { if (e.key === 'Enter') refresh(); }} /> setShowDirect(e.target.checked)}> Direct setShowBlocked(e.target.checked)}> Blocked setShowProxy(e.target.checked)}> Proxy