Gates / frontend (push) Successful in 1m34s
Gates / test (push) Successful in 2m3s
Gates / test-aarch64 (push) Failing after 3h13m33s
Gates / package (push) Successful in 5m20s
Gates / container (push) Successful in 15s
CI / gates (push) Failing after 6h30m45s
89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import type { LiveQueryEvent, PolicyReason, QueryRow, RouteKind } from "@/lib/types";
|
|
|
|
/**
|
|
* What the query-log table renders for one row, whichever surface it came from.
|
|
*
|
|
* The stored list row and the live stream's provenance event describe the same
|
|
* query in two different shapes — flat summary against nested full detail — and
|
|
* both pages share one set of cells, so both project into this.
|
|
*
|
|
* `id` is null for a streamed event: the frame precedes its own insert, so no
|
|
* row exists to link to yet.
|
|
*/
|
|
export interface QuerySummary {
|
|
id: number | null;
|
|
ts: number;
|
|
domain: string;
|
|
client_ip: string;
|
|
qtype: number | null;
|
|
blocked: boolean;
|
|
policy_reason: PolicyReason;
|
|
/** The twelve-bit extended code the client saw, including a synthesized SERVFAIL. */
|
|
rcode: number;
|
|
route_kind: RouteKind;
|
|
response_time_us: number | null;
|
|
cache_hit: boolean | null;
|
|
upstream: string;
|
|
}
|
|
|
|
/**
|
|
* Whether the cache answered, or null where it never applied. Mirrors
|
|
* `Context.cacheHit` in src/server/handler.zig, which derives the stored
|
|
* `cache_hit` column from the same route: a local record, a blocked answer and
|
|
* a protocol refusal all bypass the cache, and "miss" would claim a lookup that
|
|
* never happened.
|
|
*/
|
|
export function cacheHitFor(kind: RouteKind): boolean | null {
|
|
switch (kind) {
|
|
case "cache":
|
|
return true;
|
|
case "upstream":
|
|
case "forward_zone":
|
|
return false;
|
|
case "local":
|
|
case "blocked":
|
|
case "rejected":
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function summarizeRow(row: QueryRow): QuerySummary {
|
|
return {
|
|
id: row.id,
|
|
ts: row.ts,
|
|
domain: row.domain,
|
|
client_ip: row.client_ip,
|
|
qtype: row.qtype,
|
|
blocked: row.blocked,
|
|
policy_reason: row.policy_reason,
|
|
rcode: row.rcode,
|
|
route_kind: row.route_kind,
|
|
response_time_us: row.response_time_us,
|
|
cache_hit: row.cache_hit,
|
|
upstream: row.upstream,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The same summary out of a live frame. `blocked` and `cache_hit` are derived
|
|
* rather than sent: the server derives the stored columns from exactly these
|
|
* two fields (handler.zig's `Entry.init` call), so the projection reproduces
|
|
* them instead of the DTO carrying the same fact twice.
|
|
*/
|
|
export function summarizeEvent(event: LiveQueryEvent): QuerySummary {
|
|
return {
|
|
id: null,
|
|
ts: event.request.time,
|
|
domain: event.request.domain,
|
|
client_ip: event.request.client,
|
|
qtype: event.request.qtype,
|
|
blocked: event.policy.action === "block",
|
|
policy_reason: event.policy.reason,
|
|
rcode: event.response.rcode,
|
|
route_kind: event.route.kind,
|
|
response_time_us: event.response.duration_us,
|
|
cache_hit: cacheHitFor(event.route.kind),
|
|
upstream: event.route.upstream,
|
|
};
|
|
}
|