taler-typescript-core

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

commit 99c8a394dffb04b9e11188f1ba8bf8ce9965a3b4
parent 7cebb432ed98dc75c8763c3f38c21ade879738d5
Author: Florian Dold <dold@taler.net>
Date:   Mon, 31 Aug 2026 14:56:16 +0200

auditor web UI: use version 2 monitoring API

Diffstat:
Mpackages/taler-auditor-webui/src/Application.tsx | 2+-
Mpackages/taler-auditor-webui/src/InstanceRoutes.tsx | 61++++++++++++++++---------------------------------------------
Mpackages/taler-auditor-webui/src/components/menu/index.tsx | 4----
Mpackages/taler-auditor-webui/src/declaration.d.ts | 18+++++++++++++++---
Mpackages/taler-auditor-webui/src/hooks/critical.ts | 28++++++++++++++++++++--------
Mpackages/taler-auditor-webui/src/hooks/entity.ts | 8++++----
Mpackages/taler-auditor-webui/src/hooks/finance.ts | 50+++++++++++++++++++++++++++++++++++++-------------
Mpackages/taler-auditor-webui/src/hooks/operational.ts | 61++++++++++++++++++++++++++++++++++++++++++++-----------------
Mpackages/taler-auditor-webui/src/paths/default/Table.tsx | 7+++----
Mpackages/taler-auditor-webui/src/paths/details/ListPage.tsx | 24------------------------
Mpackages/taler-auditor-webui/src/paths/finance/ListPage.tsx | 27+++++++++------------------
Mpackages/taler-auditor-webui/src/paths/operations/ListPage.tsx | 17+++--------------
Mpackages/taler-auditor-webui/src/paths/security/ListPage.tsx | 19+++----------------
13 files changed, 155 insertions(+), 171 deletions(-)

diff --git a/packages/taler-auditor-webui/src/Application.tsx b/packages/taler-auditor-webui/src/Application.tsx @@ -135,7 +135,7 @@ function ApplicationStatusRoutes(): VNode { ); } - const SUPPORTED_VERSION = "1:0:1"; + const SUPPORTED_VERSION = "2:0:0"; if ( result.data && !LibtoolVersion.compare(SUPPORTED_VERSION, result.data.version)?.compatible diff --git a/packages/taler-auditor-webui/src/InstanceRoutes.tsx b/packages/taler-auditor-webui/src/InstanceRoutes.tsx @@ -57,8 +57,6 @@ export enum Paths { denomination_key_validity_withdraw_inconsistency_list = "/denomination-key-validity-withdraw-inconsistencies", - denomination_pending_list = "/denominations-pending", - denomination_without_sig_list = "/denominations-without-sig", deposit_confirmation_list = "/deposit-confirmations", @@ -82,8 +80,6 @@ export enum Paths { purse_list = "/purses", - refresh_hanging_list = "/refreshes-hanging", - reserve_balance_insufficient_inconsistency_list = "/reserve-balance-insufficient-inconsistencies", reserve_balance_summary_wrong_inconsistency_list = "/reserve-balance-summary-wrong-inconsistencies", @@ -124,7 +120,7 @@ function getInstanceTitle(path: string): TestProps { {} as AuditorBackend.AmountArithmeticInconsistency.ClassAmountArithmeticInconsistency; return { title: `Amount arithmetic inconsistencies`, - endpoint: "amount-arithmetic-inconsistency", + endpoint: "amount-arithmetic-inconsistencies", entity: amountArithmeticInconsistency, }; } @@ -141,19 +137,13 @@ function getInstanceTitle(path: string): TestProps { case Paths.coin_inconsistency_list: return { title: `Coin inconsistencies`, - endpoint: "coin-inconsistency", + endpoint: "coin-inconsistencies", entity: null, }; case Paths.denomination_key_validity_withdraw_inconsistency_list: return { title: `Denomination key validity withdraw inconsistency`, - endpoint: "denomination-key-validity-withdraw-inconsistency", - entity: null, - }; - case Paths.denomination_pending_list: - return { - title: `Denominations pending`, - endpoint: "denomination-pending", + endpoint: "denomination-key-validity-withdraw-inconsistencies", entity: null, }; case Paths.denomination_without_sig_list: @@ -165,33 +155,33 @@ function getInstanceTitle(path: string): TestProps { case Paths.deposit_confirmation_list: return { title: "Deposit Confirmations", - endpoint: "deposit-confirmation", + endpoint: "deposit-confirmations", entity: null, }; case Paths.emergency_list: - return { title: "Emergencies", endpoint: "emergency", entity: null }; + return { title: "Emergencies", endpoint: "emergencies", entity: null }; case Paths.emergency_by_count_list: return { title: "Emergencies by count", - endpoint: "emergency-by-count", + endpoint: "emergencies-by-count", entity: null, }; case Paths.fee_time_inconsistency_list: return { title: "Fee time inconsistencies", - endpoint: "fee-time-inconsistency", + endpoint: "fee-time-inconsistencies", entity: null, }; case Paths.historic_denomination_revenue_list: return { title: "Historic denomination revenue", - endpoint: "historic-denomination-revenue", + endpoint: "historic-denomination-revenues", entity: null, }; case Paths.misattribution_in_inconsistency_list: return { title: "Misattribution in inconsistencies", - endpoint: "misattribution-in-inconsistency", + endpoint: "misattribution-in-inconsistencies", entity: null, }; case Paths.progress_list: @@ -204,42 +194,36 @@ function getInstanceTitle(path: string): TestProps { }; case Paths.purse_list: return { title: "Purses", endpoint: "purses", entity: null }; - case Paths.refresh_hanging_list: - return { - title: "Refreshes hanging", - endpoint: "refreshes-hanging", - entity: null, - }; case Paths.reserves_list: return { title: "Reserves", endpoint: "reserves", entity: null }; case Paths.reserve_balance_insufficient_inconsistency_list: return { title: "Reserve balance insufficient inconsistencies", - endpoint: "reserve-balance-insufficient-inconsistency", + endpoint: "reserve-balance-insufficient-inconsistencies", entity: null, }; case Paths.reserve_balance_summary_wrong_inconsistency_list: return { title: "Reserve balance summary wrong inconsistencies", - endpoint: "reserve-balance-summary-wrong-inconsistency", + endpoint: "reserve-balance-summary-wrong-inconsistencies", entity: null, }; case Paths.reserve_in_inconsistency_list: return { title: "Reserves in inconsistencies", - endpoint: "reserve-in-inconsistency", + endpoint: "reserve-in-inconsistencies", entity: null, }; case Paths.reserve_not_closed_inconsistency_list: return { title: "Reserves not closed inconsistencies", - endpoint: "reserve-not-closed-inconsistency", + endpoint: "reserve-not-closed-inconsistencies", entity: null, }; case Paths.row_inconsistency_list: return { title: "Row inconsistencies", - endpoint: "row-inconsistency", + endpoint: "row-inconsistencies", entity: null, }; case Paths.row_minor_inconsistency_list: @@ -253,14 +237,14 @@ function getInstanceTitle(path: string): TestProps { {} as AuditorBackend.WireFormatInconsistency.ClassWireFormatInconsistency; return { title: "Wire format inconsistencies", - endpoint: "wire-format-inconsistency", + endpoint: "wire-format-inconsistencies", entity: wireFormatInconsistency, }; } case Paths.wire_out_inconsistency_list: return { title: "Wire out inconsistencies", - endpoint: "wire-out-inconsistency", + endpoint: "wire-out-inconsistencies", entity: null, }; case Paths.settings: @@ -417,12 +401,6 @@ export function InstanceRoutes({ path }: Props): VNode { //onLoadError={ServerErrorRedirectTo(Paths.balance_list)} /> <Route - path={Paths.denomination_pending_list} - component={DefaultList} - onNotFound={NotFoundPage} - //onLoadError={ServerErrorRedirectTo(Paths.balance_list)} - /> - <Route path={Paths.denomination_without_sig_list} component={DefaultList} onNotFound={NotFoundPage} @@ -497,13 +475,6 @@ export function InstanceRoutes({ path }: Props): VNode { } { <Route - path={Paths.refresh_hanging_list} - component={DefaultList} - onNotFound={NotFoundPage} - /> - } - { - <Route path={Paths.reserve_balance_insufficient_inconsistency_list} component={DefaultList} onNotFound={NotFoundPage} diff --git a/packages/taler-auditor-webui/src/components/menu/index.tsx b/packages/taler-auditor-webui/src/components/menu/index.tsx @@ -51,8 +51,6 @@ function getInstanceTitle(path: string): string { return `Coin inconsistencies`; case Paths.denomination_key_validity_withdraw_inconsistency_list: return `Denomination key validity withdraw inconsistency`; - case Paths.denomination_pending_list: - return `Denominations pending`; case Paths.denomination_without_sig_list: return `Denominations without sigs`; case Paths.deposit_confirmation_list: @@ -77,8 +75,6 @@ function getInstanceTitle(path: string): string { return `Purse not closed inconsistencies`; case Paths.purse_list: return `Purses`; - case Paths.refresh_hanging_list: - return `Refreshes hanging`; case Paths.reserve_balance_insufficient_inconsistency_list: return `Reserve balance insufficient inconsistencies`; case Paths.reserve_balance_summary_wrong_inconsistency_list: diff --git a/packages/taler-auditor-webui/src/declaration.d.ts b/packages/taler-auditor-webui/src/declaration.d.ts @@ -24,6 +24,16 @@ export interface WithId { id: string; } +export interface RecordsResponse<T> { + records: T[]; +} + +export interface MonitoringSummary<T = Record<string, unknown>> { + endpoint: string; + label: string; + records: T[]; +} + export namespace AuditorBackend { interface ErrorDetail { // Numeric error code unique to the condition. @@ -70,6 +80,7 @@ export namespace AuditorBackend { name: "taler-auditor"; version: string; + build_version: string; // Default (!) currency supported by this backend. // This is the currency that the backend should @@ -105,11 +116,12 @@ export namespace AuditorBackend { } interface SummaryResponse { - amount_arithmetic_inconsistency: AmountArithmeticInconsistencyDetail[]; + records: AmountArithmeticInconsistencyDetail[]; } interface AmountArithmeticInconsistencyDetail { row_id: number; + problem_row_id: number; operation: string; exchange_amount: string; auditor_amount: string; @@ -124,13 +136,13 @@ export namespace AuditorBackend { } interface SummaryResponse { - responseData: WireFormatInconsistencyDetail[]; + records: WireFormatInconsistencyDetail[]; } interface WireFormatInconsistencyDetail { row_id: number; amount: string; - wire_offset: string; + wire_offset: number; diagnostic: string; suppressed: boolean; } diff --git a/packages/taler-auditor-webui/src/hooks/critical.ts b/packages/taler-auditor-webui/src/hooks/critical.ts @@ -13,7 +13,11 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AuditorBackend } from "../declaration.js"; +import { + AuditorBackend, + MonitoringSummary, + RecordsResponse, +} from "../declaration.js"; import { HttpResponse, HttpResponseOk, RequestError } from "../utils/http.js"; import { useBackendRequest } from "./backend.js"; @@ -37,16 +41,20 @@ export function getCriticalData( ): HttpResponse<any, AuditorBackend.ErrorDetail> { const { multiFetcher } = useBackendRequest(); const endpoints = [ - "monitoring/fee-time-inconsistency", - "monitoring/emergency", - "monitoring/emergency-by-count", - "monitoring/reserve-balance-insufficient-inconsistency", + { endpoint: "fee-time-inconsistencies", label: "Fee time inconsistencies" }, + { endpoint: "emergencies", label: "Emergencies" }, + { endpoint: "emergencies-by-count", label: "Emergencies by count" }, + { + endpoint: "reserve-balance-insufficient-inconsistencies", + label: "Insufficient reserve balances", + }, ]; + const paths = endpoints.map(({ endpoint }) => `monitoring/${endpoint}`); const { data: list, error: listError } = useSWR< - HttpResponseOk<any>[], + HttpResponseOk<RecordsResponse<Record<string, unknown>>>[], RequestError<AuditorBackend.ErrorDetail> - >(endpoints, multiFetcher, { + >(paths, multiFetcher, { refreshInterval: 60000, refreshWhenHidden: false, revalidateOnFocus: false, @@ -57,7 +65,11 @@ export function getCriticalData( if (listError) return listError.cause; if (list) { - return { ok: true, data: [list] }; + const summaries: MonitoringSummary[] = list.map((response, index) => ({ + ...endpoints[index], + records: response.data.records, + })); + return { ok: true, data: [summaries] }; } return { loading: true }; } diff --git a/packages/taler-auditor-webui/src/hooks/entity.ts b/packages/taler-auditor-webui/src/hooks/entity.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { AuditorBackend } from "../declaration.js"; +import { AuditorBackend, RecordsResponse } from "../declaration.js"; import { HttpResponse, HttpResponseOk, RequestError } from "../utils/http.js"; import { useBackendRequest, useMatchMutate } from "./backend.js"; @@ -39,7 +39,7 @@ export function getEntityList({ const { fetcher } = useBackendRequest(); const { data: list, error: listError } = useSWR< - HttpResponseOk<typeof entity>, + HttpResponseOk<RecordsResponse<typeof entity>>, RequestError<AuditorBackend.ErrorDetail> >([`monitoring/` + endpoint], fetcher, { refreshInterval: 0, @@ -52,7 +52,7 @@ export function getEntityList({ if (listError) return listError.cause; if (list?.data != null) { - return { ok: true, data: [list?.data] }; + return { ok: true, data: list.data.records }; } return { loading: true }; } @@ -67,7 +67,7 @@ export function useEntityAPI(): EntityAPI { const data = { suppressed: true }; const updateEntity = async (id: string): Promise<void> => { - const r = await request(`monitoring/${endpoint}/${id}`, { + await request(`monitoring/${endpoint}/${id}`, { method: "PATCH", data, }); diff --git a/packages/taler-auditor-webui/src/hooks/finance.ts b/packages/taler-auditor-webui/src/hooks/finance.ts @@ -17,7 +17,11 @@ /** * Imports. */ -import { AuditorBackend } from "../declaration.js"; +import { + AuditorBackend, + MonitoringSummary, + RecordsResponse, +} from "../declaration.js"; import { HttpResponse, HttpResponseOk, RequestError } from "../utils/http.js"; import { useBackendRequest } from "./backend.js"; @@ -32,21 +36,37 @@ export function getKeyFiguresData(): HttpResponse< > { const { multiFetcher } = useBackendRequest(); const endpoints = [ - "monitoring/misattribution-in-inconsistency", - "monitoring/coin-inconsistency", - "monitoring/reserve-in-inconsistency", - "monitoring/bad-sig-losses", - "monitoring/balances", - "monitoring/amount-arithmetic-inconsistency", - "monitoring/wire-format-inconsistency", - "monitoring/wire-out-inconsistency", - "monitoring/reserve-balance-summary-wrong-inconsistency", + { + endpoint: "misattribution-in-inconsistencies", + label: "Misattribution in inconsistencies", + }, + { endpoint: "coin-inconsistencies", label: "Coin inconsistencies" }, + { + endpoint: "reserve-in-inconsistencies", + label: "Reserve in inconsistencies", + }, + { endpoint: "bad-sig-losses", label: "Bad signature losses" }, + { endpoint: "balances", label: "Balances" }, + { + endpoint: "amount-arithmetic-inconsistencies", + label: "Amount arithmetic inconsistencies", + }, + { + endpoint: "wire-format-inconsistencies", + label: "Wire format inconsistencies", + }, + { endpoint: "wire-out-inconsistencies", label: "Wire out inconsistencies" }, + { + endpoint: "reserve-balance-summary-wrong-inconsistencies", + label: "Wrong reserve balance summaries", + }, ]; + const paths = endpoints.map(({ endpoint }) => `monitoring/${endpoint}`); const { data: list, error: listError } = useSWR< - HttpResponseOk<any>[], + HttpResponseOk<RecordsResponse<Record<string, unknown>>>[], RequestError<AuditorBackend.ErrorDetail> - >(endpoints, multiFetcher, { + >(paths, multiFetcher, { refreshInterval: 60000, refreshWhenHidden: false, revalidateOnFocus: false, @@ -57,7 +77,11 @@ export function getKeyFiguresData(): HttpResponse< if (listError) return listError.cause; if (list) { - return { ok: true, data: [list] }; + const summaries: MonitoringSummary[] = list.map((response, index) => ({ + ...endpoints[index], + records: response.data.records, + })); + return { ok: true, data: [summaries] }; } return { loading: true }; } diff --git a/packages/taler-auditor-webui/src/hooks/operational.ts b/packages/taler-auditor-webui/src/hooks/operational.ts @@ -13,7 +13,11 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AuditorBackend } from "../declaration.js"; +import { + AuditorBackend, + MonitoringSummary, + RecordsResponse, +} from "../declaration.js"; import { HttpResponse, HttpResponseOk, RequestError } from "../utils/http.js"; import { useBackendRequest } from "./backend.js"; @@ -37,25 +41,44 @@ export function getOperationData( ): HttpResponse<any, AuditorBackend.ErrorDetail> { const { multiFetcher } = useBackendRequest(); const endpoints = [ - "monitoring/row-inconsistency", - "monitoring/purse-not-closed-inconsistencies", - "monitoring/reserve-not-closed-inconsistency", - "monitoring/denominations-without-sigs", - "monitoring/deposit-confirmation", - "monitoring/denomination-key-validity-withdraw-inconsistency", - "monitoring/refreshes-hanging", - //TODO fix endpoint - // "monitoring/closure-lags", - // "monitoring/row-minor-inconsistencies", - // "monitoring/historic-denomination-revenue", - // "monitoring/denomination-pending", - "monitoring/historic-reserve-summary", + { endpoint: "row-inconsistencies", label: "Row inconsistencies" }, + { + endpoint: "purse-not-closed-inconsistencies", + label: "Purses not closed", + }, + { + endpoint: "reserve-not-closed-inconsistencies", + label: "Reserves not closed", + }, + { + endpoint: "denominations-without-sigs", + label: "Denominations without signatures", + }, + { endpoint: "deposit-confirmations", label: "Deposit confirmations" }, + { + endpoint: "denomination-key-validity-withdraw-inconsistencies", + label: "Invalid denomination keys at withdrawal", + }, + { endpoint: "closure-lags", label: "Closure lags" }, + { + endpoint: "row-minor-inconsistencies", + label: "Minor row inconsistencies", + }, + { + endpoint: "historic-denomination-revenues", + label: "Historic denomination revenues", + }, + { + endpoint: "historic-reserve-summaries", + label: "Historic reserve summaries", + }, ]; + const paths = endpoints.map(({ endpoint }) => `monitoring/${endpoint}`); const { data: list, error: listError } = useSWR< - HttpResponseOk<any>[], + HttpResponseOk<RecordsResponse<Record<string, unknown>>>[], RequestError<AuditorBackend.ErrorDetail> - >(endpoints, multiFetcher, { + >(paths, multiFetcher, { refreshInterval: 60000, refreshWhenHidden: false, revalidateOnFocus: false, @@ -66,7 +89,11 @@ export function getOperationData( if (listError) return listError.cause; if (list) { - return { ok: true, data: [list] }; + const summaries: MonitoringSummary[] = list.map((response, index) => ({ + ...endpoints[index], + records: response.data.records, + })); + return { ok: true, data: [summaries] }; } return { loading: true }; } diff --git a/packages/taler-auditor-webui/src/paths/default/Table.tsx b/packages/taler-auditor-webui/src/paths/default/Table.tsx @@ -37,7 +37,7 @@ export function CardTable({ onSuppress }: Props): any { undefined, ); const { i18n } = useTranslationContext(); - const { title, endpoint, entity } = useEntityContext(); + const { title } = useEntityContext(); return ( <div class="card has-table"> @@ -53,9 +53,8 @@ export function CardTable({ onSuppress }: Props): any { <div class="card-content"> <div class="b-table has-pagination"> <div class="table-wrapper has-mobile-cards"> - {data.data[0][endpoint] !== undefined && - data.data[0][endpoint].length != 0 ? ( - <Table data={data.data[0][endpoint]} onSuppress={onSuppress} /> + {data.data.length !== 0 ? ( + <Table data={data.data} onSuppress={onSuppress} /> ) : ( <EmptyTable /> )} diff --git a/packages/taler-auditor-webui/src/paths/details/ListPage.tsx b/packages/taler-auditor-webui/src/paths/details/ListPage.tsx @@ -144,18 +144,6 @@ export function ListPage(): VNode { <div class="card-body"> <button class="button is-fullwidth" - onClick={(e) => route(Paths.denomination_pending_list)} - > - Denominations pending - </button> - </div> - </div> - </div> - <div class="column"> - <div class="card"> - <div class="card-body"> - <button - class="button is-fullwidth" onClick={(e) => route(Paths.deposit_confirmation_list)} > Deposit confirmations @@ -250,18 +238,6 @@ export function ListPage(): VNode { <div class="card-body"> <button class="button is-fullwidth" - onClick={(e) => route(Paths.refresh_hanging_list)} - > - Refreshes hanging - </button> - </div> - </div> - </div> - <div class="column"> - <div class="card"> - <div class="card-body"> - <button - class="button is-fullwidth" onClick={(e) => route(Paths.reserve_balance_insufficient_inconsistency_list) } diff --git a/packages/taler-auditor-webui/src/paths/finance/ListPage.tsx b/packages/taler-auditor-webui/src/paths/finance/ListPage.tsx @@ -26,7 +26,9 @@ import { Fragment, h, VNode } from "preact"; export function ListPage(data: any): VNode { - let balances = data.data.data[0][4].data.balances; + const summaries = data.data.data[0]; + const balances = + summaries.find((x: any) => x.endpoint === "balances")?.records ?? []; let coinBalances = [ "Total recoup loss", "Coin refund fee revenue", @@ -70,26 +72,15 @@ export function ListPage(data: any): VNode { <b>Gain/Loss</b> </td> </tr> - {data["data"]["data"][0].map((x: any, idx: number) => { - const key = Object.keys(x.data)[0]; - let value = Object.values(x.data)[0]; - const paramName = - key[0].toUpperCase() + - key - .split("_") - .join(" ") - .split("-") - .join(" ") - .slice(1, key.length); - if (key == "balances") { + {summaries.map((x: any, idx: number) => { + const value = x.records.length; + if (x.endpoint === "balances") { //TODO fix - let gains = 0; - if (value == null) value = 0; - else value = Object.keys(value).length; + const gains = 0; return ( <tr key={idx} class="is-link"> - <td>{paramName}</td> + <td>{x.label}</td> <td class="has-text-right"> <p class={ @@ -113,7 +104,7 @@ export function ListPage(data: any): VNode { } else { return ( <tr key={idx} class="is-link"> - <td>{paramName}</td> + <td>{x.label}</td> <td class="has-text-right"> <p class={ diff --git a/packages/taler-auditor-webui/src/paths/operations/ListPage.tsx b/packages/taler-auditor-webui/src/paths/operations/ListPage.tsx @@ -46,22 +46,11 @@ export function ListPage(data: any): VNode { <b>Diagnostic</b> </td> </tr> - {data["data"]["data"][0].map((x: any, idx: number) => { - const key = Object.keys(x.data)[0]; - let value = Object.values(x.data)[0]; - console.log(value); - if (!!value) value = 0; - const paramName = - key[0].toUpperCase() + - key - .split("_") - .join(" ") - .split("-") - .join(" ") - .slice(1, key.length); + {data.data.data[0].map((x: any, idx: number) => { + const value = x.records.length; return ( <tr key={idx} class="is-link"> - <td>{paramName}</td> + <td>{x.label}</td> <td className="has-text-right"> <p class={value == 0 ? "text-success" : "text-danger"} diff --git a/packages/taler-auditor-webui/src/paths/security/ListPage.tsx b/packages/taler-auditor-webui/src/paths/security/ListPage.tsx @@ -20,12 +20,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; export function ListPage(data: any): VNode { - const { i18n } = useTranslationContext(); - return ( <Fragment> <div class="columns is-fullwidth"> @@ -43,21 +40,11 @@ export function ListPage(data: any): VNode { <b>Expiration dates</b> </td> </tr> - {data["data"]["data"][0].map((x: any, idx: number) => { - const key = Object.keys(x.data)[0]; - let value = Object.values(x.data)[0]; - if (value === undefined) value = 0; - const paramName = - key[0].toUpperCase() + - key - .split("_") - .join(" ") - .split("-") - .join(" ") - .slice(1, key.length); + {data.data.data[0].map((x: any, idx: number) => { + const value = x.records.length; return ( <tr key={idx} class="is-link"> - <td>{paramName}</td> + <td>{x.label}</td> <td class="has-text-right"> <p class={value !== 0 ? "text-success" : "text-danger"}