Files
nxdns/admin/src/features/diagnostics/eventCopy.ts
T
mokhtar 648d9b4496
Gates / frontend (push) Successful in 1m32s
Gates / test (push) Successful in 1m54s
Gates / package (push) Successful in 5m28s
Gates / container (push) Successful in 14s
Gates / test-aarch64 (push) Failing after 3h10m0s
CI / gates (push) Failing after 3h11m55s
milestone 30: overview as a dashboard, explicit health contract, period aggregations
2026-08-22 16:45:15 +02:00

162 lines
7.1 KiB
TypeScript

/**
* What each event code means to the operator, in three fixed fields: what the
* episode is (`title`), what it costs while it stays open (`impact`), and what
* to do about it (`remediation`). The server sends a code and an error string;
* every word of explanation the page shows comes from here.
*
* The record is exhaustive over `DiagnosticCode` by type, and a test walks
* `DIAGNOSTIC_CODES` to prove it at runtime too. A sixteenth code added to the
* enum fails `tsc` here before it can reach the page as a bare dotted string.
*
* `link` points at the configuration surface that governs the failure. Those
* are today's routes; the navigation restructure re-points them.
*/
import { DIAGNOSTIC_CODES, type DiagnosticCode } from "@/lib/types";
/** The literal paths keep `link.to` assignable to a typed router `Link`. */
export type CopyLinkPath = "/settings" | "/blocklists" | "/upstreams" | "/clients";
export interface EventCopy {
title: string;
impact: string;
remediation: string;
link?: { to: CopyLinkPath; label: string };
}
/**
* The copy for a code, with a floor under it. `tsc` proves the record covers
* the union, but a server one release ahead can send a code this build has
* never heard of; showing the raw code beats rendering "undefined".
*/
export function copyFor(code: DiagnosticCode): EventCopy {
return (
EVENT_COPY[code] ?? {
title: code,
impact: "This build has no description for this event code.",
remediation: "The error detail below is the whole of what the server reported.",
}
);
}
const SETTINGS = { to: "/settings", label: "Settings" } as const;
const BLOCKLISTS = { to: "/blocklists", label: "Blocklists" } as const;
const UPSTREAMS = { to: "/upstreams", label: "Upstreams" } as const;
const CLIENTS = { to: "/clients", label: "Clients" } as const;
/**
* The component filter's options, derived from the codes rather than listed
* again: the server matches `component` against the part of `code` before the
* dot, so any list written by hand here could drift from the enum.
*/
export const DIAGNOSTIC_COMPONENTS: readonly string[] = [
...new Set(DIAGNOSTIC_CODES.map((code) => code.slice(0, code.indexOf(".")))),
];
/** `query_log` → "Query log". Display only; the filter sends the raw component. */
export function componentLabel(component: string): string {
const spaced = component.replaceAll("_", " ");
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
}
export const EVENT_COPY: Record<DiagnosticCode, EventCopy> = {
"disk.space": {
title: "Disk space low",
impact: "Below the critical threshold nxdns stops blocklist updates and query log flushes to protect the disk.",
remediation: "Free space on the data volume, or lower the retention window so the query log holds fewer days.",
link: SETTINGS,
},
"disk.probe": {
title: "Disk usage probe failed",
impact: "Free space is unknown, so the low-disk guard cannot act until a probe succeeds.",
remediation: "Check that the data and log directories exist and that the service user can read them.",
link: SETTINGS,
},
"blocklist.refresh": {
title: "Blocklist source failed to update",
impact: "The source keeps serving its last good snapshot, so blocking continues but the list ages.",
remediation: "Check the source url and the machine's internet access, then update the lists again.",
link: BLOCKLISTS,
},
"blocklist.snapshot": {
title: "Filter snapshot failed to publish",
impact: "The resolver keeps the snapshot it already holds; blocklist edits do not take effect until one publishes.",
remediation: "Check free disk space and the data directory's permissions, then update the lists again.",
link: BLOCKLISTS,
},
"blocklist.storage": {
title: "Blocklist storage operation failed",
impact: "Cached list files or their database rows are out of step; a later pass can redownload what is missing.",
remediation: "Check free disk space and the data directory's permissions.",
link: BLOCKLISTS,
},
"certificate.reload": {
title: "TLS certificate reload failed",
impact: "The endpoint keeps serving the certificate it already loaded, which expires on its own schedule.",
remediation:
"Check the certificate and key paths, and that renewal writes both files the service user can read.",
link: SETTINGS,
},
"query_log.write": {
title: "Query log write failed",
impact: "Queries are resolved and answered as usual, but they are not being recorded.",
remediation: "Check free disk space and the log database's permissions, then restart nxdns.",
link: SETTINGS,
},
"query_log.maintenance": {
title: "Query log maintenance failed",
impact: "Old rows are not being trimmed, so the log database grows past its retention window.",
remediation: "Check free disk space; the next maintenance pass retries on its own.",
link: SETTINGS,
},
"query_log.recreated": {
title: "Query log recreated",
impact: "The old log database was moved aside — a release changed its schema, or the file could not be read — and the history it held is not in the new one.",
remediation: "Keep or delete the aside file named below. Nothing else is required — logging is running.",
link: SETTINGS,
},
/**
* Legacy. Nothing emits this code any more: milestone 30 deleted the
* upstream-minute history subsystem. Stored rows outlive it, and the list
* endpoint passes their codes through verbatim, so the copy stays — a
* retained episode must still read as prose rather than as a dotted string.
*/
"upstream_history.write": {
title: "Upstream history write failed",
impact: "A recorded failure of a subsystem this version no longer runs. Resolution was unaffected; the per-upstream aggregates it fed are gone.",
remediation: "Nothing to do. Purge the entry once you have read it.",
link: UPSTREAMS,
},
"upstream.exchange": {
title: "Upstream failing",
impact: "Queries fall through to the remaining upstreams; answers are slower while this one backs off.",
remediation: "Check the upstream's reachability and its TLS name. Remove it if it stays down.",
link: UPSTREAMS,
},
"client_names.storage": {
title: "Client name storage failed",
impact: "Learned reverse-DNS names are not persisted, so clients can show as bare addresses after a restart.",
remediation: "Check free disk space and the configuration database's permissions.",
link: CLIENTS,
},
"clients.storage": {
title: "Client record storage failed",
impact: "New clients may not appear in the list and stale ones may not be pruned.",
remediation: "Check free disk space and the configuration database's permissions.",
link: CLIENTS,
},
"listener.start": {
title: "Encrypted DNS listener failed to start",
impact: "That endpoint is not accepting queries. Plain DNS on port 53 is unaffected.",
remediation:
"Check the bind address, the port, and the certificate paths, then restart nxdns. The episode closes on a clean start.",
link: SETTINGS,
},
"configuration.load": {
title: "Configuration problem at startup",
impact: "The setting named below was rejected or replaced by its default for this run.",
remediation: "Correct the setting and restart nxdns. The episode closes on a clean start.",
link: SETTINGS,
},
};