"use client" import { useEffect, useRef, useState } from "react" import Link from "next/link" import { type LogSettings, type LogSummary } from "@mmapp/contracts" import { Frame, FrameHeader, FrameTitle, FrameDescription, FramePanel } from "@/components/reui/frame" import { Alert, AlertTitle, AlertDescription } from "@/components/reui/alert" import { Badge } from "@/components/reui/badge" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { Skeleton } from "@/components/ui/skeleton" import { getLogSettings, getLogSummary, putLogSettings } from "@/shared/api/logs" import { can, ensureAuthConfig } from "@/lib/auth" import { formatLogTime } from "./log-presentation" export function LogsCollectorSettings({ base, onSaved }: { base: string; onSaved?: (settings: LogSettings) => void }) { const [settings, setSettings] = useState(null), [summary, setSummary] = useState(null) const [interval, setIntervalDraft] = useState("10"), [retention, setRetention] = useState("14"), [enabled, setEnabled] = useState(false) const [error, setError] = useState(null), [saving, setSaving] = useState(false), [revision, setRevision] = useState(0), [notice, setNotice] = useState("") const lifetime = useRef(null), busy = useRef(false) useEffect(() => { const ac = new AbortController(); lifetime.current = ac void ensureAuthConfig().then(async () => { if (!can("mm:settings:admin")) throw new Error("Настройки логов доступны с mm:settings:admin") const data = await getLogSettings(base, ac.signal) if (ac.signal.aborted) return setSettings(data); setEnabled(data.enabled); setIntervalDraft(String(data.intervalSec)); setRetention(String(data.retentionDays)); setError(null) const stats = await getLogSummary(base, new URLSearchParams({ from: new Date(Date.now() - 900000).toISOString() }), ac.signal) if (!ac.signal.aborted) setSummary(stats) }).catch(e => { if (!ac.signal.aborted) setError(e instanceof Error ? e.message : String(e)) }) return () => ac.abort() }, [base, revision]) const save = async () => { if (busy.current || !settings || !can("mm:settings:admin")) return if (!/^\d+$/.test(interval) || !/^\d+$/.test(retention)) { setError("Укажите целые значения интервала и срока хранения"); return } if (Number(interval) < 5 || Number(interval) > 3600 || Number(retention) < 1 || Number(retention) > 365) { setError("Интервал должен быть от 5 до 3600 секунд, срок хранения — от 1 до 365 дней"); return } busy.current = true; setSaving(true); setNotice("") const signal = lifetime.current?.signal try { const confirmed = await putLogSettings(base, { enabled, intervalSec: Number(interval), retentionDays: Number(retention) }, signal) if (signal?.aborted) return setSettings(confirmed); setError(null); setNotice("Настройки сборщика логов сохранены"); onSaved?.(confirmed) } catch (e) { if (!signal?.aborted) setError(e instanceof Error ? e.message : String(e)) } finally { busy.current = false; if (!signal?.aborted) setSaving(false) } } return Логи MikroTikREST-сбор буфера memory, сохранение в PostgreSQL. При открытом live опрос выбранного устройства ускоряется до 2 секунд. {error && Данные сборщика не подтверждены{error}} {!settings && !error && }
{notice &&

{notice}

}

Интервал: 5–3600 секунд, хранение: 1–365 дней. Буфер MikroTik может перезаписаться между опросами. Новые правила логирования на устройствах не создаются.

{summary?.states.length ?
{summary.states.map(state =>
{state.serverName}{state.lastError ? "Ошибка" : state.lastCollectedAt ? "Получено" : "Ожидание"}{formatLogTime(state.lastCollectedAt)}{state.gapAt && Возможный разрыв: {formatLogTime(state.gapAt)}}{state.lastError && {state.lastError}}
)}
:

Результатов сбора по устройствам пока нет.

}
}