Files
Denozordec 5efd0339fb
Docker images / prepare-release (push) Successful in 9s
Docker images / backend-test (push) Successful in 4m35s
Docker images / frontend-image (push) Successful in 3m29s
Docker images / updater-image (push) Successful in 41s
Docker images / netflow-agent-image (push) Successful in 34s
Docker images / backend-image (push) Successful in 1m44s
Docker images / publish-release (push) Successful in 10s
Docker images / notify-webhook (push) Successful in 2s
feat(logs): добавить раздел логов с историей и живым потоком
2026-10-16 22:13:48 +07:00

37 lines
2.9 KiB
TypeScript

import { z } from "zod"
import { logEntrySchema, logPageSchema, logSettingsSchema, logSettingsPatchSchema, logSummarySchema } from "@mmapp/contracts"
import { requestBlob, requestJson } from "./http-client"
const timedSignal = (signal?: AbortSignal, ms = 30000) => AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(ms)])
function confirmed<T>(schema: z.ZodType<T>, raw: unknown): T {
const parsed = schema.safeParse(raw)
if (!parsed.success) throw new Error("Ответ сервера журнала не подтверждён: некорректные данные")
return parsed.data
}
export async function getLogs(base: string, params: URLSearchParams, signal?: AbortSignal) {
const result = confirmed(logPageSchema, await requestJson(base, "/api/logs?" + params, { signal: timedSignal(signal) }))
const server = params.get("serverId")
if (server && result.entries.some(row => String(row.serverId) !== server)) throw new Error("Журнал относится к другому устройству")
return result
}
export async function getLogSummary(base: string, params: URLSearchParams, signal?: AbortSignal) {
return confirmed(logSummarySchema, await requestJson(base, "/api/logs/summary?" + params, { signal: timedSignal(signal) }))
}
export async function getLogContext(base: string, id: string, signal?: AbortSignal) {
return confirmed(z.object({ entry: logEntrySchema, before: z.array(logEntrySchema), after: z.array(logEntrySchema) }), await requestJson(base, `/api/logs/${encodeURIComponent(id)}/context`, { signal: timedSignal(signal) }))
}
export async function getLogSettings(base: string, signal?: AbortSignal) { return confirmed(logSettingsSchema, await requestJson(base, "/api/logs/settings", { signal: timedSignal(signal) })) }
export async function putLogSettings(base: string, patch: z.infer<typeof logSettingsPatchSchema>, signal?: AbortSignal) {
const input = logSettingsPatchSchema.parse(patch)
const result = confirmed(z.object({ ok: z.literal(true), settings: logSettingsSchema }), await requestJson(base, "/api/logs/settings", { method: "PUT", body: JSON.stringify(input), signal: timedSignal(signal) }))
if (Object.entries(input).some(([key, value]) => result.settings[key as keyof typeof result.settings] !== value)) throw new Error("Сервер подтвердил другие настройки; обновите данные")
return result.settings
}
export async function exportLogs(base: string, params: URLSearchParams, format: "csv" | "json", signal?: AbortSignal) {
const query = new URLSearchParams(params); query.set("format", format)
const res = await requestBlob(base, "/api/logs/export?" + query, { signal: timedSignal(signal, 60000) })
const blob = await res.blob(), url = URL.createObjectURL(blob), anchor = document.createElement("a")
anchor.href = url; anchor.download = `router-logs.${format}`; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 1000)
}