From 142593083f1d0ad0443ccadeedbad7daab2fd36f Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 19 Sep 2026 02:02:45 +0200 Subject: [PATCH] feat(diagnostics): walk the log lines from the keyboard; name the console "Server log" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both log viewers put their lines in a NavGrid: j/k (and the arrows) step from line to line, and Enter opens one exactly as a click does (the line's button is its `data-nav-open`). A focused line is named by its code and message. The console's nav entry is "Szervernapló" / "Server log", its page title: "Diagnosztika" was already the Settings tab that holds the user's own log, and two places with one name read as one place. --- .../src/components/AdminLogs.dom.test.tsx | 11 ++++ frontend/src/components/AdminLogs.tsx | 44 ++++++++------- frontend/src/components/LogLine.tsx | 11 +++- frontend/src/components/MyLogViewer.tsx | 56 ++++++++++--------- frontend/src/i18n/locales/en/header.json | 2 +- frontend/src/i18n/locales/hu/header.json | 2 +- 6 files changed, 77 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/AdminLogs.dom.test.tsx b/frontend/src/components/AdminLogs.dom.test.tsx index 773ec9e7..f4646334 100644 --- a/frontend/src/components/AdminLogs.dom.test.tsx +++ b/frontend/src/components/AdminLogs.dom.test.tsx @@ -142,6 +142,17 @@ describe("AdminLogs", () => { ).toBe("true"); }); + it("the keyboard can walk the lines and open one (NavGrid items)", async () => { + logsMock.mockResolvedValue({ items: [row(2), row(1)], accounts: {}, next_cursor: null }); + renderConsole(); + await waitFor(() => expect(lines()).toHaveLength(2)); + + const items = lines().map((b) => b.parentElement!); + expect(items.map((el) => el.getAttribute("data-nav-index"))).toEqual(["0", "1"]); + expect(lines().every((b) => b.hasAttribute("data-nav-open"))).toBe(true); + expect(screen.getByTestId("admin-logs-lines").querySelector("[data-nav-list]")).toBeTruthy(); + }); + it("a failed read is an error with a retry, not an empty log", async () => { logsMock.mockRejectedValue(new Error("502")); renderConsole(); diff --git a/frontend/src/components/AdminLogs.tsx b/frontend/src/components/AdminLogs.tsx index 9289de67..1d8b9992 100644 --- a/frontend/src/components/AdminLogs.tsx +++ b/frontend/src/components/AdminLogs.tsx @@ -24,13 +24,14 @@ import { qk } from "../lib/queryKeys"; import { useDebounced } from "../lib/useDebounced"; import { useUrlMirror } from "../lib/useUrlMirror"; import LogLine from "./LogLine"; +import NavGrid from "./ui/NavGrid"; import { Button } from "./ui/Button"; import FilterChipGroup from "./ui/FilterChipGroup"; import FilterSelect from "./ui/FilterSelect"; import { Section } from "./ui/form"; import { LoadingState, StateMessage } from "./ui/QueryState"; -// Admin → Diagnosztika: the server's stored log (`log_events`, E22.6). One list, newest first; a line +// Admin → Szervernapló: the server's stored log (`log_events`, E22.6). One list, newest first; a line // opens in place to its full record, and its request id / job id narrow the list to that request or // job. Filters live in the URL, so a filtered view is a link. "Élő" re-reads the newest page every // few seconds while the tab is visible — the newest page only: live is watching the top, so paging @@ -287,25 +288,28 @@ export default function AdminLogs() { {t("adminLogs.empty")}

) : ( - rows.map((row) => ( - setOpen(open === row.id ? null : row.id)} - > - pivot({ requestId, jobId: null })} - onJob={(jobId) => pivot({ requestId: null, jobId })} - /> - - )) + + {rows.map((row, index) => ( + setOpen(open === row.id ? null : row.id)} + > + pivot({ requestId, jobId: null })} + onJob={(jobId) => pivot({ requestId: null, jobId })} + /> + + ))} + )} diff --git a/frontend/src/components/LogLine.tsx b/frontend/src/components/LogLine.tsx index 7dc63fed..bb9ea5cc 100644 --- a/frontend/src/components/LogLine.tsx +++ b/frontend/src/components/LogLine.tsx @@ -1,10 +1,12 @@ import type { ReactNode } from "react"; import type { Level } from "../lib/log"; +import { navItemAttrs } from "../lib/listNav"; // One log line as both log viewers draw it — "Saját napló" (this tab's ring) and the admin // diagnostics console (the server's stored log): time, level, event code, message, and the line opens // in place to whatever detail its screen has. One component, so a level reads the same on both. +// Inside a NavGrid the keyboard walks the lines (`j`/`k`) and Enter opens one, as a click does. const LEVEL_TAG: Record = { TRACE: { text: "TRACE", className: "text-muted opacity-80" }, @@ -30,6 +32,7 @@ export default function LogLine({ open, onToggle, title, + navIndex, children, }: { ts: string; @@ -40,14 +43,20 @@ export default function LogLine({ onToggle: () => void; /** The row button's tooltip — the admin console puts the full date there. */ title?: string; + /** The line's place in its NavGrid. */ + navIndex: number; /** The detail shown while the line is open. */ children: ReactNode; }) { const tag = LEVEL_TAG[level]; return ( -
+
diff --git a/frontend/src/i18n/locales/en/header.json b/frontend/src/i18n/locales/en/header.json index c29248ed..19968222 100644 --- a/frontend/src/i18n/locales/en/header.json +++ b/frontend/src/i18n/locales/en/header.json @@ -35,7 +35,7 @@ "addAccount": "Add another account", "switchTo": "Switch to {{name}}", "audit": "Audit log", - "logs": "Diagnostics", + "logs": "Server log", "googleAddress": "Google account: {{email}}" }, "sync": { diff --git a/frontend/src/i18n/locales/hu/header.json b/frontend/src/i18n/locales/hu/header.json index 2c744d33..7aa1d217 100644 --- a/frontend/src/i18n/locales/hu/header.json +++ b/frontend/src/i18n/locales/hu/header.json @@ -35,7 +35,7 @@ "addAccount": "Másik fiók hozzáadása", "switchTo": "Váltás erre: {{name}}", "audit": "Auditnapló", - "logs": "Diagnosztika", + "logs": "Szervernapló", "googleAddress": "Google-fiók: {{email}}" }, "sync": {