import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button, Checkbox, Empty, Form, Input, Modal, Select, Tag } from 'antd'; import { DownloadOutlined, SyncOutlined } from '@ant-design/icons'; import { HttpUtil, FileManager, IntlUtil, PromiseUtil, SizeFormatter } from '@/utils'; import { activateOnKey } from '@/utils/a11y'; import { useDatepicker } from '@/hooks/useDatepicker'; import { useMediaQuery } from '@/hooks/useMediaQuery'; import type { AmneziaWGLogs } from '@/generated/types'; import './XrayLogModal.css'; import './AmneziaWGLogModal.css'; interface AmneziaWGLogModalProps { open: boolean; onClose: () => void; } const AUTO_UPDATE_INTERVAL = 5000; function shortTime(value?: 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 AmneziaWGLogModal({ open, onClose }: AmneziaWGLogModalProps) { const { t } = useTranslation(); const { datepicker } = useDatepicker(); const { isMobile } = useMediaQuery(); const [rows, setRows] = useState('50'); const [filter, setFilter] = useState(''); const [autoUpdate, setAutoUpdate] = useState(false); const [loading, setLoading] = useState(false); const [logs, setLogs] = useState>({}); const peers = useMemo(() => logs.peers ?? [], [logs.peers]); const events = useMemo(() => logs.events ?? [], [logs.events]); const runRefresh = useCallback(async () => { try { const msg = await HttpUtil.post(`/panel/api/server/amneziawglogs/${rows}`, { filter, }); if (msg?.success) setLogs(msg.obj || {}); await PromiseUtil.sleep(300); } finally { setLoading(false); } }, [rows, filter]); const refresh = useCallback(() => { setLoading(true); void runRefresh(); }, [runRefresh]); const refreshRef = useRef(refresh); useEffect(() => { refreshRef.current = refresh; }); // The spinner is raised during render so the fetch effect stays side-effect // free until its response lands. const refreshKey = open ? `${rows}|${filter}` : null; const [loadingKey, setLoadingKey] = useState(null); if (refreshKey !== loadingKey) { setLoadingKey(refreshKey); if (refreshKey) setLoading(true); } useEffect(() => { if (open) void runRefresh(); }, [open, rows, filter, runRefresh]); useEffect(() => { if (!open || !autoUpdate) return; const id = setInterval(() => refreshRef.current(), AUTO_UPDATE_INTERVAL); return () => clearInterval(id); }, [open, autoUpdate]); function fullDate(value?: number): string { return value ? IntlUtil.formatDate(value, datepicker) : ''; } function download() { const peerLines = peers.map((p) => { const at = p.handshake ? new Date(p.handshake).toISOString() : 'never'; return `${at} IFACE=${p.interface || ''} INBOUND=${p.tag || ''} EMAIL=${p.email || ''} ENDPOINT=${p.endpoint || '-'} ALLOWEDIPS=${p.allowedIPs || ''} UP=${p.up ?? 0} DOWN=${p.down ?? 0} ONLINE=${p.online ? 'yes' : 'no'}`; }); FileManager.downloadTextFile([...peerLines, '', ...events].join('\n'), 'amneziawg.log'); } return ( {t('pages.index.amneziawgLogs')} } >
setFilter(e.target.value)} onKeyUp={(e) => { if (e.key === 'Enter') refresh(); }} /> setAutoUpdate(e.target.checked)}> {t('pages.index.autoUpdate')}