import { useState, useEffect, useCallback, useRef } from 'react'; import api from './lib/api.js'; import PageHeader from './components/PageHeader.jsx'; import { IconClockPlay, IconSettings, IconTerminal2, IconPlayerPlay, IconRefresh, } from '@tabler/icons-react'; const POLL_LOGS_MS = 2500; const INTERVAL_MIN = 1; const INTERVAL_MAX = 1440; const LOG_LINES_MIN = 100; const LOG_LINES_MAX = 2000; export default function NetworkMapSchedulerPage() { const [settings, setSettings] = useState({ enabled: false, intervalMinutes: 5, runPing: true, runSpeedTest: true, logMaxLines: 500, }); const [nextRunAt, setNextRunAt] = useState(null); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [running, setRunning] = useState(false); const [error, setError] = useState(null); const logEndRef = useRef(null); const logContainerRef = useRef(null); const [autoScroll, setAutoScroll] = useState(true); const fetchSettings = useCallback(async (abortSignal) => { try { const opts = abortSignal ? { signal: abortSignal } : {}; const { data } = await api.get('/scheduler/network-map/settings', opts); setSettings({ enabled: Boolean(data.enabled), intervalMinutes: Math.max(INTERVAL_MIN, Math.min(INTERVAL_MAX, Number(data.intervalMinutes) || 5)), runPing: data.runPing !== false, runSpeedTest: data.runSpeedTest !== false, logMaxLines: Math.max(LOG_LINES_MIN, Math.min(LOG_LINES_MAX, Number(data.logMaxLines) || 500)), }); setNextRunAt(typeof data.nextRunAt === 'number' ? data.nextRunAt : null); } catch (e) { if (e?.name === 'CanceledError' || e?.name === 'AbortError' || e?.code === 'ERR_CANCELED') return; setError(e?.response?.data?.message || e?.message || 'Не удалось загрузить настройки'); } }, []); const fetchLogs = useCallback(async () => { try { const { data } = await api.get('/scheduler/network-map/logs'); setLogs(Array.isArray(data?.lines) ? data.lines : []); } catch (_) { // не показываем ошибку при опросе логов } }, []); useEffect(() => { const controller = new AbortController(); (async () => { setLoading(true); await fetchSettings(controller.signal); setLoading(false); })(); return () => controller.abort(); }, [fetchSettings]); useEffect(() => { fetchLogs(); const t = setInterval(fetchLogs, POLL_LOGS_MS); return () => clearInterval(t); }, [fetchLogs]); useEffect(() => { if (!settings.enabled || !nextRunAt) return; const t = setInterval(fetchSettings, 60 * 1000); return () => clearInterval(t); }, [settings.enabled, nextRunAt, fetchSettings]); useEffect(() => { if (autoScroll && logEndRef.current) { logEndRef.current.scrollIntoView({ behavior: 'smooth' }); } }, [logs, autoScroll]); const handleSave = async () => { setSaving(true); setError(null); try { await api.patch('/scheduler/network-map/settings', { networkMapSchedulerEnabled: settings.enabled, networkMapSchedulerIntervalMinutes: settings.intervalMinutes, networkMapSchedulerRunPing: settings.runPing, networkMapSchedulerRunSpeedTest: settings.runSpeedTest, networkMapSchedulerLogMaxLines: settings.logMaxLines, }); await fetchSettings(); } catch (e) { setError(e?.response?.data?.message || e?.message || 'Не удалось сохранить'); } finally { setSaving(false); } }; const handleRunNow = async () => { setRunning(true); setError(null); try { await api.post('/scheduler/network-map/run-now'); setTimeout(fetchLogs, 500); } catch (e) { setError(e?.response?.data?.message || e?.message || 'Не удалось запустить'); } finally { setRunning(false); } }; if (loading) { return (
{logs.length === 0 ? (
Логов пока нет. Включите планировщик и нажмите «Запустить сейчас» или дождитесь следующего запуска.
) : (
logs.map((line, i) => (
{line}
))
)}