aboutsummaryrefslogtreecommitdiff
path: root/packages/auditor-backoffice-ui/src/hooks
diff options
context:
space:
mode:
authorNic Eigel <nic@eigel.ch>2024-06-24 01:50:38 +0200
committerNic Eigel <nic@eigel.ch>2024-06-24 01:50:38 +0200
commit0b90a34e7c7c5d9bcca9a2ebe74df9fdfafc6577 (patch)
tree6a1818c24f8fcad09d1756d96e171d32a1f9c0f7 /packages/auditor-backoffice-ui/src/hooks
parentbf94287904c08f2c62acbb5b52c2cc0d1fcb964b (diff)
downloadwallet-core-0b90a34e7c7c5d9bcca9a2ebe74df9fdfafc6577.tar.xz
real-time-auditor
Diffstat (limited to 'packages/auditor-backoffice-ui/src/hooks')
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/backend.ts618
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/critical.ts70
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/deposit_confirmations.ts161
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/entity.ts82
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/finance.ts61
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/index.ts176
-rw-r--r--packages/auditor-backoffice-ui/src/hooks/operational.ts83
7 files changed, 549 insertions, 702 deletions
diff --git a/packages/auditor-backoffice-ui/src/hooks/backend.ts b/packages/auditor-backoffice-ui/src/hooks/backend.ts
index 5ed06bf78..4b0a5a828 100644
--- a/packages/auditor-backoffice-ui/src/hooks/backend.ts
+++ b/packages/auditor-backoffice-ui/src/hooks/backend.ts
@@ -17,461 +17,245 @@
/**
*
* @author Sebastian Javier Marchano (sebasjm)
+ * @author Nic Eigel
*/
-import { AbsoluteTime, HttpStatusCode } from "@gnu-taler/taler-util";
import {
- ErrorType,
- HttpError,
- HttpResponse,
- HttpResponseOk,
- RequestError,
- RequestOptions,
- useApiContext,
+ HttpResponse,
+ HttpResponseOk,
+ RequestError,
+ RequestOptions,
+ useApiContext,
} from "@gnu-taler/web-util/browser";
-import { useCallback, useEffect, useState } from "preact/hooks";
-import { useSWRConfig } from "swr";
+import {useCallback, useEffect, useState} from "preact/hooks";
+import {useSWRConfig} from "swr";
import { useBackendContext } from "../context/backend.js";
-import { useInstanceContext } from "../context/instance.js";
-import { AccessToken, LoginToken, MerchantBackend, Timestamp } from "../declaration.js";
-
+import { AuditorBackend } from "../declaration.js";
export function useMatchMutate(): (
- re?: RegExp,
- value?: unknown,
+ re?: RegExp,
+ value?: unknown,
) => Promise<any> {
- const { cache, mutate } = useSWRConfig();
+ const {cache, mutate} = useSWRConfig();
- if (!(cache instanceof Map)) {
- throw new Error(
- "matchMutate requires the cache provider to be a Map instance",
- );
- }
-
- return function matchRegexMutate(re?: RegExp) {
- return mutate((key) => {
- // evict if no key or regex === all
- if (!key || !re) return true
- // match string
- if (typeof key === 'string' && re.test(key)) return true
- // record or object have the path at [0]
- if (typeof key === 'object' && re.test(key[0])) return true
- //key didn't match regex
- return false
- }, undefined, {
- revalidate: true,
- });
- };
+ if (!(cache instanceof Map)) {
+ throw new Error(
+ "matchMutate requires the cache provider to be a Map instance",
+ );
+ }
+
+ return function matchRegexMutate(re?: RegExp) {
+ return mutate((key) => {
+ // evict if no key or regex === all
+ if (!key || !re) return true
+ // match string
+ if (typeof key === 'string' && re.test(key)) return true
+ // record or object have the path at [0]
+ if (typeof key === 'object' && re.test(key[0])) return true
+ //key didn't match regex
+ return false
+ }, undefined, {
+ revalidate: true,
+ });
+ };
}
-export function useBackendInstancesTestForAdmin(): HttpResponse<
- MerchantBackend.Instances.InstancesResponse,
- MerchantBackend.ErrorDetail
+const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000;
+const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;
+
+export function useBackendConfig(): HttpResponse<
+ AuditorBackend.VersionResponse | undefined,
+ RequestError<AuditorBackend.ErrorDetail>
> {
- const { request } = useBackendBaseRequest();
+ const {request} = useBackendBaseRequest();
- type Type = MerchantBackend.Instances.InstancesResponse;
+ type Type = AuditorBackend.VersionResponse;
+ type State = { data: HttpResponse<Type, RequestError<AuditorBackend.ErrorDetail>>, timer: number }
+ const [result, setResult] = useState<State>({data: {loading: true}, timer: 0});
- const [result, setResult] = useState<
- HttpResponse<Type, MerchantBackend.ErrorDetail>
- >({ loading: true });
+ useEffect(() => {
+ if (result.timer) {
+ clearTimeout(result.timer);
+ }
- useEffect(() => {
- request<Type>(`/management/instances`)
- .then((data) => setResult(data))
- .catch((error: RequestError<MerchantBackend.ErrorDetail>) =>
- setResult(error.cause),
- );
- }, [request]);
+ function tryConfig(): void {
+ request<Type>(`/config`)
+ .then((data) => {
+ const timer: any = setTimeout(() => {
+ tryConfig();
+ }, CHECK_CONFIG_INTERVAL_OK);
+ setResult({data, timer});
+ })
+ .catch((error) => {
+ const timer: any = setTimeout(() => {
+ tryConfig();
+ }, CHECK_CONFIG_INTERVAL_FAIL);
+ const data = error.cause;
+ setResult({data, timer});
+ });
+ }
- return result;
-}
+ tryConfig();
+ }, [request]);
-const CHECK_CONFIG_INTERVAL_OK = 5 * 60 * 1000;
-const CHECK_CONFIG_INTERVAL_FAIL = 2 * 1000;
+ return result.data;
+}
-export function useBackendConfig(): HttpResponse<
- MerchantBackend.VersionResponse | undefined,
- RequestError<MerchantBackend.ErrorDetail>
+export function useBackendToken(): HttpResponse<
+ AuditorBackend.VersionResponse,
+ RequestError<AuditorBackend.ErrorDetail>
> {
- const { request } = useBackendBaseRequest();
+ const {request} = useBackendBaseRequest();
- type Type = MerchantBackend.VersionResponse;
- type State = { data: HttpResponse<Type, RequestError<MerchantBackend.ErrorDetail>>, timer: number }
- const [result, setResult] = useState<State>({ data: { loading: true }, timer: 0 });
+ type Type = AuditorBackend.VersionResponse;
+ type State = { data: HttpResponse<Type, RequestError<AuditorBackend.ErrorDetail>>, timer: number }
+ const [result, setResult] = useState<State>({data: {loading: true}, timer: 0});
- useEffect(() => {
- if (result.timer) {
- clearTimeout(result.timer)
- }
- function tryConfig(): void {
- request<Type>(`/config`)
- .then((data) => {
- const timer: any = setTimeout(() => {
- tryConfig()
- }, CHECK_CONFIG_INTERVAL_OK)
- setResult({ data, timer })
- })
- .catch((error) => {
- const timer: any = setTimeout(() => {
- tryConfig()
- }, CHECK_CONFIG_INTERVAL_FAIL)
- const data = error.cause
- setResult({ data, timer })
- });
- }
- tryConfig()
- }, [request]);
+ useEffect(() => {
+ if (result.timer) {
+ clearTimeout(result.timer);
+ }
+
+ function tryToken(): void {
+ request<Type>(`/monitoring/balances`)
+ .then((data) => {
+ const timer: any = setTimeout(() => {
+ tryToken();
+ }, CHECK_CONFIG_INTERVAL_OK);
+ setResult({data, timer});
+ })
+ .catch((error) => {
+ const timer: any = setTimeout(() => {
+ tryToken();
+ }, CHECK_CONFIG_INTERVAL_FAIL);
+ const data = error.cause;
+ setResult({data, timer});
+ });
+ }
+
+ tryToken();
+ }, [request]);
- return result.data;
+ return result.data;
}
interface useBackendInstanceRequestType {
- request: <T>(
- endpoint: string,
- options?: RequestOptions,
- ) => Promise<HttpResponseOk<T>>;
- fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
- reserveDetailFetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
- rewardsDetailFetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
- multiFetcher: <T>(params: [url: string[]]) => Promise<HttpResponseOk<T>[]>;
- orderFetcher: <T>(
- params: [endpoint: string,
- paid?: YesOrNo,
- refunded?: YesOrNo,
- wired?: YesOrNo,
- searchDate?: Date,
- delta?: number,]
- ) => Promise<HttpResponseOk<T>>;
- transferFetcher: <T>(
- params: [endpoint: string,
- payto_uri?: string,
- verified?: string,
- position?: string,
- delta?: number,]
- ) => Promise<HttpResponseOk<T>>;
- templateFetcher: <T>(
- params: [endpoint: string,
- position?: string,
- delta?: number]
- ) => Promise<HttpResponseOk<T>>;
- webhookFetcher: <T>(
- params: [endpoint: string,
- position?: string,
- delta?: number]
- ) => Promise<HttpResponseOk<T>>;
-}
-interface useBackendBaseRequestType {
- request: <T>(
- endpoint: string,
- options?: RequestOptions,
- ) => Promise<HttpResponseOk<T>>;
-}
-type YesOrNo = "yes" | "no";
-type LoginResult = {
- valid: true;
- token: string;
- expiration: Timestamp;
-} | {
- valid: false;
- cause: HttpError<{}>;
+ request: <T>(
+ endpoint: string,
+ options?: RequestOptions,
+ ) => Promise<HttpResponseOk<T>>;
+ fetcher: <T>(endpoint: string) => Promise<HttpResponseOk<T>>;
+ multiFetcher: <T>(params: string[]) => Promise<HttpResponseOk<T>[]>;
+ depositConfirmationFetcher: <T>(
+ params: [
+ endpoint: string,
+ ],
+ ) => Promise<HttpResponseOk<T>>;
}
-export function useCredentialsChecker() {
- const { request } = useApiContext();
- //check against instance details endpoint
- //while merchant backend doesn't have a login endpoint
- async function requestNewLoginToken(
- baseUrl: string,
- token: AccessToken,
- ): Promise<LoginResult> {
- const data: MerchantBackend.Instances.LoginTokenRequest = {
- scope: "write",
- duration: {
- d_us: "forever"
- },
- refreshable: true,
- }
- try {
- const response = await request<MerchantBackend.Instances.LoginTokenSuccessResponse>(baseUrl, `/private/token`, {
- method: "POST",
- token,
- data
- });
- return { valid: true, token: response.data.token, expiration: response.data.expiration };
- } catch (error) {
- if (error instanceof RequestError) {
- return { valid: false, cause: error.cause };
- }
-
- return {
- valid: false, cause: {
- type: ErrorType.UNEXPECTED,
- loading: false,
- info: {
- hasToken: true,
- status: 0,
- options: {},
- url: `/private/token`,
- payload: {}
- },
- exception: error,
- message: (error instanceof Error ? error.message : "unpexepected error")
- }
- };
- }
- };
-
- async function refreshLoginToken(
- baseUrl: string,
- token: LoginToken
- ): Promise<LoginResult> {
-
- if (AbsoluteTime.isExpired(AbsoluteTime.fromProtocolTimestamp(token.expiration))) {
- return {
- valid: false, cause: {
- type: ErrorType.CLIENT,
- status: HttpStatusCode.Unauthorized,
- message: "login token expired, login again.",
- info: {
- hasToken: true,
- status: 401,
- options: {},
- url: `/private/token`,
- payload: {}
- },
- payload: {}
- },
- }
- }
+interface useBackendBaseRequestType {
- return requestNewLoginToken(baseUrl, token.token as AccessToken)
- }
- return { requestNewLoginToken, refreshLoginToken }
+ request: <T>(
+ endpoint: string,
+ options?: RequestOptions
+ ) => Promise<HttpResponseOk<T>>;
}
+type YesOrNo = "yes" | "no";
+
/**
*
* @param root the request is intended to the base URL and no the instance URL
* @returns request handler to
*/
+//TODO: Add token
export function useBackendBaseRequest(): useBackendBaseRequestType {
- const { url: backend, token: loginToken } = useBackendContext();
- const { request: requestHandler } = useApiContext();
- const token = loginToken?.token;
-
- const request = useCallback(
- function requestImpl<T>(
- endpoint: string,
- options: RequestOptions = {},
- ): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(backend, endpoint, { ...options, token }).then(res => {
- return res
- }).catch(err => {
- throw err
- });
- },
- [backend, token],
- );
-
- return { request };
+ const {url: backend} = useBackendContext();
+ const {request: requestHandler} = useApiContext();
+ //const { token } = useBackendTokenContext();
+ const token = "D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0";
+
+
+ const request = useCallback(
+ function requestImpl<T>(
+ endpoint: string,
+ //todo: remove
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(backend, endpoint, {...options, token}).then(res => {
+ return res;
+ }).catch(err => {
+ throw err;
+ });
+ },
+ [backend],
+ );
+
+ return {request};
}
-export function useBackendInstanceRequest(): useBackendInstanceRequestType {
- const { url: rootBackendUrl, token: rootToken } = useBackendContext();
- const { token: instanceToken, id, admin } = useInstanceContext();
- const { request: requestHandler } = useApiContext();
-
- const { baseUrl, token: loginToken } = !admin
- ? { baseUrl: rootBackendUrl, token: rootToken }
- : { baseUrl: `${rootBackendUrl}/instances/${id}`, token: instanceToken };
-
- const token = loginToken?.token;
-
- const request = useCallback(
- function requestImpl<T>(
- endpoint: string,
- options: RequestOptions = {},
- ): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(baseUrl, endpoint, { token, ...options });
- },
- [baseUrl, token],
- );
-
- const multiFetcher = useCallback(
- function multiFetcherImpl<T>(
- args: [endpoints: string[]],
- ): Promise<HttpResponseOk<T>[]> {
- const [endpoints] = args
- return Promise.all(
- endpoints.map((endpoint) =>
- requestHandler<T>(baseUrl, endpoint, { token }),
- ),
- );
- },
- [baseUrl, token],
- );
-
- const fetcher = useCallback(
- function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(baseUrl, endpoint, { token });
- },
- [baseUrl, token],
- );
-
- const orderFetcher = useCallback(
- function orderFetcherImpl<T>(
- args: [endpoint: string,
- paid?: YesOrNo,
- refunded?: YesOrNo,
- wired?: YesOrNo,
- searchDate?: Date,
- delta?: number,]
- ): Promise<HttpResponseOk<T>> {
- const [endpoint, paid, refunded, wired, searchDate, delta] = args
- const date_s =
- delta && delta < 0 && searchDate
- ? Math.floor(searchDate.getTime() / 1000) + 1
- : searchDate !== undefined ? Math.floor(searchDate.getTime() / 1000) : undefined;
- const params: any = {};
- if (paid !== undefined) params.paid = paid;
- if (delta !== undefined) params.delta = delta;
- if (refunded !== undefined) params.refunded = refunded;
- if (wired !== undefined) params.wired = wired;
- if (date_s !== undefined) params.date_s = date_s;
- if (delta === 0) {
- //in this case we can already assume the response
- //and avoid network
- return Promise.resolve({
- ok: true,
- data: { orders: [] } as T,
- })
- }
- return requestHandler<T>(baseUrl, endpoint, { params, token });
- },
- [baseUrl, token],
- );
-
- const reserveDetailFetcher = useCallback(
- function reserveDetailFetcherImpl<T>(
- endpoint: string,
- ): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(baseUrl, endpoint, {
- params: {
- rewards: "yes",
+
+export function useBackendRequest(): useBackendInstanceRequestType {
+ const {url: rootBackendUrl} = useBackendContext();
+ // const {id} = useInstanceContext();
+ const {request: requestHandler} = useApiContext();
+
+ //TODO: check
+ const baseUrl = "http://localhost:8083/";
+ const token = "D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0";
+
+
+
+
+ const request = useCallback(
+ function requestImpl<T>(
+ endpoint: string,
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, {...options, token});
},
- token,
- });
- },
- [baseUrl, token],
- );
-
- const rewardsDetailFetcher = useCallback(
- function rewardsDetailFetcherImpl<T>(
- endpoint: string,
- ): Promise<HttpResponseOk<T>> {
- return requestHandler<T>(baseUrl, endpoint, {
- params: {
- pickups: "yes",
+ [baseUrl],
+ );
+
+ const multiFetcher = useCallback(
+ function multiFetcherImpl<T>(
+ params: string[],
+ options: RequestOptions = {},
+ ): Promise<HttpResponseOk<T>[]> {
+ return Promise.all(
+ params.map((endpoint) =>
+ requestHandler<T>(baseUrl, endpoint, {...options, token}),
+ ),
+ );
},
- token,
- });
- },
- [baseUrl, token],
- );
-
- const transferFetcher = useCallback(
- function transferFetcherImpl<T>(
- args: [endpoint: string,
- payto_uri?: string,
- verified?: string,
- position?: string,
- delta?: number,]
- ): Promise<HttpResponseOk<T>> {
- const [endpoint, payto_uri, verified, position, delta] = args
- const params: any = {};
- if (payto_uri !== undefined) params.payto_uri = payto_uri;
- if (verified !== undefined) params.verified = verified;
- if (delta === 0) {
- //in this case we can already assume the response
- //and avoid network
- return Promise.resolve({
- ok: true,
- data: { transfers: [] } as T,
- })
- }
- if (delta !== undefined) {
- params.limit = delta;
- }
- if (position !== undefined) params.offset = position;
-
- return requestHandler<T>(baseUrl, endpoint, { params, token });
- },
- [baseUrl, token],
- );
-
- const templateFetcher = useCallback(
- function templateFetcherImpl<T>(
- args: [endpoint: string,
- position?: string,
- delta?: number,]
- ): Promise<HttpResponseOk<T>> {
- const [endpoint, position, delta] = args
- const params: any = {};
- if (delta === 0) {
- //in this case we can already assume the response
- //and avoid network
- return Promise.resolve({
- ok: true,
- data: { templates: [] } as T,
- })
- }
- if (delta !== undefined) {
- params.limit = delta;
- }
- if (position !== undefined) params.offset = position;
-
- return requestHandler<T>(baseUrl, endpoint, { params, token });
- },
- [baseUrl, token],
- );
-
- const webhookFetcher = useCallback(
- function webhookFetcherImpl<T>(
- args: [endpoint: string,
- position?: string,
- delta?: number,]
- ): Promise<HttpResponseOk<T>> {
- const [endpoint, position, delta] = args
- const params: any = {};
- if (delta === 0) {
- //in this case we can already assume the response
- //and avoid network
- return Promise.resolve({
- ok: true,
- data: { webhooks: [] } as T,
- })
- }
- if (delta !== undefined) {
- params.limit = delta;
- }
- if (position !== undefined) params.offset = position;
-
- return requestHandler<T>(baseUrl, endpoint, { params, token });
- },
- [baseUrl, token],
- );
-
- return {
- request,
- fetcher,
- multiFetcher,
- orderFetcher,
- reserveDetailFetcher,
- rewardsDetailFetcher,
- transferFetcher,
- templateFetcher,
- webhookFetcher,
- };
-}
+ [baseUrl],
+ );
+
+ const fetcher = useCallback(
+ function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
+ return requestHandler<T>(baseUrl, endpoint, {token});
+ },
+ [baseUrl],
+ );
+
+ const depositConfirmationFetcher = useCallback(
+ function orderFetcherImpl<T>(
+ args: [endpoint: string,
+ ],
+ ): Promise<HttpResponseOk<T>> {
+ const [endpoint] = args;
+ const params: any = {"token": "D4CST1Z6AHN3RT03M0T9NSTF2QGHTB5ZD2D3RYZB4HAWG8SX0JEFWBXCKXZHMB7Y3Z7KVFW0B3XPXD5BHCFP8EB0R6CNH2KAWDWVET0"};
+ return requestHandler<T>(baseUrl, endpoint, {params, token});
+ },
+ [baseUrl],
+ );
+
+
+ return {
+ request,
+ fetcher,
+ depositConfirmationFetcher,
+ multiFetcher
+ };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/critical.ts b/packages/auditor-backoffice-ui/src/hooks/critical.ts
new file mode 100644
index 000000000..6a25d3037
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/critical.ts
@@ -0,0 +1,70 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ 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 {
+ HttpResponse,
+ HttpResponseOk,
+ HttpResponsePaginated,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { useEffect, useState } from "preact/hooks";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js";
+import { useBackendRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+export interface HelperDashboardFilter {
+ finance?: YesOrNo;
+ security?: YesOrNo;
+ operating?: YesOrNo;
+ detail?: YesOrNo;
+}
+
+export function getCriticalData(
+ args?: HelperDashboardFilter,
+ updateFilter?: (d: Date) => void,
+): HttpResponse<any, AuditorBackend.ErrorDetail> {
+ const { multiFetcher } = useBackendRequest();
+ const endpoints = [
+ "monitoring/fee-time-inconsistency",
+ "monitoring/emergency",
+ "monitoring/emergency-by-count",
+ "monitoring/reserve-balance-insufficient-inconsistency",
+ ];
+
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<any>[], RequestError<AuditorBackend.ErrorDetail>
+ >(endpoints, multiFetcher, {
+ refreshInterval: 60,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list) {
+ return { ok: true, data: [list] };
+ }
+ return { loading: true };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/deposit_confirmations.ts b/packages/auditor-backoffice-ui/src/hooks/deposit_confirmations.ts
deleted file mode 100644
index e4ec9a2f2..000000000
--- a/packages/auditor-backoffice-ui/src/hooks/deposit_confirmations.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021-2024 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- 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 {
- HttpResponse,
- HttpResponseOk,
- RequestError,
-} from "@gnu-taler/web-util/browser";
-import { AuditorBackend, MerchantBackend, WithId } from "../declaration.js";
-import { useBackendInstanceRequest, useMatchMutate } from "./backend.js";
-
-// FIX default import https://github.com/microsoft/TypeScript/issues/49189
-import _useSWR, { SWRHook, useSWRConfig } from "swr";
-const useSWR = _useSWR as unknown as SWRHook;
-
-export interface DepositConfirmationAPI {
- getDepositConfirmation: (
- id: string,
- ) => Promise<void>;
- createDepositConfirmation: (
- data: MerchantBackend.Products.ProductAddDetail,
- ) => Promise<void>;
- updateDepositConfirmation: (
- id: string,
- data: MerchantBackend.Products.ProductPatchDetail,
- ) => Promise<void>;
- deleteDepositConfirmation: (id: string) => Promise<void>;
-}
-
-export function useDepositConfirmationAPI(): DepositConfirmationAPI {
- const mutateAll = useMatchMutate();
- const { mutate } = useSWRConfig();
-
- const { request } = useBackendInstanceRequest();
-
- const createDepositConfirmation = async (
- data: MerchantBackend.Products.ProductAddDetail,
- ): Promise<void> => {
- const res = await request(`/private/products`, {
- method: "POST",
- data,
- });
-
- return await mutateAll(/.*\/private\/products.*/);
- };
-
- const updateDepositConfirmation = async (
- productId: string,
- data: MerchantBackend.Products.ProductPatchDetail,
- ): Promise<void> => {
- const r = await request(`/private/products/${productId}`, {
- method: "PATCH",
- data,
- });
-
- return await mutateAll(/.*\/private\/products.*/);
- };
-
- const deleteDepositConfirmation = async (productId: string): Promise<void> => {
- await request(`/private/products/${productId}`, {
- method: "DELETE",
- });
- await mutate([`/private/products`]);
- };
-
- const getDepositConfirmation = async (
- serialId: string,
- ): Promise<void> => {
- await request(`/deposit-confirmation/${serialId}`, {
- method: "GET",
- });
-
- return
- };
-
- return {createDepositConfirmation, updateDepositConfirmation, deleteDepositConfirmation, getDepositConfirmation};
-}
-
-export function useDepositConfirmation(): HttpResponse<
- (AuditorBackend.DepositConfirmation.DepositConfirmationDetail & WithId)[],
- AuditorBackend.ErrorDetail
-> {
- const { fetcher, multiFetcher } = useBackendInstanceRequest();
-
- const { data: list, error: listError } = useSWR<
- HttpResponseOk<AuditorBackend.DepositConfirmation.DepositConfirmationList>,
- RequestError<AuditorBackend.ErrorDetail>
- >([`/deposit-confirmation`], fetcher, {
- refreshInterval: 0,
- refreshWhenHidden: false,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- refreshWhenOffline: false,
- });
-
- const paths = (list?.data.depositConfirmations || []).map(
- (p) => `/deposit-confirmation/${p.serial_id}`,
- );
- const { data: depositConfirmations, error: depositConfirmationError } = useSWR<
- HttpResponseOk<AuditorBackend.DepositConfirmation.DepositConfirmationDetail>[],
- RequestError<AuditorBackend.ErrorDetail>
- >([paths], multiFetcher, {
- refreshInterval: 0,
- refreshWhenHidden: false,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- refreshWhenOffline: false,
- });
-
- if (listError) return listError.cause;
- if (depositConfirmationError) return depositConfirmationError.cause;
-
- if (depositConfirmations) {
- const dataWithId = depositConfirmations.map((d) => {
- //take the id from the queried url
- return {
- ...d.data,
- id: d.info?.url.replace(/.*\/deposit-confirmation\//, "") || "",
- };
- });
- return { ok: true, data: dataWithId };
- }
- return { loading: true };
-}
-
-export function useDepositConfirmationDetails(
- serialId: string,
-): HttpResponse<
- AuditorBackend.DepositConfirmation.DepositConfirmationDetail,
- AuditorBackend.ErrorDetail
-> {
- const { fetcher } = useBackendInstanceRequest();
-
- const { data, error, isValidating } = useSWR<
- HttpResponseOk<AuditorBackend.DepositConfirmation.DepositConfirmationDetail>,
- RequestError<AuditorBackend.ErrorDetail>
- >([`/deposit-confirmation/${serialId}`], fetcher, {
- refreshInterval: 0,
- refreshWhenHidden: false,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
- refreshWhenOffline: false,
- });
-
- if (isValidating) return { loading: true, data: data?.data };
- if (data) return data;
- if (error) return error.cause;
- return { loading: true };
-}
diff --git a/packages/auditor-backoffice-ui/src/hooks/entity.ts b/packages/auditor-backoffice-ui/src/hooks/entity.ts
new file mode 100644
index 000000000..ae62da35e
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/entity.ts
@@ -0,0 +1,82 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ 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 {
+ HttpResponse,
+ HttpResponseOk,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { useBackendRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+import { useEntityContext } from "../context/entity.js";
+
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+interface Props {
+ endpoint: string;
+ entity: any;
+}
+
+export function getEntityList({ endpoint, entity }: Props): HttpResponse<any, AuditorBackend.ErrorDetail> {
+ const { fetcher } = useBackendRequest();
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<typeof entity>,
+ RequestError<AuditorBackend.ErrorDetail>
+ >([`monitoring/` + endpoint], fetcher, {
+ refreshInterval: 0,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list?.data != null) {
+ return { ok: true, data: [list?.data] };
+ }
+ return { loading: true };
+}
+export interface EntityAPI {
+ updateEntity: (
+ id: string
+ ) => Promise<void>;
+}
+
+export function useEntityAPI(): EntityAPI {
+ const mutateAll = useMatchMutate();
+ const { request } = useBackendRequest();
+ const { endpoint } = useEntityContext();
+ const data = {"suppressed": true};
+
+ const updateEntity = async (
+ id: string,
+ ): Promise<void> => {
+ const r = await request(`monitoring/${endpoint}/${id}`, {
+ method: "PATCH",
+ data,
+ });
+
+ return await mutateAll(/.*\/monitoring.*/);
+ };
+
+ return { updateEntity };
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/finance.ts b/packages/auditor-backoffice-ui/src/hooks/finance.ts
new file mode 100644
index 000000000..97bf2577f
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/finance.ts
@@ -0,0 +1,61 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ 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 {
+ HttpResponse,
+ HttpResponseOk,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { useBackendRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+
+const useSWR = _useSWR as unknown as SWRHook;
+
+
+export function getKeyFiguresData(): HttpResponse<any, AuditorBackend.ErrorDetail> {
+ 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",
+
+ ];
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<any>[], RequestError<AuditorBackend.ErrorDetail>
+ >(endpoints, multiFetcher, {
+ refreshInterval: 60,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list) {
+ return { ok: true, data: [list] };
+ }
+ return { loading: true };
+}
diff --git a/packages/auditor-backoffice-ui/src/hooks/index.ts b/packages/auditor-backoffice-ui/src/hooks/index.ts
index 61afbc94a..cf1c57771 100644
--- a/packages/auditor-backoffice-ui/src/hooks/index.ts
+++ b/packages/auditor-backoffice-ui/src/hooks/index.ts
@@ -14,138 +14,66 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-/**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { buildCodecForObject, codecForMap, codecForString, codecForTimestamp } from "@gnu-taler/taler-util";
-import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
-import { StateUpdater, useEffect, useState } from "preact/hooks";
-import { LoginToken } from "../declaration.js";
+import {StateUpdater, useState} from "preact/hooks";
import { ValueOrFunction } from "../utils/types.js";
-import { useMatchMutate } from "./backend.js";
-
-const calculateRootPath = () => {
- const rootPath =
- typeof window !== undefined
- ? window.location.origin + window.location.pathname
- : "/";
-
- /**
- * By default, merchant backend serves the html content
- * from the /webui root. This should cover most of the
- * cases and the rootPath will be the merchant backend
- * URL where the instances are
- */
- return rootPath.replace("/webui/", "");
-};
-
-const loginTokenCodec = buildCodecForObject<LoginToken>()
- .property("token", codecForString())
- .property("expiration", codecForTimestamp)
- .build("loginToken")
-const TOKENS_KEY = buildStorageKey("auditor-token", codecForMap(loginTokenCodec));
-
export function useBackendURL(
- url?: string,
+ url?: string,
): [string, StateUpdater<string>] {
- const [value, setter] = useSimpleLocalStorage(
- "auditor-base-url",
- url || calculateRootPath(),
- );
-
- const checkedSetter = (v: ValueOrFunction<string>) => {
- return setter((p) => (v instanceof Function ? v(p ?? "") : v).replace(/\/$/, ""));
- };
-
- return [value!, checkedSetter];
-}
+ const [value, setter] = useSimpleLocalStorage(
+ "auditor-base-url",
+ url || calculateRootPath(),
+ );
-export function useBackendDefaultToken(
-): [LoginToken | undefined, ((d: LoginToken | undefined) => void)] {
- const { update: setToken, value: tokenMap, reset } = useLocalStorage(TOKENS_KEY, {})
+ const checkedSetter = (v: ValueOrFunction<string>) => {
+ return setter((p) => (v instanceof Function ? v(p ?? "") : v).replace(/\/$/, ""));
+ };
- const tokenOfDefaultInstance = tokenMap["default"]
- const clearCache = useMatchMutate()
- useEffect(() => {
- clearCache()
- }, [tokenOfDefaultInstance])
-
- function updateToken(
- value: (LoginToken | undefined)
- ): void {
- if (value === undefined) {
- reset()
- } else {
- const res = { ...tokenMap, "default": value }
- setToken(res)
- }
- }
- return [tokenMap["default"], updateToken];
-}
-
-export function useBackendInstanceToken(
- id: string,
-): [LoginToken | undefined, ((d: LoginToken | undefined) => void)] {
- const { update: setToken, value: tokenMap, reset } = useLocalStorage(TOKENS_KEY, {})
- const [defaultToken, defaultSetToken] = useBackendDefaultToken();
-
- // instance named 'default' use the default token
- if (id === "default") {
- return [defaultToken, defaultSetToken];
- }
- function updateToken(
- value: (LoginToken | undefined)
- ): void {
- if (value === undefined) {
- reset()
- } else {
- const res = { ...tokenMap, [id]: value }
- setToken(res)
- }
- }
-
- return [tokenMap[id], updateToken];
+ return [value!, checkedSetter];
}
-export function useLang(initial?: string): [string, StateUpdater<string>] {
- const browserLang =
- typeof window !== "undefined"
- ? navigator.language || (navigator as any).userLanguage
- : undefined;
- const defaultLang = (browserLang || initial || "en").substring(0, 2);
- return useSimpleLocalStorage("lang-preference", defaultLang) as [string, StateUpdater<string>];
-}
+const calculateRootPath = () => {
+ const rootPath =
+ typeof window !== undefined
+ ? window.location.origin + window.location.pathname
+ : "/";
+
+ /**
+ * By default, auditor backend serves the html content
+ * from the /webui root. This should cover most of the
+ * cases and the rootPath will be the auditor backend
+ * URL where the instances are
+ */
+ return rootPath.replace("/webui/", "");
+};
export function useSimpleLocalStorage(
- key: string,
- initialValue?: string,
+ key: string,
+ initialValue?: string,
): [string | undefined, StateUpdater<string | undefined>] {
- const [storedValue, setStoredValue] = useState<string | undefined>(
- (): string | undefined => {
- return typeof window !== "undefined"
- ? window.localStorage.getItem(key) || initialValue
- : initialValue;
- },
- );
-
- const setValue = (
- value?: string | ((val?: string) => string | undefined),
- ) => {
- setStoredValue((p) => {
- const toStore = value instanceof Function ? value(p) : value;
- if (typeof window !== "undefined") {
- if (!toStore) {
- window.localStorage.removeItem(key);
- } else {
- window.localStorage.setItem(key, toStore);
- }
- }
- return toStore;
- });
- };
-
- return [storedValue, setValue];
-}
+ const [storedValue, setStoredValue] = useState<string | undefined>(
+ (): string | undefined => {
+ return typeof window !== "undefined"
+ ? window.localStorage.getItem(key) || initialValue
+ : initialValue;
+ },
+ );
+
+ const setValue = (
+ value?: string | ((val?: string) => string | undefined),
+ ) => {
+ setStoredValue((p) => {
+ const toStore = value instanceof Function ? value(p) : value;
+ if (typeof window !== "undefined") {
+ if (!toStore) {
+ window.localStorage.removeItem(key);
+ } else {
+ window.localStorage.setItem(key, toStore);
+ }
+ }
+ return toStore;
+ });
+ };
+
+ return [storedValue, setValue];
+} \ No newline at end of file
diff --git a/packages/auditor-backoffice-ui/src/hooks/operational.ts b/packages/auditor-backoffice-ui/src/hooks/operational.ts
new file mode 100644
index 000000000..89524f24e
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/hooks/operational.ts
@@ -0,0 +1,83 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ 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 {
+ HttpResponse,
+ HttpResponseOk,
+ RequestError,
+} from "@gnu-taler/web-util/browser";
+import { AuditorBackend, WithId } from "../declaration.js";
+import { useBackendRequest, useMatchMutate } from "./backend.js";
+
+// FIX default import https://github.com/microsoft/TypeScript/issues/49189
+import _useSWR, { SWRHook, useSWRConfig } from "swr";
+
+const useSWR = _useSWR as unknown as SWRHook;
+
+type YesOrNo = "yes" | "no";
+
+export interface HelperDashboardFilter {
+ finance?: YesOrNo;
+ security?: YesOrNo;
+ operating?: YesOrNo;
+ detail?: YesOrNo;
+}
+
+export function getOperationData(
+ args?: HelperDashboardFilter,
+ updateFilter?: (d: Date) => void,
+): 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",
+
+ ];
+
+
+ const { data: list, error: listError } = useSWR<
+ HttpResponseOk<any>[], RequestError<AuditorBackend.ErrorDetail>
+ >(endpoints, multiFetcher, {
+ refreshInterval: 60,
+ refreshWhenHidden: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ refreshWhenOffline: false,
+ });
+
+ if (listError) return listError.cause;
+
+ if (list) {
+ return { ok: true, data: [list] };
+ }
+ return { loading: true };
+}
+
+export interface EntityAPI {
+ updateEntity: (
+ id: string,
+ ) => Promise<void>;
+} \ No newline at end of file