feat(diagnostics): walk the log lines from the keyboard; name the console "Server log"
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.
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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,8 +288,10 @@ export default function AdminLogs() {
|
||||
{t("adminLogs.empty")}
|
||||
</p>
|
||||
) : (
|
||||
rows.map((row) => (
|
||||
<NavGrid>
|
||||
{rows.map((row, index) => (
|
||||
<LogLine
|
||||
navIndex={index}
|
||||
key={row.id}
|
||||
ts={row.ts}
|
||||
level={row.level}
|
||||
@@ -305,7 +308,8 @@ export default function AdminLogs() {
|
||||
onJob={(jobId) => pivot({ requestId: null, jobId })}
|
||||
/>
|
||||
</LogLine>
|
||||
))
|
||||
))}
|
||||
</NavGrid>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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<Level, { text: string; className: string }> = {
|
||||
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 (
|
||||
<div className="border-b border-border/50 last:border-b-0">
|
||||
<div
|
||||
className="border-b border-border/50 last:border-b-0"
|
||||
{...navItemAttrs(navIndex, `${event} ${msg}`)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-nav-open
|
||||
aria-expanded={open}
|
||||
onClick={onToggle}
|
||||
title={title}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../lib/log";
|
||||
import { notify } from "../lib/notifications";
|
||||
import LogLine from "./LogLine";
|
||||
import NavGrid from "./ui/NavGrid";
|
||||
import { Button } from "./ui/Button";
|
||||
import FilterChipGroup from "./ui/FilterChipGroup";
|
||||
|
||||
@@ -150,8 +151,10 @@ export default function MyLogViewer() {
|
||||
{all.length === 0 ? t("settings.myLog.emptyAll") : t("settings.myLog.empty")}
|
||||
</p>
|
||||
) : (
|
||||
shown.slice(0, ROWS_SHOWN).map((e) => (
|
||||
<NavGrid>
|
||||
{shown.slice(0, ROWS_SHOWN).map((e, index) => (
|
||||
<LogLine
|
||||
navIndex={index}
|
||||
key={rowKey(e)}
|
||||
ts={e.ts}
|
||||
level={e.level}
|
||||
@@ -175,7 +178,8 @@ export default function MyLogViewer() {
|
||||
)}
|
||||
</pre>
|
||||
</LogLine>
|
||||
))
|
||||
))}
|
||||
</NavGrid>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user