feat(activity): one Activity page — runs, admin actions, server log and quota under one filter bar
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.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// An audit row whose server summary is a fixed English line ("log_level = DEBUG", "rss_poll → 15
|
||||
// min/run", "Background sync paused") reads in the reader's language, rebuilt from the row's
|
||||
// data through the REAL catalog. A row whose summary is data (an e-mail, a URL) keeps it.
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import i18n, { setLanguage } from "../i18n";
|
||||
import type { AuditRow } from "./api";
|
||||
import { auditText } from "./auditText";
|
||||
|
||||
afterEach(() => {
|
||||
setLanguage("en");
|
||||
});
|
||||
|
||||
const row = (over: Partial<AuditRow>): AuditRow => ({
|
||||
id: 1,
|
||||
created_at: null,
|
||||
actor_id: null,
|
||||
action: "sync_pause",
|
||||
target_type: null,
|
||||
target_id: null,
|
||||
summary: null,
|
||||
before: null,
|
||||
after: null,
|
||||
request_id: null,
|
||||
...over,
|
||||
});
|
||||
|
||||
function details(r: AuditRow): string {
|
||||
setLanguage("hu");
|
||||
const t = i18n.getFixedT("hu");
|
||||
const { head, sub } = auditText(t, r, "hu");
|
||||
return [head, sub].filter(Boolean).join(" | ");
|
||||
}
|
||||
|
||||
describe("an audit row's details", () => {
|
||||
it("name a config change's field and values", () => {
|
||||
const set = row({
|
||||
action: "config_set",
|
||||
target_type: "config",
|
||||
target_id: "plex_enabled",
|
||||
summary: "plex_enabled = True",
|
||||
before: { value: false },
|
||||
after: { value: true },
|
||||
});
|
||||
expect(details(set)).toMatch(/ \| Ki → Be$/);
|
||||
expect(details(set)).not.toContain("plex_enabled");
|
||||
const reset = row({
|
||||
action: "config_reset",
|
||||
target_type: "config",
|
||||
target_id: "download_upload_max_bytes",
|
||||
summary: "download_upload_max_bytes → default",
|
||||
before: { value: 5242880 },
|
||||
});
|
||||
expect(details(reset)).toMatch(/ \| 5\s242\s880 → alapérték$/);
|
||||
const secret = row({
|
||||
action: "config_set",
|
||||
target_type: "config",
|
||||
target_id: "youtube_api_key",
|
||||
summary: "youtube_api_key = (secret updated)",
|
||||
});
|
||||
expect(details(secret)).toMatch(/ \| titok frissítve/);
|
||||
});
|
||||
|
||||
it("tell a secret's reset from its update, and name a choice", () => {
|
||||
const reset = row({
|
||||
action: "config_reset",
|
||||
target_type: "config",
|
||||
target_id: "tmdb_api_key",
|
||||
summary: "tmdb_api_key → default",
|
||||
});
|
||||
expect(details(reset)).toMatch(/ \| titok visszaállítva/);
|
||||
const choice = row({
|
||||
action: "config_set",
|
||||
target_type: "config",
|
||||
target_id: "plex_transcode_hw",
|
||||
before: { value: "auto" },
|
||||
after: { value: "on" },
|
||||
});
|
||||
expect(details(choice)).toMatch(/ \| Automatikus → GPU$/);
|
||||
});
|
||||
|
||||
it("name an interval, a batch and a count in the reader's language", () => {
|
||||
expect(
|
||||
details(
|
||||
row({
|
||||
action: "scheduler_interval",
|
||||
target_type: "job",
|
||||
target_id: "rss_poll",
|
||||
summary: "rss_poll → 15 min/run",
|
||||
after: { interval_minutes: 15 },
|
||||
}),
|
||||
),
|
||||
).toBe("RSS-lekérdezés (új feltöltések) | 15 percenként");
|
||||
expect(
|
||||
details(row({ action: "maintenance_tune", after: { revalidate_batch: 50000 } })),
|
||||
).toMatch(/^Futásonként újraellenőrzött videók \| 50\s000$/);
|
||||
expect(
|
||||
details(
|
||||
row({ action: "audit_clear", summary: "Cleared 12 audit entries", after: { cleared: 12 } }),
|
||||
),
|
||||
).not.toContain("Cleared");
|
||||
});
|
||||
|
||||
it("add nothing to a pause, whose action already says it", () => {
|
||||
expect(details(row({ action: "sync_pause", summary: "Background sync paused" }))).toBe("");
|
||||
});
|
||||
|
||||
it("keep a summary that is data", () => {
|
||||
expect(details(row({ action: "invite_add", summary: "someone@example.com" }))).toBe(
|
||||
"someone@example.com",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user