The audit log, the server log and the admin quota view were three pages (the quota one a Stats tab),
each with its own filters, and the background runs had no list at all. Now one page ("Napló" /
"Activity") reads all four, each still its own table on the server: a shared bar (text, a period of
1 h to 90 days defaulting to 7, an account) and each tab's own filters, the whole view in the URL.
The old page ids open their tab.
Server side: the audit log and the new run list (`/api/admin/runs`) filter and page like the server
log, through one keyset helper (`routes/_keyset`) the log now shares; an audit row keeps the request
it was made in (migration 0111), so its log lines are a pivot away, as a run's are (`lrun`). The
quota breakdown takes since/until and one account. The Scheduler rows get a history button to their
job's runs.
Also fixed on the way: re-saving the value in force on the Configuration page staged its override
without committing it (the commit sat inside the audit branch), so the save was lost.
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
// The server-log tab's own filters in the address bar (`?page=activity&at=logs&lgrp=error&lreq=…`),
|
|
// so a filtered view — a request's lines, say — is a link. The filters every activity tab shares
|
|
// (time range, account, text) live in ./activityUrlState. Keys are namespaced (`l…`) like the Plex
|
|
// and Channels ones; setPage clears them (via ACTIVITY_KEYS) and the tab re-mirrors them while open.
|
|
import type { LevelGroup } from "./log";
|
|
|
|
export interface LogsUrlState {
|
|
group: LevelGroup;
|
|
sources: string[];
|
|
/** An event code, or `none` for the rows without one. */
|
|
event: string | null;
|
|
requestId: string | null;
|
|
jobId: number | null;
|
|
/** A background run's lines (`job_runs.id`). */
|
|
runId: number | null;
|
|
}
|
|
|
|
export const DEFAULT_LOGS_STATE: LogsUrlState = {
|
|
group: "all",
|
|
sources: [],
|
|
event: null,
|
|
requestId: null,
|
|
jobId: null,
|
|
runId: null,
|
|
};
|
|
|
|
export const LOGS_KEYS = ["lgrp", "lsrc", "lev", "lreq", "ljob", "lrun"] as const;
|
|
|
|
const GROUPS: readonly LevelGroup[] = ["all", "error", "warning", "info", "detail"];
|
|
// The same shapes the API validates: a link someone edited by hand degrades to "no filter" here
|
|
// instead of to a 422 on every request the console makes.
|
|
const EVENT_RE = /^(none|[a-z][a-z0-9_]*(\.[a-z0-9_]+)*)$/;
|
|
const REQUEST_RE = /^[A-Za-z0-9._-]{8,64}$/;
|
|
const SOURCE_RE = /^[a-z]{1,16}$/;
|
|
const SOURCES_MAX = 8;
|
|
|
|
const pick = (value: string | null, re: RegExp, max = 64) =>
|
|
value && value.length <= max && re.test(value) ? value : null;
|
|
const positive = (value: string | null) =>
|
|
value && /^\d{1,10}$/.test(value) && Number(value) > 0 ? Number(value) : null;
|
|
|
|
export function logsParamsToState(params: URLSearchParams): LogsUrlState {
|
|
const group = params.get("lgrp");
|
|
return {
|
|
group: GROUPS.includes(group as LevelGroup) ? (group as LevelGroup) : DEFAULT_LOGS_STATE.group,
|
|
sources: (params.get("lsrc") ?? "")
|
|
.split(",")
|
|
.filter((s) => SOURCE_RE.test(s))
|
|
.slice(0, SOURCES_MAX),
|
|
event: pick(params.get("lev"), EVENT_RE),
|
|
requestId: pick(params.get("lreq"), REQUEST_RE),
|
|
jobId: positive(params.get("ljob")),
|
|
runId: positive(params.get("lrun")),
|
|
};
|
|
}
|
|
|
|
/** The URL updates for a state: a filter at its default clears its key. */
|
|
export function logsParamUpdates(state: LogsUrlState): Record<string, string | null> {
|
|
return {
|
|
lgrp: state.group === DEFAULT_LOGS_STATE.group ? null : state.group,
|
|
lsrc: state.sources.length ? state.sources.join(",") : null,
|
|
lev: state.event,
|
|
lreq: state.requestId,
|
|
ljob: state.jobId === null ? null : String(state.jobId),
|
|
lrun: state.runId === null ? null : String(state.runId),
|
|
};
|
|
}
|