taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit bc009847f578335db9ff972263a9a56a69c2a618
parent 633cebb5adfc15ad00b8e39b1eb787f979fc79f4
Author: Florian Dold <dold@taler.net>
Date:   Mon, 31 Aug 2026 19:14:33 +0200

auditor web UI: link dashboard rows to findings

Diffstat:
Mpackages/taler-auditor-webui/src/InstanceRoutes.tsx | 42++++++------------------------------------
Apackages/taler-auditor-webui/src/components/DashboardTableRow.tsx | 42++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-auditor-webui/src/components/menu/SideBar.tsx | 2+-
Mpackages/taler-auditor-webui/src/paths/default/index.tsx | 2+-
Mpackages/taler-auditor-webui/src/paths/details/ListPage.tsx | 5++++-
Mpackages/taler-auditor-webui/src/paths/finance/ListPage.tsx | 17+++++++++++++----
Mpackages/taler-auditor-webui/src/paths/login/index.tsx | 2+-
Mpackages/taler-auditor-webui/src/paths/operations/ListPage.tsx | 9+++++++--
Mpackages/taler-auditor-webui/src/paths/security/ListPage.tsx | 9+++++++--
Apackages/taler-auditor-webui/src/routing/monitoringRoutes.test.ts | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-auditor-webui/src/routing/monitoringRoutes.ts | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 212 insertions(+), 48 deletions(-)

diff --git a/packages/taler-auditor-webui/src/InstanceRoutes.tsx b/packages/taler-auditor-webui/src/InstanceRoutes.tsx @@ -19,45 +19,10 @@ import NotFoundPage from "./paths/notfound/index.js"; import OperationsDashboard from "./paths/operations/index.js"; import SecurityDashboard from "./paths/security/index.js"; import { Settings } from "./paths/settings/index.js"; +import { Paths } from "./routing/monitoringRoutes.js"; import type { HttpError } from "./utils/http.js"; import type { AuditorBackend } from "./declaration.js"; -export enum Paths { - error = "/error", - settings = "/settings", - key_figures = "/key-figures", - critical_errors = "/critical-errors", - operating_status = "/operating-status", - detail_view = "/detail-view", - amount_arithmethic_inconsistency_list = "/amount-arithmetic-inconsistencies", - bad_sig_losses_list = "/bad-sig-losses", - balance_list = "/balance", - closure_lag_list = "/closure-lags", - coin_inconsistency_list = "/coin-inconsistencies", - denomination_key_validity_withdraw_inconsistency_list = "/denomination-key-validity-withdraw-inconsistencies", - denomination_without_sig_list = "/denominations-without-sig", - deposit_confirmation_list = "/deposit-confirmations", - deposit_confirmation_update = "/deposit-confirmation/:rowid/update", - emergency_list = "/emergencies", - emergency_by_count_list = "/emergencies-by-count", - exchange_signkey_list = "/exchange-sign-keys", - fee_time_inconsistency_list = "/fee-time-inconsistencies", - historic_denomination_revenue_list = "/historic-denomination-revenues", - misattribution_in_inconsistency_list = "/misattribution-in-inconsistencies", - progress_list = "/progress", - purse_not_closed_inconsistency_list = "/purse-not-closed-inconsistencies", - purse_list = "/purses", - reserve_balance_insufficient_inconsistency_list = "/reserve-balance-insufficient-inconsistencies", - reserve_balance_summary_wrong_inconsistency_list = "/reserve-balance-summary-wrong-inconsistencies", - reserve_in_inconsistency_list = "/reserve-in-inconsistencies", - reserve_not_closed_inconsistency_list = "/reserve-not-closed-inconsistencies", - reserves_list = "/reserves", - row_inconsistency_list = "/row-inconsistencies", - row_minor_inconsistency_list = "/row-minor-inconsistencies", - wire_format_inconsistency_list = "/wire-format-inconsistencies", - wire_out_inconsistency_list = "/wire-out-inconsistencies", -} - interface EntityRoute { title: string; endpoint: string; @@ -122,6 +87,11 @@ const entityRoutes: Partial<Record<Paths, EntityRoute>> = { endpoint: "historic-denomination-revenues", entity: null, }, + [Paths.historic_reserve_summary_list]: { + title: "Historic reserve summaries", + endpoint: "historic-reserve-summaries", + entity: null, + }, [Paths.misattribution_in_inconsistency_list]: { title: "Misattribution in inconsistencies", endpoint: "misattribution-in-inconsistencies", diff --git a/packages/taler-auditor-webui/src/components/DashboardTableRow.tsx b/packages/taler-auditor-webui/src/components/DashboardTableRow.tsx @@ -0,0 +1,42 @@ +/* + This file is part of GNU Taler + (C) 2021-2026 Taler Systems S.A. + */ + +import type { ComponentChildren, JSX, VNode } from "preact"; +import { useLocation } from "wouter-preact"; +import { pathForMonitoringEndpoint } from "../routing/monitoringRoutes.js"; + +interface Props { + endpoint: string; + label: string; + children: ComponentChildren; +} + +export function DashboardTableRow({ endpoint, label, children }: Props): VNode { + const [, setLocation] = useLocation(); + const path = pathForMonitoringEndpoint(endpoint); + + if (!path) { + return <tr>{children}</tr>; + } + + const onKeyDown = (event: JSX.TargetedKeyboardEvent<HTMLTableRowElement>): void => { + if (event.key !== "Enter" && event.key !== " ") return; + event.preventDefault(); + setLocation(path); + }; + + return ( + <tr + aria-label={`View ${label}`} + class="cursor-pointer transition hover:bg-blue-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-taler-brand" + onClick={() => setLocation(path)} + onKeyDown={onKeyDown} + role="link" + tabIndex={0} + > + {children} + </tr> + ); +} diff --git a/packages/taler-auditor-webui/src/components/menu/SideBar.tsx b/packages/taler-auditor-webui/src/components/menu/SideBar.tsx @@ -7,7 +7,7 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import type { VNode } from "preact"; import { Link, useLocation } from "wouter-preact"; import { useConfigContext } from "../../context/config.js"; -import { Paths } from "../../InstanceRoutes.js"; +import { Paths } from "../../routing/monitoringRoutes.js"; interface Props { mobile?: boolean; diff --git a/packages/taler-auditor-webui/src/paths/default/index.tsx b/packages/taler-auditor-webui/src/paths/default/index.tsx @@ -33,7 +33,7 @@ import { } from "../../context/entity.js"; import { AuditorBackend } from "../../declaration.js"; import { useEntityList, useEntityAPI } from "../../hooks/entity.js"; -import { Paths } from "../../InstanceRoutes.js"; +import { Paths } from "../../routing/monitoringRoutes.js"; import { Notification } from "../../utils/types.js"; import { ErrorType, HttpError } from "../../utils/http.js"; import { CardTable } from "./Table.js"; diff --git a/packages/taler-auditor-webui/src/paths/details/ListPage.tsx b/packages/taler-auditor-webui/src/paths/details/ListPage.tsx @@ -5,12 +5,13 @@ import type { VNode } from "preact"; import { Link } from "wouter-preact"; -import { Paths } from "../../InstanceRoutes.js"; +import { Paths } from "../../routing/monitoringRoutes.js"; export function ListPage(): VNode { const entries = [ [Paths.amount_arithmethic_inconsistency_list, "Amount arithmetic inconsistencies"], [Paths.bad_sig_losses_list, "Bad signature losses"], + [Paths.balance_list, "Balances"], [Paths.closure_lag_list, "Closure lags"], [Paths.coin_inconsistency_list, "Coin inconsistencies"], [Paths.denomination_key_validity_withdraw_inconsistency_list, "Denomination key validity at withdrawal"], @@ -19,6 +20,8 @@ export function ListPage(): VNode { [Paths.emergency_list, "Emergencies"], [Paths.emergency_by_count_list, "Emergencies by count"], [Paths.fee_time_inconsistency_list, "Fee time inconsistencies"], + [Paths.historic_denomination_revenue_list, "Historic denomination revenues"], + [Paths.historic_reserve_summary_list, "Historic reserve summaries"], [Paths.misattribution_in_inconsistency_list, "Misattribution-in inconsistencies"], [Paths.purse_not_closed_inconsistency_list, "Purses not closed"], [Paths.purse_list, "Purses"], diff --git a/packages/taler-auditor-webui/src/paths/finance/ListPage.tsx b/packages/taler-auditor-webui/src/paths/finance/ListPage.tsx @@ -4,6 +4,7 @@ */ import type { VNode } from "preact"; +import { DashboardTableRow } from "../../components/DashboardTableRow.js"; const coinBalances = new Set([ "Total recoup loss", @@ -50,12 +51,16 @@ function BalanceTable({ <thead><tr><th>Balance</th><th class="text-right">Value</th></tr></thead> <tbody> {balances.map((balance) => ( - <tr key={balance.balance_key}> + <DashboardTableRow + endpoint="balances" + key={balance.balance_key} + label={balanceName(balance.balance_key)} + > <td>{balanceName(balance.balance_key)}</td> <td class="whitespace-nowrap text-right font-mono"> {balance.balance_value.replace(":", " ")} </td> - </tr> + </DashboardTableRow> ))} </tbody> </table> @@ -90,12 +95,16 @@ export function ListPage(data: any): VNode { {findings.map((finding) => { const count = finding.records.length; return ( - <tr key={finding.endpoint}> + <DashboardTableRow + endpoint={finding.endpoint} + key={finding.endpoint} + label={finding.label} + > <td>{finding.label}</td> <td class={`text-right font-bold ${count === 0 ? "text-taler-success" : "text-taler-error"}`}> {count} </td> - </tr> + </DashboardTableRow> ); })} </tbody> diff --git a/packages/taler-auditor-webui/src/paths/login/index.tsx b/packages/taler-auditor-webui/src/paths/login/index.tsx @@ -5,7 +5,7 @@ import type { VNode } from "preact"; import { Redirect } from "wouter-preact"; -import { Paths } from "../../InstanceRoutes.js"; +import { Paths } from "../../routing/monitoringRoutes.js"; export function LoginPage(): VNode { return <Redirect to={Paths.key_figures} replace />; diff --git a/packages/taler-auditor-webui/src/paths/operations/ListPage.tsx b/packages/taler-auditor-webui/src/paths/operations/ListPage.tsx @@ -4,6 +4,7 @@ */ import type { VNode } from "preact"; +import { DashboardTableRow } from "../../components/DashboardTableRow.js"; export function ListPage(data: any): VNode { const summaries = data.data.data[0] as Array<{ @@ -24,12 +25,16 @@ export function ListPage(data: any): VNode { {summaries.map((finding) => { const count = finding.records.length; return ( - <tr key={finding.endpoint}> + <DashboardTableRow + endpoint={finding.endpoint} + key={finding.endpoint} + label={finding.label} + > <td>{finding.label}</td> <td class={`text-right font-bold ${count === 0 ? "text-taler-success" : "text-taler-error"}`}> {count} </td> - </tr> + </DashboardTableRow> ); })} </tbody> diff --git a/packages/taler-auditor-webui/src/paths/security/ListPage.tsx b/packages/taler-auditor-webui/src/paths/security/ListPage.tsx @@ -4,6 +4,7 @@ */ import type { VNode } from "preact"; +import { DashboardTableRow } from "../../components/DashboardTableRow.js"; export function ListPage(data: any): VNode { const summaries = data.data.data[0] as Array<{ @@ -24,12 +25,16 @@ export function ListPage(data: any): VNode { {summaries.map((finding) => { const count = finding.records.length; return ( - <tr key={finding.endpoint}> + <DashboardTableRow + endpoint={finding.endpoint} + key={finding.endpoint} + label={finding.label} + > <td>{finding.label}</td> <td class={`text-right font-bold ${count === 0 ? "text-taler-success" : "text-taler-error"}`}> {count} </td> - </tr> + </DashboardTableRow> ); })} </tbody> diff --git a/packages/taler-auditor-webui/src/routing/monitoringRoutes.test.ts b/packages/taler-auditor-webui/src/routing/monitoringRoutes.test.ts @@ -0,0 +1,52 @@ +/* + This file is part of GNU Taler + (C) 2026 Taler Systems S.A. + */ + +import assert from "node:assert"; +import test from "node:test"; +import { pathForMonitoringEndpoint, Paths } from "./monitoringRoutes.js"; + +test("dashboard monitoring endpoints have detail routes", () => { + const dashboardEndpoints = [ + "misattribution-in-inconsistencies", + "coin-inconsistencies", + "reserve-in-inconsistencies", + "bad-sig-losses", + "balances", + "amount-arithmetic-inconsistencies", + "wire-format-inconsistencies", + "wire-out-inconsistencies", + "reserve-balance-summary-wrong-inconsistencies", + "fee-time-inconsistencies", + "emergencies", + "emergencies-by-count", + "reserve-balance-insufficient-inconsistencies", + "row-inconsistencies", + "purse-not-closed-inconsistencies", + "reserve-not-closed-inconsistencies", + "denominations-without-sigs", + "deposit-confirmations", + "denomination-key-validity-withdraw-inconsistencies", + "closure-lags", + "row-minor-inconsistencies", + "historic-denomination-revenues", + "historic-reserve-summaries", + ]; + + for (const endpoint of dashboardEndpoints) { + assert.ok(pathForMonitoringEndpoint(endpoint), `missing route for ${endpoint}`); + } +}); + +test("monitoring endpoints with non-matching paths resolve correctly", () => { + assert.equal(pathForMonitoringEndpoint("balances"), Paths.balance_list); + assert.equal( + pathForMonitoringEndpoint("denominations-without-sigs"), + Paths.denomination_without_sig_list, + ); + assert.equal( + pathForMonitoringEndpoint("historic-reserve-summaries"), + Paths.historic_reserve_summary_list, + ); +}); diff --git a/packages/taler-auditor-webui/src/routing/monitoringRoutes.ts b/packages/taler-auditor-webui/src/routing/monitoringRoutes.ts @@ -0,0 +1,78 @@ +/* + This file is part of GNU Taler + (C) 2021-2026 Taler Systems S.A. + */ + +export enum Paths { + error = "/error", + settings = "/settings", + key_figures = "/key-figures", + critical_errors = "/critical-errors", + operating_status = "/operating-status", + detail_view = "/detail-view", + amount_arithmethic_inconsistency_list = "/amount-arithmetic-inconsistencies", + bad_sig_losses_list = "/bad-sig-losses", + balance_list = "/balance", + closure_lag_list = "/closure-lags", + coin_inconsistency_list = "/coin-inconsistencies", + denomination_key_validity_withdraw_inconsistency_list = "/denomination-key-validity-withdraw-inconsistencies", + denomination_without_sig_list = "/denominations-without-sig", + deposit_confirmation_list = "/deposit-confirmations", + deposit_confirmation_update = "/deposit-confirmation/:rowid/update", + emergency_list = "/emergencies", + emergency_by_count_list = "/emergencies-by-count", + exchange_signkey_list = "/exchange-sign-keys", + fee_time_inconsistency_list = "/fee-time-inconsistencies", + historic_denomination_revenue_list = "/historic-denomination-revenues", + historic_reserve_summary_list = "/historic-reserve-summaries", + misattribution_in_inconsistency_list = "/misattribution-in-inconsistencies", + progress_list = "/progress", + purse_not_closed_inconsistency_list = "/purse-not-closed-inconsistencies", + purse_list = "/purses", + reserve_balance_insufficient_inconsistency_list = "/reserve-balance-insufficient-inconsistencies", + reserve_balance_summary_wrong_inconsistency_list = "/reserve-balance-summary-wrong-inconsistencies", + reserve_in_inconsistency_list = "/reserve-in-inconsistencies", + reserve_not_closed_inconsistency_list = "/reserve-not-closed-inconsistencies", + reserves_list = "/reserves", + row_inconsistency_list = "/row-inconsistencies", + row_minor_inconsistency_list = "/row-minor-inconsistencies", + wire_format_inconsistency_list = "/wire-format-inconsistencies", + wire_out_inconsistency_list = "/wire-out-inconsistencies", +} + +const monitoringPathByEndpoint: Readonly<Record<string, Paths>> = { + "amount-arithmetic-inconsistencies": Paths.amount_arithmethic_inconsistency_list, + "bad-sig-losses": Paths.bad_sig_losses_list, + balances: Paths.balance_list, + "closure-lags": Paths.closure_lag_list, + "coin-inconsistencies": Paths.coin_inconsistency_list, + "denomination-key-validity-withdraw-inconsistencies": + Paths.denomination_key_validity_withdraw_inconsistency_list, + "denominations-without-sigs": Paths.denomination_without_sig_list, + "deposit-confirmations": Paths.deposit_confirmation_list, + emergencies: Paths.emergency_list, + "emergencies-by-count": Paths.emergency_by_count_list, + "exchange-sign-keys": Paths.exchange_signkey_list, + "fee-time-inconsistencies": Paths.fee_time_inconsistency_list, + "historic-denomination-revenues": Paths.historic_denomination_revenue_list, + "historic-reserve-summaries": Paths.historic_reserve_summary_list, + "misattribution-in-inconsistencies": Paths.misattribution_in_inconsistency_list, + progress: Paths.progress_list, + "purse-not-closed-inconsistencies": Paths.purse_not_closed_inconsistency_list, + purses: Paths.purse_list, + "reserve-balance-insufficient-inconsistencies": + Paths.reserve_balance_insufficient_inconsistency_list, + "reserve-balance-summary-wrong-inconsistencies": + Paths.reserve_balance_summary_wrong_inconsistency_list, + "reserve-in-inconsistencies": Paths.reserve_in_inconsistency_list, + "reserve-not-closed-inconsistencies": Paths.reserve_not_closed_inconsistency_list, + reserves: Paths.reserves_list, + "row-inconsistencies": Paths.row_inconsistency_list, + "row-minor-inconsistencies": Paths.row_minor_inconsistency_list, + "wire-format-inconsistencies": Paths.wire_format_inconsistency_list, + "wire-out-inconsistencies": Paths.wire_out_inconsistency_list, +}; + +export function pathForMonitoringEndpoint(endpoint: string): Paths | undefined { + return monitoringPathByEndpoint[endpoint]; +}