"use client" // Adapted from ReUI PRO sheet-14: event header, facts, tabs and stepping context. // https://reui.io/preview/base/sheet-14 import { useEffect, useState } from "react" import Link from "next/link" import { z } from "zod" import type { LogEntry } from "@mmapp/contracts" import { toast } from "sonner" import { CopyIcon, ExternalLinkIcon } from "lucide-react" import { Sheet, SheetContent, SheetTitle, SheetDescription, SheetHeader } from "@/components/ui/sheet" import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" import { Button } from "@/components/ui/button" import { Skeleton } from "@/components/ui/skeleton" import { Alert, AlertTitle, AlertDescription } from "@/components/reui/alert" import { getLogContext } from "@/shared/api/logs" import { LevelBadge, formatLogTime } from "./log-presentation" export function LogDetailSheet({ entry, base, onClose, onSelect }: { entry: LogEntry | null; base: string; onClose: () => void; onSelect: (entry: LogEntry) => void }) { return { if (!open) onClose() }}> {entry && } } function LogDetail({ entry, base, onSelect }: { entry: LogEntry; base: string; onSelect: (entry: LogEntry) => void }) { const [context, setContext] = useState> | null>(null), [error, setError] = useState(null), [retry, setRetry] = useState(0) useEffect(() => { const ac = new AbortController() void getLogContext(base, entry.id, ac.signal).then(data => { if (!ac.signal.aborted) { setContext(data); setError(null) } }).catch(e => { if (!ac.signal.aborted) setError(e instanceof z.ZodError ? "Некорректный ответ контекста" : e.message) }) return () => ac.abort() }, [base, entry.id, retry]) const copy = () => { void navigator.clipboard.writeText(JSON.stringify(entry, null, 2)).then(() => toast.success("Событие скопировано"), () => toast.error("Не удалось скопировать событие")) } return <> {entry.routerId} Событие RouterOS{entry.serverName} · {entry.host} · {formatLogTime(entry.effectiveAt)} Копировать}>Устройство СобытиеИсходные поляКонтекст {entry.message} {[["Topics", entry.topics.join(", ")], ["Время RouterOS", entry.rawTime], ["Получено", formatLogTime(entry.receivedAt)], ["Источник времени", entry.timeKind === "receipt" ? "Время получения: дата или часовой пояс RouterOS неоднозначны" : "Время RouterOS"], ["Поколение буфера", String(entry.generation)]].map(([label, value]) => )} {JSON.stringify({ ".id": entry.routerId, time: entry.rawTime, topics: entry.topics.join(","), message: entry.message }, null, 2)} До 10 событий до и после записи на этом MikroTik. Фильтры журнала здесь не применяются. {error ? Контекст не получен{error} setRetry(n => n + 1)}>Повторить : !context ? : [context.before, [context.entry], context.after].flat().map(row => onSelect(row)} aria-current={row.id === entry.id ? "true" : undefined}>{formatLogTime(row.effectiveAt)} · {row.topics.join(", ")}{row.message})} > } function FragmentPair({ label, value }: { label: string; value: string }) { return <>{label}{value}> }
{entry.message}
{JSON.stringify({ ".id": entry.routerId, time: entry.rawTime, topics: entry.topics.join(","), message: entry.message }, null, 2)}
До 10 событий до и после записи на этом MikroTik. Фильтры журнала здесь не применяются.