diff options
author | Nic Eigel <nic@eigel.ch> | 2024-06-24 01:50:38 +0200 |
---|---|---|
committer | Nic Eigel <nic@eigel.ch> | 2024-06-24 01:50:38 +0200 |
commit | 0b90a34e7c7c5d9bcca9a2ebe74df9fdfafc6577 (patch) | |
tree | 6a1818c24f8fcad09d1756d96e171d32a1f9c0f7 /packages/auditor-backoffice-ui/src | |
parent | bf94287904c08f2c62acbb5b52c2cc0d1fcb964b (diff) | |
download | wallet-core-0b90a34e7c7c5d9bcca9a2ebe74df9fdfafc6577.tar.xz |
real-time-auditor
Diffstat (limited to 'packages/auditor-backoffice-ui/src')
46 files changed, 2608 insertions, 3381 deletions
diff --git a/packages/auditor-backoffice-ui/src/Application.tsx b/packages/auditor-backoffice-ui/src/Application.tsx index a1a05fc97..3b6aa8dd3 100644 --- a/packages/auditor-backoffice-ui/src/Application.tsx +++ b/packages/auditor-backoffice-ui/src/Application.tsx @@ -17,149 +17,150 @@ /** * * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ -import { HttpStatusCode, LibtoolVersion } from "@gnu-taler/taler-util"; +import {HttpStatusCode, LibtoolVersion} from "@gnu-taler/taler-util"; import { - ErrorType, - TranslationProvider, - useTranslationContext, + ErrorType, + TranslationProvider, + useTranslationContext } from "@gnu-taler/web-util/browser"; -import { Fragment, VNode, h } from "preact"; -import { useMemo } from "preact/hooks"; -import { ApplicationReadyRoutes } from "./ApplicationReadyRoutes.js"; -import { Loading } from "./components/exception/loading.js"; +import {Fragment, VNode, h, render} from "preact"; +import {useMemo} from "preact/hooks"; +import {ApplicationReadyRoutes} from "./ApplicationReadyRoutes.js"; +import {Loading} from "./components/exception/loading.js"; import { - NotConnectedAppMenu, - NotificationCard + NotConnectedAppMenu, + NotificationCard } from "./components/menu/index.js"; import { - BackendContextProvider + BackendContextProvider } from "./context/backend.js"; -import { ConfigContextProvider } from "./context/config.js"; -import { useBackendConfig } from "./hooks/backend.js"; +import {ConfigContextProvider} from "./context/config.js"; +import {useBackendConfig} from "./hooks/backend.js"; import { strings } from "./i18n/strings.js"; export function Application(): VNode { - return ( - <BackendContextProvider> - <TranslationProvider source={strings}> - <ApplicationStatusRoutes /> - </TranslationProvider> - </BackendContextProvider> - ); + return ( + <BackendContextProvider> + <TranslationProvider source={strings}> + <ApplicationStatusRoutes/> + </TranslationProvider> + </BackendContextProvider> + ); } /** * Check connection testing against /config - * - * @returns + * + * @returns */ function ApplicationStatusRoutes(): VNode { - const result = useBackendConfig(); - const { i18n } = useTranslationContext(); + const result = useBackendConfig(); + const {i18n} = useTranslationContext(); - const { currency, version } = result.ok && result.data - ? result.data - : { currency: "unknown", version: "unknown" }; - const ctx = useMemo(() => ({ currency, version }), [currency, version]); + const configData = result.ok && result.data + ? result.data + : undefined; + const ctx = useMemo(() => (configData), [configData]); - if (!result.ok) { - if (result.loading) return <Loading />; - if ( - result.type === ErrorType.CLIENT && - result.status === HttpStatusCode.Unauthorized - ) { - return ( - <Fragment> - <NotConnectedAppMenu title="Login" /> - <NotificationCard - notification={{ - message: i18n.str`Checking the /config endpoint got authorization error`, - type: "ERROR", - description: `The /config endpoint of the backend server should be accessible`, - }} - /> - </Fragment> - ); + if (!result.ok) { + if (result.loading) return <Loading/>; + if ( + result.type === ErrorType.CLIENT && + result.status === HttpStatusCode.Unauthorized + ) { + return ( + <Fragment> + <NotConnectedAppMenu title="Login"/> + <NotificationCard + notification={{ + message: i18n.str`Checking the /config endpoint got authorization error`, + type: "ERROR", + description: `The /config endpoint of the backend server should be accessible`, + }} + /> + </Fragment> + ); + } + if ( + result.type === ErrorType.CLIENT && + result.status === HttpStatusCode.NotFound + ) { + return ( + <Fragment> + <NotConnectedAppMenu title="Error"/> + <NotificationCard + notification={{ + message: i18n.str`Could not find /config endpoint on this URL`, + type: "ERROR", + description: `Check the URL or contact the system administrator.`, + }} + /> + </Fragment> + ); + } + if (result.type === ErrorType.SERVER) { + <Fragment> + <NotConnectedAppMenu title="Error"/> + <NotificationCard + notification={{ + message: i18n.str`Server response with an error code`, + type: "ERROR", + description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, + }} + /> + </Fragment>; + } + if (result.type === ErrorType.UNREADABLE) { + <Fragment> + <NotConnectedAppMenu title="Error"/> + <NotificationCard + notification={{ + message: i18n.str`Response from server is unreadable, http status: ${result.status}`, + type: "ERROR", + description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, + }} + /> + </Fragment>; + } + return ( + <Fragment> + <NotConnectedAppMenu title="Error"/> + <NotificationCard + notification={{ + message: i18n.str`Unexpected Error`, + type: "ERROR", + description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, + }} + /> + </Fragment> + ); } - if ( - result.type === ErrorType.CLIENT && - result.status === HttpStatusCode.NotFound - ) { - return ( - <Fragment> - <NotConnectedAppMenu title="Error" /> - <NotificationCard - notification={{ - message: i18n.str`Could not find /config endpoint on this URL`, - type: "ERROR", - description: `Check the URL or contact the system administrator.`, - }} - /> + + const SUPPORTED_VERSION = "1:0:1" + if (result.data && !LibtoolVersion.compare( + SUPPORTED_VERSION, + result.data.version, + )?.compatible) { + return <Fragment> + <NotConnectedAppMenu title="Error"/> + <NotificationCard + notification={{ + message: i18n.str`Incompatible version`, + type: "ERROR", + description: i18n.str`Auditor backend server version ${result.data.version} is not compatible with the supported version ${SUPPORTED_VERSION}`, + }} + /> </Fragment> - ); - } - if (result.type === ErrorType.SERVER) { - <Fragment> - <NotConnectedAppMenu title="Error" /> - <NotificationCard - notification={{ - message: i18n.str`Server response with an error code`, - type: "ERROR", - description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, - }} - /> - </Fragment>; - } - if (result.type === ErrorType.UNREADABLE) { - <Fragment> - <NotConnectedAppMenu title="Error" /> - <NotificationCard - notification={{ - message: i18n.str`Response from server is unreadable, http status: ${result.status}`, - type: "ERROR", - description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, - }} - /> - </Fragment>; } + return ( - <Fragment> - <NotConnectedAppMenu title="Error" /> - <NotificationCard - notification={{ - message: i18n.str`Unexpected Error`, - type: "ERROR", - description: i18n.str`Got message "${result.message}" from ${result.info?.url}`, - }} - /> - </Fragment> + <div class="has-navbar-fixed-top"> + <ConfigContextProvider value={ctx!}> + <ApplicationReadyRoutes/> + </ConfigContextProvider> + </div> ); - } - - const SUPPORTED_VERSION = "18:0:1" - if (result.data && !LibtoolVersion.compare( - SUPPORTED_VERSION, - result.data.version, - )?.compatible) { - return <Fragment> - <NotConnectedAppMenu title="Error" /> - <NotificationCard - notification={{ - message: i18n.str`Incompatible version`, - type: "ERROR", - description: i18n.str`Merchant backend server version ${result.data.version} is not compatible with the supported version ${SUPPORTED_VERSION}`, - }} - /> - </Fragment> - } - - return ( - <div class="has-navbar-fixed-top"> - <ConfigContextProvider value={ctx}> - <ApplicationReadyRoutes /> - </ConfigContextProvider> - </div> - ); -} +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx index 414eee39d..576792d6f 100644 --- a/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx +++ b/packages/auditor-backoffice-ui/src/ApplicationReadyRoutes.tsx @@ -17,159 +17,73 @@ /** * * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ -import { HttpStatusCode } from "@gnu-taler/taler-util"; -import { ErrorType, useTranslationContext } from "@gnu-taler/web-util/browser"; -import { createHashHistory } from "history"; -import { Fragment, VNode, h } from "preact"; -import { Route, Router, route } from "preact-router"; -import { useState } from "preact/hooks"; -import { InstanceRoutes } from "./InstanceRoutes.js"; +import {ErrorType, useTranslationContext} from "@gnu-taler/web-util/browser"; +import {createHashHistory} from "history"; +import {Fragment, VNode, h} from "preact"; +import {Route, Router, route} from "preact-router"; +import {useEffect, useErrorBoundary, useState} from "preact/hooks"; +import {InstanceRoutes} from "./InstanceRoutes.js"; import { - NotConnectedAppMenu, - NotYetReadyAppMenu, - NotificationCard, + NotConnectedAppMenu, + NotYetReadyAppMenu, + NotificationCard, } from "./components/menu/index.js"; -import { useBackendContext } from "./context/backend.js"; -import { LoginToken } from "./declaration.js"; -import { useBackendInstancesTestForAdmin } from "./hooks/backend.js"; +import { useBackendContext, useBackendTokenContext } from "./context/backend.js"; +import {Settings} from "./paths/settings/index.js"; +import { useBackendConfig, useBackendToken } from "./hooks/backend.js"; +import { Loading } from "./components/exception/loading.js"; import { LoginPage } from "./paths/login/index.js"; -import { Settings } from "./paths/settings/index.js"; -import { INSTANCE_ID_LOOKUP } from "./utils/constants.js"; /** * Check if admin against /management/instances - * @returns + * @returns */ export function ApplicationReadyRoutes(): VNode { - const { i18n } = useTranslationContext(); - const [unauthorized, setUnauthorized] = useState(false) - const { - url: backendURL, - updateToken, - alreadyTriedLogin, - } = useBackendContext(); - - function updateLoginStatus(token: LoginToken | undefined) { - updateToken(token) - setUnauthorized(false) - } - - const result = useBackendInstancesTestForAdmin(); - - const clearTokenAndGoToRoot = () => { - route("/"); - }; - const [showSettings, setShowSettings] = useState(false) - const unauthorizedAdmin = !result.loading - && !result.ok - && result.type === ErrorType.CLIENT - && result.status === HttpStatusCode.Unauthorized; - - if (!alreadyTriedLogin && !result.ok) { - return ( - <Fragment> - <NotConnectedAppMenu title="Welcome!" /> - <LoginPage onConfirm={updateToken} /> - </Fragment> - ); - } - - if (showSettings) { - return <Fragment> - <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> - <Settings onClose={() => setShowSettings(false)} /> - </Fragment> - } - - if (result.loading) { - return <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Loading..." isPasswordOk={false} />; - } - - let admin = result.ok || unauthorizedAdmin; - let instanceNameByBackendURL: string | undefined; + const {i18n} = useTranslationContext(); + const [unauthorized, setUnauthorized] = useState(false) + const [backendToken, setToken] = useState(false) + const { url: backendURL} = useBackendContext(); + const { token } = useBackendTokenContext(); + + //TODO FIX bearer + const result = useBackendToken(); + if (result.loading) return <Loading/>; + if (!result.ok) { + return ( + <LoginPage /> + ); + } + const [showSettings, setShowSettings] = useState(false) - if (!admin) { - // * the testing against admin endpoint failed and it's not - // an authorization problem - // * merchant backend will return this SPA under the main - // endpoint or /instance/<id> endpoint - // => trying to infer the instance id - const path = new URL(backendURL).pathname; - const match = INSTANCE_ID_LOOKUP.exec(path); - if (!match || !match[1]) { - // this should be rare because - // query to /config is ok but the URL - // does not match our pattern - return ( - <Fragment> - <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Error" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> - <NotificationCard - notification={{ - message: i18n.str`Couldn't access the server.`, - description: i18n.str`Could not infer instance id from url ${backendURL}`, - type: "ERROR", - }} - /> - {/* <ConnectionPage onConfirm={changeBackend} /> */} + if (showSettings) { + return <Fragment> + <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="UI Settings"/> + <Settings onClose={() => setShowSettings(false)}/> </Fragment> - ); } - instanceNameByBackendURL = match[1]; - } - - if (unauthorized || unauthorizedAdmin) { - return <Fragment> - <NotYetReadyAppMenu onShowSettings={() => setShowSettings(true)} title="Login" onLogout={clearTokenAndGoToRoot} isPasswordOk={false} /> - <NotificationCard - notification={{ - message: i18n.str`Access denied`, - description: i18n.str`Check your token is valid`, - type: "ERROR", - }} - /> - <LoginPage onConfirm={updateLoginStatus} /> - </Fragment> - } - - const history = createHashHistory(); - return ( - <Router history={history}> - <Route - default - component={DefaultMainRoute} - admin={admin} - onUnauthorized={() => setUnauthorized(true)} - onLoginPass={() => { - setUnauthorized(false) - }} - instanceNameByBackendURL={instanceNameByBackendURL} - /> - </Router> - ); + const history = createHashHistory(); + return ( + <Router history={history}> + <Route + default + component={DefaultMainRoute} + /> + </Router> + ); } function DefaultMainRoute({ - instance, - admin, - onUnauthorized, - onLoginPass, - instanceNameByBackendURL, - url, //from preact-router -}: any): VNode { - const [instanceName, setInstanceName] = useState( - instanceNameByBackendURL || instance || "default", - ); + url, //from preact-router + }: any): VNode { + //TODO + url = "app/#" + url; - return ( - <InstanceRoutes - admin={admin} - path={url} - onUnauthorized={onUnauthorized} - onLoginPass={onLoginPass} - id={instanceName} - setInstanceName={setInstanceName} - /> - ); + return ( + <InstanceRoutes + path={url} + /> + ); } diff --git a/packages/auditor-backoffice-ui/src/components/exception/loading.tsx b/packages/auditor-backoffice-ui/src/components/exception/loading.tsx index 5c249f79d..11b62c124 100644 --- a/packages/auditor-backoffice-ui/src/components/exception/loading.tsx +++ b/packages/auditor-backoffice-ui/src/components/exception/loading.tsx @@ -22,27 +22,27 @@ import { h, VNode } from "preact"; export function Loading(): VNode { - return ( - <div - class="columns is-centered is-vcentered" - style={{ - height: "calc(100% - 3rem)", - position: "absolute", - width: "100%", - }} - > - <Spinner /> - </div> - ); + return ( + <div + class="columns is-centered is-vcentered" + style={{ + height: "calc(100% - 3rem)", + position: "absolute", + width: "100%", + }} + > + <Spinner /> + </div> + ); } export function Spinner(): VNode { - return ( - <div class="lds-ring"> - <div /> - <div /> - <div /> - <div /> - </div> - ); + return ( + <div class="lds-ring"> + <div /> + <div /> + <div /> + <div /> + </div> + ); } diff --git a/packages/auditor-backoffice-ui/src/components/form/JumpToElementById.tsx b/packages/auditor-backoffice-ui/src/components/form/JumpToElementById.tsx deleted file mode 100644 index a0e1d6ae4..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/JumpToElementById.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; - -export function JumpToElementById({ testIfExist, onSelect, placeholder, description }: { placeholder: TranslatedString, description: TranslatedString, testIfExist: (id: string) => Promise<any>, onSelect: (id: string) => void }): VNode { - const { i18n } = useTranslationContext() - - const [error, setError] = useState<string | undefined>( - undefined, - ); - - const [id, setId] = useState<string>() - async function check(currentId: string | undefined): Promise<void> { - if (!currentId) { - setError(i18n.str`missing id`); - return; - } - try { - await testIfExist(currentId); - onSelect(currentId); - setError(undefined); - } catch { - setError(i18n.str`not found`); - } - } - - return <div class="level"> - <div class="level-left"> - <div class="level-item"> - <div class="field has-addons"> - <div class="control"> - <input - class={error ? "input is-danger" : "input"} - type="text" - value={id ?? ""} - onChange={(e) => setId(e.currentTarget.value)} - placeholder={placeholder} - /> - {error && <p class="help is-danger">{error}</p>} - </div> - <span - class="has-tooltip-bottom" - data-tooltip={description} - > - <button - class="button" - onClick={(e) => check(id)} - > - <span class="icon"> - <i class="mdi mdi-arrow-right" /> - </span> - </button> - </span> - </div> - </div> - </div> - </div> -} diff --git a/packages/auditor-backoffice-ui/src/components/form/useGroupField.tsx b/packages/auditor-backoffice-ui/src/components/form/useGroupField.tsx deleted file mode 100644 index 4fbfc4a75..000000000 --- a/packages/auditor-backoffice-ui/src/components/form/useGroupField.tsx +++ /dev/null @@ -1,41 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { useFormContext } from "./FormProvider.js"; - -interface Use { - hasError?: boolean; -} - -export function useGroupField<T>(name: keyof T): Use { - const f = useFormContext<T>(); - if (!f) return {}; - - return { - hasError: readField(f.errors, String(name)), - }; -} - -const readField = (object: any, name: string) => { - return name - .split(".") - .reduce((prev, current) => prev && prev[current], object); -}; diff --git a/packages/auditor-backoffice-ui/src/components/form/FormProvider.tsx b/packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx index a5f3c1d2f..a5f3c1d2f 100644 --- a/packages/auditor-backoffice-ui/src/components/form/FormProvider.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/FormProvider.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/Input.tsx b/packages/auditor-backoffice-ui/src/components/forms/Input.tsx index 899061c35..899061c35 100644 --- a/packages/auditor-backoffice-ui/src/components/form/Input.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/Input.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/InputCurrency.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx index c1359e641..c1359e641 100644 --- a/packages/auditor-backoffice-ui/src/components/form/InputCurrency.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/InputCurrency.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/InputNumber.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx index 10b28cd93..10b28cd93 100644 --- a/packages/auditor-backoffice-ui/src/components/form/InputNumber.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/InputNumber.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/InputSelector.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx index f567f7247..f567f7247 100644 --- a/packages/auditor-backoffice-ui/src/components/form/InputSelector.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/InputSelector.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/InputToggle.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx index 89b815b4b..89b815b4b 100644 --- a/packages/auditor-backoffice-ui/src/components/form/InputToggle.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/InputToggle.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/InputWithAddon.tsx b/packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx index b8cd4c2d2..b8cd4c2d2 100644 --- a/packages/auditor-backoffice-ui/src/components/form/InputWithAddon.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/InputWithAddon.tsx diff --git a/packages/auditor-backoffice-ui/src/components/form/useField.tsx b/packages/auditor-backoffice-ui/src/components/forms/useField.tsx index 49bba4984..49bba4984 100644 --- a/packages/auditor-backoffice-ui/src/components/form/useField.tsx +++ b/packages/auditor-backoffice-ui/src/components/forms/useField.tsx diff --git a/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx b/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx index e3387d597..0b662d8de 100644 --- a/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/auditor-backoffice-ui/src/components/menu/SideBar.tsx @@ -15,57 +15,20 @@ */ /** - * - * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, h, VNode } from "preact"; -import { useBackendContext } from "../../context/backend.js"; import { useConfigContext } from "../../context/config.js"; -import { useInstanceKYCDetails } from "../../hooks/instance.js"; -import { LangSelector } from "./LangSelector.js"; -const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; -const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; - -interface Props { - onLogout: () => void; - onShowSettings: () => void; - mobile?: boolean; - instance: string; - admin?: boolean; - mimic?: boolean; - isPasswordOk: boolean; -} - -export function Sidebar({ - mobile, - instance, - onShowSettings, - onLogout, - admin, - mimic, - isPasswordOk -}: Props): VNode { - const config = useConfigContext(); - const { url: backendURL } = useBackendContext() +export function Sidebar(props: any): VNode { + const configData = useConfigContext(); const { i18n } = useTranslationContext(); - const kycStatus = useInstanceKYCDetails(); - const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; + console.log(configData); return ( <aside class="aside is-placed-left is-expanded" style={{ overflowY: "scroll" }}> - {mobile && ( - <div - class="footer" - onClick={(e) => { - return e.stopImmediatePropagation(); - }} - > - <LangSelector /> - </div> - )} <div class="aside-tools"> <div class="aside-tools-label"> <div> @@ -75,210 +38,66 @@ export function Sidebar({ class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }} > - {VERSION} ({config.version}) + (Version {configData.version}) </div> </div> </div> <div class="menu is-menu-main"> - {instance ? ( - <Fragment> - <ul class="menu-list"> - <li> - <a href={"/orders"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-cash-register" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Orders</i18n.Translate> - </span> - </a> - </li> - <li> - <a href={"/inventory"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-shopping" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Inventory</i18n.Translate> - </span> - </a> - </li> - <li> - <a href={"/transfers"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-arrow-left-right" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Transfers</i18n.Translate> - </span> - </a> - </li> - <li> - <a href={"/templates"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-newspaper" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Templates</i18n.Translate> - </span> - </a> - </li> - {needKYC && ( - <li> - <a href={"/kyc"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-account-check" /> - </span> - <span class="menu-item-label">KYC Status</span> - </a> - </li> - )} - </ul> - <p class="menu-label"> - <i18n.Translate>Configuration</i18n.Translate> - </p> - <ul class="menu-list"> - <li> - <a href={"/bank"} class="has-icon"> + <Fragment> + <ul class="menu-list"> + <li> + <a href={"/key-figures"} class="has-icon"> <span class="icon"> <i class="mdi mdi-bank" /> </span> - <span class="menu-item-label"> - <i18n.Translate>Bank account</i18n.Translate> + <span class="menu-item-label"> + <i18n.Translate>Key figures</i18n.Translate> </span> - </a> - </li> - <li> - <a href={"/otp-devices"} class="has-icon"> + </a> + </li> + <li> + <a href={"/critical-errors"} class="has-icon"> <span class="icon"> - <i class="mdi mdi-lock" /> + <i class="mdi mdi-alert" /> </span> - <span class="menu-item-label"> - <i18n.Translate>OTP Devices</i18n.Translate> + <span class="menu-item-label"> + <i18n.Translate>Critical errors</i18n.Translate> </span> - </a> - </li> - <li> - <a href={"/reserves"} class="has-icon"> + </a> + </li> + <li> + <a href={"/operating-status"} class="has-icon"> <span class="icon"> - <i class="mdi mdi-cash" /> + <i class="mdi mdi-close-network" /> + </span> + <span class="menu-item-label"> + <i18n.Translate>Operating status</i18n.Translate> </span> - <span class="menu-item-label">Reserves</span> - </a> - </li> - <li> - <a href={"/webhooks"} class="has-icon"> + </a> + </li> + <li> + <a href={"/detail-view"} class="has-icon"> <span class="icon"> - <i class="mdi mdi-newspaper" /> + <i class="mdi mdi-format-wrap-tight" /> </span> - <span class="menu-item-label"> - <i18n.Translate>Webhooks</i18n.Translate> + <span class="menu-item-label"> + <i18n.Translate>Inconsistencies</i18n.Translate> </span> - </a> - </li> - <li> - <a href={"/settings"} class="has-icon"> + </a> + </li> + <li> + <a href={"/settings"} class="has-icon"> <span class="icon"> <i class="mdi mdi-square-edit-outline" /> </span> - <span class="menu-item-label"> + <span class="menu-item-label"> <i18n.Translate>Settings</i18n.Translate> </span> - </a> - </li> - <li> - <a href={"/token"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-security" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Access token</i18n.Translate> - </span> - </a> - </li> - </ul> - </Fragment> - ) : undefined} - <p class="menu-label"> - <i18n.Translate>Connection</i18n.Translate> - </p> - <ul class="menu-list"> - <li> - <a class="has-icon is-state-info is-hoverable" - onClick={(): void => onShowSettings()} - > - <span class="icon"> - <i class="mdi mdi-newspaper" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Interface</i18n.Translate> - </span> - </a> - </li> - <li> - <div> - <span style={{ width: "3rem" }} class="icon"> - <i class="mdi mdi-web" /> - </span> - <span class="menu-item-label"> - {new URL(backendURL).hostname} - </span> - </div> - </li> - <li> - <div> - <span style={{ width: "3rem" }} class="icon"> - ID - </span> - <span class="menu-item-label"> - {!instance ? "default" : instance} - </span> - </div> - </li> - {admin && !mimic && ( - <Fragment> - <p class="menu-label"> - <i18n.Translate>Instances</i18n.Translate> - </p> - <li> - <a href={"/instance/new"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-plus" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>New</i18n.Translate> - </span> - </a> - </li> - <li> - <a href={"/instances"} class="has-icon"> - <span class="icon"> - <i class="mdi mdi-format-list-bulleted" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>List</i18n.Translate> - </span> - </a> - </li> - </Fragment> - )} - {isPasswordOk ? - <li> - <a - class="has-icon is-state-info is-hoverable" - onClick={(): void => onLogout()} - > - <span class="icon"> - <i class="mdi mdi-logout default" /> - </span> - <span class="menu-item-label"> - <i18n.Translate>Log out</i18n.Translate> - </span> </a> - </li> : undefined - } - </ul> + </li> + </ul> + </Fragment> </div> </aside> ); -} +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/components/modal/index.tsx b/packages/auditor-backoffice-ui/src/components/modal/index.tsx index c684ba7a3..ab2834d86 100644 --- a/packages/auditor-backoffice-ui/src/components/modal/index.tsx +++ b/packages/auditor-backoffice-ui/src/components/modal/index.tsx @@ -22,11 +22,11 @@ import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { useInstanceContext } from "../../context/instance.js"; +import { useEntityContext } from "../../context/entity.js"; import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js"; import { Spinner } from "../exception/loading.js"; -import { FormProvider } from "../form/FormProvider.js"; -import { Input } from "../form/Input.js"; +import { FormProvider } from "../forms/FormProvider.js"; +import { Input } from "../forms/Input.js"; interface Props { active?: boolean; @@ -310,9 +310,9 @@ export function UpdateTokenModal({ (k) => (errors as any)[k] !== undefined, ); - const instance = useInstanceContext(); + const instance = useEntityContext(); - const text = i18n.str`You are updating the access token from instance with id ${instance.id}`; + const text = i18n.str`You are updating the access token from instance with id `; return ( <ClearConfirmModal diff --git a/packages/auditor-backoffice-ui/src/context/backend.ts b/packages/auditor-backoffice-ui/src/context/backend.ts index b13b92c42..ce321c3e6 100644 --- a/packages/auditor-backoffice-ui/src/context/backend.ts +++ b/packages/auditor-backoffice-ui/src/context/backend.ts @@ -17,54 +17,54 @@ /** * * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ -import { useMemoryStorage } from "@gnu-taler/web-util/browser"; import { createContext, h, VNode } from "preact"; import { useContext } from "preact/hooks"; -import { LoginToken } from "../declaration.js"; -import { useBackendDefaultToken, useBackendURL } from "../hooks/index.js"; +import { useBackendURL } from "../hooks/index.js"; interface BackendContextType { - url: string, - alreadyTriedLogin: boolean; - token?: LoginToken; - updateToken: (token: LoginToken | undefined) => void; + url: string, } const BackendContext = createContext<BackendContextType>({ - url: "", - alreadyTriedLogin: false, - token: undefined, - updateToken: () => null, + url: "", }); function useBackendContextState( - defaultUrl?: string, + defaultUrl?: string, ): BackendContextType { -const [url] = useBackendURL(defaultUrl); - //const url = "http://localhost:8081"; - const [token, updateToken] = useBackendDefaultToken(); - - return { - url, - token, - alreadyTriedLogin: token !== undefined, - updateToken, - }; + const [url] = useBackendURL(defaultUrl); + + return { + url, + }; } export const BackendContextProvider = ({ - children, - defaultUrl, -}: { - children: any; - defaultUrl?: string; + children, + defaultUrl, + }: { + children: any; + defaultUrl?: string; }): VNode => { - const value = useBackendContextState(defaultUrl); + const value = useBackendContextState(defaultUrl); - return h(BackendContext.Provider, { value, children }); + return h(BackendContext.Provider, { value, children }); }; + + export const useBackendContext = (): BackendContextType => - useContext(BackendContext); + useContext(BackendContext); + +interface BackendTokenType { + token: string; +} + +const BackendTokenContext = createContext<BackendTokenType>({} as any); + +export const BackendTokenContextProvider = BackendTokenContext.Provider; + +export const useBackendTokenContext = (): BackendTokenType => useContext(BackendTokenContext);
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/context/config.ts b/packages/auditor-backoffice-ui/src/context/config.ts index def45ea64..58ee5a594 100644 --- a/packages/auditor-backoffice-ui/src/context/config.ts +++ b/packages/auditor-backoffice-ui/src/context/config.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021-2024 Taler Systems S.A. + (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 @@ -21,12 +21,9 @@ import { createContext } from "preact"; import { useContext } from "preact/hooks"; +import { AuditorBackend } from "../declaration.js"; -interface Type { - currency: string; - version: string; -} -const Context = createContext<Type>(null!); +const Context = createContext<AuditorBackend.VersionResponse>(null!); export const ConfigContextProvider = Context.Provider; -export const useConfigContext = (): Type => useContext(Context); +export const useConfigContext = (): AuditorBackend.VersionResponse => useContext(Context); diff --git a/packages/auditor-backoffice-ui/src/context/instance.ts b/packages/auditor-backoffice-ui/src/context/entity.ts index 5800ade7e..a5f87ee02 100644 --- a/packages/auditor-backoffice-ui/src/context/instance.ts +++ b/packages/auditor-backoffice-ui/src/context/entity.ts @@ -17,20 +17,31 @@ /** * * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ import { createContext } from "preact"; import { useContext } from "preact/hooks"; -import { LoginToken } from "../declaration.js"; -interface Type { - id: string; - token?: LoginToken; - admin?: boolean; - changeToken: (t?: LoginToken) => void; +interface EntityType { + title: string; + path: string; + endpoint: string; + entity: any; } -const Context = createContext<Type>({} as any); +const EntityContext = createContext<EntityType>({} as any); -export const InstanceContextProvider = Context.Provider; -export const useInstanceContext = (): Type => useContext(Context); +export const EntityContextProvider = EntityContext.Provider; + +export const useEntityContext = (): EntityType => useContext(EntityContext); + +interface EntityDataType { + data: any; +} + +const EntityDataContext = createContext<EntityDataType>({} as any); + +export const EntityDataContextProvider = EntityDataContext.Provider; + +export const useEntityDataContext = (): EntityDataType => useContext(EntityDataContext); diff --git a/packages/auditor-backoffice-ui/src/custom.d.ts b/packages/auditor-backoffice-ui/src/custom.d.ts index 34522a2dd..e693c2951 100644 --- a/packages/auditor-backoffice-ui/src/custom.d.ts +++ b/packages/auditor-backoffice-ui/src/custom.d.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021-2024 Taler Systems S.A. + (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 diff --git a/packages/auditor-backoffice-ui/src/declaration.d.ts b/packages/auditor-backoffice-ui/src/declaration.d.ts index 0c6f599f7..a8cdee53c 100644 --- a/packages/auditor-backoffice-ui/src/declaration.d.ts +++ b/packages/auditor-backoffice-ui/src/declaration.d.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021-2024 Taler Systems S.A. + (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 @@ -17,6 +17,7 @@ /** * * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel */ type HashCode = string; @@ -25,7 +26,7 @@ type EddsaSignature = string; type WireTransferIdentifierRawP = string; type RelativeTime = TalerProtocolDuration; type ImageDataUrl = string; -type MerchantUserType = "business" | "individual"; +type AuditorUserType = "business" | "individual"; export interface WithId { @@ -38,9 +39,11 @@ interface Timestamp { // never happen. t_s: number | "never"; } + interface TalerProtocolDuration { d_us: number | "forever"; } + interface Duration { d_ms: number | "forever"; } @@ -53,263 +56,51 @@ type Amount = string; type UUID = string; type Integer = number; -interface WireAccount { - // payto:// URI identifying the account and wire method - payto_uri: string; - - // URI to convert amounts from or to the currency used by - // this wire account of the exchange. Missing if no - // conversion is applicable. - conversion_url?: string; - - // Restrictions that apply to bank accounts that would send - // funds to the exchange (crediting this exchange bank account). - // Optional, empty array for unrestricted. - credit_restrictions: AccountRestriction[]; - - // Restrictions that apply to bank accounts that would receive - // funds from the exchange (debiting this exchange bank account). - // Optional, empty array for unrestricted. - debit_restrictions: AccountRestriction[]; - - // Signature using the exchange's offline key over - // a TALER_MasterWireDetailsPS - // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. - master_sig: EddsaSignature; -} - -type AccountRestriction = RegexAccountRestriction | DenyAllAccountRestriction; - -// Account restriction that disables this type of -// account for the indicated operation categorically. -interface DenyAllAccountRestriction { - type: "deny"; -} - -// Accounts interacting with this type of account -// restriction must have a payto://-URI matching -// the given regex. -interface RegexAccountRestriction { - type: "regex"; - - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; - - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; - - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: { [lang_tag: string]: string }; -} -interface LoginToken { - token: string, - expiration: Timestamp, -} -// token used to get loginToken -// must forget after used -declare const __ac_token: unique symbol; -type AccessToken = string & { - [__ac_token]: true; -}; - -export namespace ExchangeBackend { - interface WireResponse { - // Master public key of the exchange, must match the key returned in /keys. - master_public_key: EddsaPublicKey; - - // Array of wire accounts operated by the exchange for - // incoming wire transfers. - accounts: WireAccount[]; - - // Object mapping names of wire methods (i.e. "sepa" or "x-taler-bank") - // to wire fees. - fees: { method: AggregateTransferFee }; - } - interface AggregateTransferFee { - // Per transfer wire transfer fee. - wire_fee: Amount; - - // Per transfer closing fee. - closing_fee: Amount; - - // What date (inclusive) does this fee go into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - start_date: Timestamp; - - // What date (exclusive) does this fee stop going into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - end_date: Timestamp; - - // Signature of TALER_MasterWireFeePS with - // purpose TALER_SIGNATURE_MASTER_WIRE_FEES. - sig: EddsaSignature; - } -} export namespace AuditorBackend { - interface ErrorDetail { - // Numeric error code unique to the condition. - // The other arguments are specific to the error value reported here. - code: number; + interface DepositConfirmation { + // identifier + deposit_confirmation_serial_id: number; - // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ... - // Should give a human-readable hint about the error's nature. Optional, may change without notice! - hint?: string; + h_contract_terms: string; - // Optional detail about the specific input value that failed. May change without notice! - detail?: string; + h_policy: string; - // Name of the parameter that was bogus (if applicable). - parameter?: string; + h_wire: string; - // Path to the argument that was bogus (if applicable). - path?: string; + exchange_timestamp: string; - // Offset of the argument that was bogus (if applicable). - offset?: string; + refund_deadline: string; - // Index of the argument that was bogus (if applicable). - index?: string; + wire_deadline: string; - // Name of the object that was bogus (if applicable). - object?: string; + total_without_fee: string; - // Name of the currency than was problematic (if applicable). - currency?: string; + coin_pubs: string; - // Expected type (if applicable). - type_expected?: string; + coin_sigs: string; - // Type that was provided instead (if applicable). - type_actual?: string; - } - interface Exchange { - // the exchange's base URL - url: string; - - // master public key of the exchange - master_pub: EddsaPublicKey; - } - namespace DepositConfirmation { - // POST /deposit-confirmation - interface ProductAddDetail { - // product ID to use. - product_id: string; - - // Human-readable product description. - description: string; + merchant_pub: string; - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; + merchant_sig: string; - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; + exchange_pub: string; - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; + exchange_sig: string; - // An optional base64-encoded product image - image: ImageDataUrl; + suppressed: string; - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - // PATCH /private/products/$PRODUCT_ID - interface ProductPatchDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - - // GET /deposit-confirmation - interface DepositConfirmationList { - depositConfirmations: DepositConfirmation []; - } - interface DepositConfirmation { - serial_id: string; - timestamp: string; - refund_deadline: string; - wire_deadline: string; - amount_without_fee: string; - } + ancient: string; + } - // GET /deposit-confirmation/$SERIAL_ID - interface DepositConfirmationDetail { - serial_id: string; - timestamp: string; - refund_deadline: string; - wire_deadline: string; - amount_without_fee: string; - } + interface Config { + name: string; + version: string; + implementation: string; + currency: string; + auditor_public_key: string; + exchange_master_public_key: string; } -} -export namespace MerchantBackend { interface ErrorDetail { // Numeric error code unique to the condition. // The other arguments are specific to the error value reported here. @@ -347,1447 +138,618 @@ export namespace MerchantBackend { type_actual?: string; } - // Delivery location, loosely modeled as a subset of - // ISO20022's PostalAddress25. - interface Tax { - // the name of the tax - name: string; - - // amount paid in tax - tax: Amount; - } - - interface Auditor { - // official name - name: string; - - // Auditor's public key - auditor_pub: EddsaPublicKey; - - // Base URL of the auditor - url: string; - } - interface Exchange { - // the exchange's base URL - url: string; - - // master public key of the exchange - master_pub: EddsaPublicKey; - } - - interface Product { - // merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n?: { [lang_tag: string]: string }; - - // The number of units of the product to deliver to the customer. - quantity: Integer; - - // The unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for this product. Can be empty. - taxes: Tax[]; - - // time indicating when this product should be delivered - delivery_date?: TalerProtocolTimestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - interface Merchant { - // label for a location with the business address of the merchant - address: Location; - - // the merchant's legal name of business - name: string; - - // label for a location that denotes the jurisdiction for disputes. - // Some of the typical fields for a location (such as a street address) may be absent. - jurisdiction: Location; - } - interface VersionResponse { // libtool-style representation of the Merchant protocol version, see // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning // The format is "current:revision:age". - version: string; - // Name of the protocol. - name: "taler-merchant"; - // Currency supported by this backend. + name: "taler-auditor"; + version: string; + + // Default (!) currency supported by this backend. + // This is the currency that the backend should + // suggest by default to the user when entering + // amounts. See currencies for a list of + // supported currencies and how to render them. + implementation: string; currency: string; + auditor_public_key: string; + exchange_master_public_key: string; + + // How services should render currencies supported + // by this backend. Maps + // currency codes (e.g. "EUR" or "KUDOS") to + // the respective currency specification. + // All currencies in this map are supported by + // the backend. Note that the actual currency + // specifications are a *hint* for applications + // that would like *advice* on how to render amounts. + // Applications *may* ignore the currency specification + // if they know how to render currencies that they are + // used with. + //currencies: { currency: CurrencySpecification }; + + // Array of exchanges trusted by the merchant. + // Since protocol v6. + // exchanges: ExchangeConfigInfo[]; } - interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; + export interface TokenResponse { + null; } - namespace Instances { - //POST /private/instances/$INSTANCE/auth - interface InstanceAuthConfigurationMessage { - // Type of authentication. - // "external": The mechant backend does not do - // any authentication checks. Instead an API - // gateway must do the authentication. - // "token": The merchant checks an auth token. - // See "token" for details. - method: "external" | "token"; - - // For method "external", this field is mandatory. - // The token MUST begin with the string "secret-token:". - // After the auth token has been set (with method "token"), - // the value must be provided in a "Authorization: Bearer $token" - // header. - token?: string; - } - //POST /private/instances - interface InstanceConfigurationMessage { - // Name of the merchant instance to create (will become $INSTANCE). - id: string; - - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // "Authentication" header required to authorize management access the instance. - // Optional, if not given authentication will be disabled for - // this instance (hopefully authentication checks are still - // done by some reverse proxy). - auth: InstanceAuthConfigurationMessage; - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; + namespace Default { + interface ObjectResponse { + object: AnyEntry[]; } + } - // PATCH /private/instances/$INSTANCE - interface InstanceReconfigurationMessage { - - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; + namespace AmountArithmeticInconsistency { - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; + class ClassAmountArithmeticInconsistency { + data: AmountArithmeticInconsistencyDetail[]; } - // GET /private/instances - interface InstancesResponse { - // List of instances that are present in the backend (see Instance) - instances: Instance[]; + interface SummaryResponse { + amount_arithmetic_inconsistency: AmountArithmeticInconsistencyDetail[]; } - interface Instance { - // Merchant name corresponding to this instance. - name: string; - - // Type of the user ("business" or "individual"). - user_type: MerchantUserType; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Merchant instance this response is about ($INSTANCE) - id: string; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // List of the payment targets supported by this instance. Clients can - // specify the desired payment target in /order requests. Note that - // front-ends do not have to support wallets selecting payment targets. - payment_targets: string[]; - - // Has this instance been deleted (but not purged)? - deleted: boolean; + interface AmountArithmeticInconsistencyDetail { + row_id: number; + operation: string; + exchange_amount: string; + auditor_amount: string; + profitable: boolean; + suppressed: boolean; } + } - //GET /private/instances/$INSTANCE - interface QueryInstancesResponse { - - // Merchant name corresponding to this instance. - name: string; - // Type of the user ("business" or "individual"). - user_type: MerchantUserType; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - - // Authentication configuration. - // Does not contain the token when token auth is configured. - auth: { - method: "external" | "token"; - }; + namespace BadSigLoss { + class ClassBadSigLoss { + data: BadSigLossDetail[]; } - // DELETE /private/instances/$INSTANCE - interface LoginTokenRequest { - // Scope of the token (which kinds of operations it will allow) - scope: "readonly" | "write"; - - // Server may impose its own upper bound - // on the token validity duration - duration?: RelativeTime; - // Can this token be refreshed? - // Defaults to false. - refreshable?: boolean; + interface SummaryResponse { + amount_arithmetic_inconsistency: BadSigLossDetail[]; } - interface LoginTokenSuccessResponse { - // The login token that can be used to access resources - // that are in scope for some time. Must be prefixed - // with "Bearer " when used in the "Authorization" HTTP header. - // Will already begin with the RFC 8959 prefix. - token: string; - // Scope of the token (which kinds of operations it will allow) - scope: "readonly" | "write"; - - // Server may impose its own upper bound - // on the token validity duration - expiration: Timestamp; - - // Can this token be refreshed? - refreshable: boolean; + interface BadSigLossDetail + { + row_id: number; + operation: string; + loss: string; + operation_specific_pub: string; + suppressed: boolean; } } - namespace KYC { - //GET /private/instances/$INSTANCE/kyc - interface AccountKycRedirects { - // Array of pending KYCs. - pending_kycs: MerchantAccountKycRedirect[]; + namespace Balance { - // Array of exchanges with no reply. - timeout_kycs: ExchangeKycTimeout[]; + class ClassBalance { + // List of products that are present in the inventory + data: BalanceDetail[]; } - interface MerchantAccountKycRedirect { - // URL that the user should open in a browser to - // proceed with the KYC process (as returned - // by the exchange's /kyc-check/ endpoint). - // Optional, missing if the account is blocked - // due to AML and not due to KYC. - kyc_url?: string; - // Base URL of the exchange this is about. - exchange_url: string; + interface SummaryResponse { + // List of products that are present in the inventory + balances: BalanceDetail[]; + } - // AML status of the account. - aml_status: number; + interface BalanceDetail { + // identifier + row_id: number; - // Our bank wire account this is about. - payto_uri: string; - } - interface ExchangeKycTimeout { - // Base URL of the exchange this is about. - exchange_url: string; + balance_key: string; - // Numeric error code indicating errors the exchange - // returned, or TALER_EC_INVALID for none. - exchange_code: number; + balance_value: string; - // HTTP status code returned by the exchange when we asked for - // information about the KYC status. - // 0 if there was no response at all. - exchange_http_status: number; + suppressed: boolean; } - } - namespace BankAccounts { - - interface AccountAddDetails { - - // payto:// URI of the account. - payto_uri: string; - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - // To really delete credentials, set them to the type: "none". - credit_facade_credentials?: FacadeCredentials; - + namespace ClosureLag { + class ClassClosureLag { + // List of products that are present in the inventory + data: ClosureLagDetail[]; } - type FacadeCredentials = - | NoFacadeCredentials - | BasicAuthFacadeCredentials; - - interface NoFacadeCredentials { - type: "none"; + interface SummaryResponse { + // List of products that are present in the inventory + closure_lags: ClosureLagDetail[]; } - interface BasicAuthFacadeCredentials { - type: "basic"; - - // Username to use to authenticate - username: string; - - // Password to use to authenticate - password: string; + interface ClosureLagDetail { + row_id: number; + amount: string; + deadline: number; + wtid: number; + account: string; + suppressed: boolean; } + } - interface AccountAddResponse { - // Hash over the wire details (including over the salt). - h_wire: HashCode; - - // Salt used to compute h_wire. - salt: HashCode; + namespace CoinInconsistency { + class ClassCoinInconsistency { + data: CoinInconsistencyDetail[]; } - interface AccountPatchDetails { - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - // To really delete credentials, set them to the type: "none". - credit_facade_credentials?: FacadeCredentials; + interface SummaryResponse { + amount_arithmetic_inconsistency: CoinInconsistencyDetail[]; } - - interface AccountsSummaryResponse { - - // List of accounts that are known for the instance. - accounts: BankAccountEntry[]; + interface CoinInconsistencyDetail + { + row_id: number; + operation: string; + exchange_amount: string; + auditor_amount: string; + coin_pub: string; + profitable: boolean; + suppressed: boolean; } + } - interface BankAccountEntry { - // payto:// URI of the account. - payto_uri: string; - - // Hash over the wire details (including over the salt) - h_wire: HashCode; - - // salt used to compute h_wire - salt: HashCode; - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - credit_facade_credentials?: FacadeCredentials; + namespace DenominationKeyValidityWithdrawInconsistency { + class ClassDenominationKeyValidityWithdrawInconsistency { + data: DenominationKeyValidityWithdrawInconsistencyDetail[]; + } - // true if this account is active, - // false if it is historic. - active: boolean; + interface SummaryResponse { + responseData: DenominationKeyValidityWithdrawInconsistencyDetail[]; } + interface DenominationKeyValidityWithdrawInconsistencyDetail + { + row_id: number; + operation: string; + loss: string; + operation_specific_pub: string; + suppressed: boolean; + } } - namespace Products { - // POST /private/products - interface ProductAddDetail { - // product ID to use. - product_id: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; + namespace DenominationPending { + class ClassDenominationPending { + data: DenominationPendingDetail[]; } - // PATCH /private/products/$PRODUCT_ID - interface ProductPatchDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; + interface SummaryResponse { + responseData: DenominationPendingDetail[]; } - // GET /private/products - interface InventorySummaryResponse { - // List of products that are present in the inventory - products: InventoryEntry[]; + interface DenominationPendingDetail + { + denom_pub_hash: string; + denom_balance: string; + denom_loss: string; + num_issued: number; + denom_risk: string; + recoup_loss: string; + suppressed: boolean; } - interface InventoryEntry { - // Product identifier, as found in the product. - product_id: string; - } - - // GET /private/products/$PRODUCT_ID - interface ProductDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n: { [lang_tag: string]: string }; - - // unit in which the product is measured (liters, kilograms, packages, etc.) - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: Amount; - - // An optional base64-encoded product image - image: ImageDataUrl; - - // a list of taxes paid by the merchant for one unit of this product - taxes: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that have already been sold. - total_sold: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.) - total_lost: Integer; - - // Identifies where the product is in stock. - address: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; + } - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; + namespace DenominationsWithoutSigs { + class ClassDenominationsWithoutSigs { + data: DenominationsWithoutSigsDetail[]; } - // POST /private/products/$PRODUCT_ID/lock - interface LockRequest { - // UUID that identifies the frontend performing the lock - // It is suggested that clients use a timeflake for this, - // see https://github.com/anthonynsimon/timeflake - lock_uuid: UUID; - - // How long does the frontend intend to hold the lock - duration: RelativeTime; - - // How many units should be locked? - quantity: Integer; + interface SummaryResponse { + responseData: DenominationsWithoutSigsDetail[]; } - // DELETE /private/products/$PRODUCT_ID + interface DenominationsWithoutSigsDetail + { + row_id: number; + denompub_h: string; + value: string; + start_time: number; + end_time: number; + suppressed: boolean; + } } - namespace Orders { - type MerchantOrderStatusResponse = - | CheckPaymentPaidResponse - | CheckPaymentClaimedResponse - | CheckPaymentUnpaidResponse; - interface CheckPaymentPaidResponse { - // The customer paid for this contract. - order_status: "paid"; - - // Was the payment refunded (even partially)? - refunded: boolean; - - // True if there are any approved refunds that the wallet has - // not yet obtained. - refund_pending: boolean; - - // Did the exchange wire us the funds? - wired: boolean; - - // Total amount the exchange deposited into our bank account - // for this contract, excluding fees. - deposit_total: Amount; - - // Numeric error code indicating errors the exchange - // encountered tracking the wire transfer for this purchase (before - // we even got to specific coin issues). - // 0 if there were no issues. - exchange_ec: number; - - // HTTP status code returned by the exchange when we asked for - // information to track the wire transfer for this purchase. - // 0 if there were no issues. - exchange_hc: number; - - // Total amount that was refunded, 0 if refunded is false. - refund_amount: Amount; - - // Contract terms. - contract_terms: ContractTerms; - - // The wire transfer status from the exchange for this order if - // available, otherwise empty array. - wire_details: TransactionWireTransfer[]; - - // Reports about trouble obtaining wire transfer details, - // empty array if no trouble were encountered. - wire_reports: TransactionWireReport[]; - - // The refund details for this order. One entry per - // refunded coin; empty array if there are no refunds. - refund_details: RefundDetails[]; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; + namespace DepositConfirmation { + class ClassDepositConfirmation{ + data: DepositConfirmationDetail[]; } - interface CheckPaymentClaimedResponse { - // A wallet claimed the order, but did not yet pay for the contract. - order_status: "claimed"; - // Contract terms. - contract_terms: ContractTerms; + interface SummaryResponse { + responseData: DepositConfirmationDetail[]; } - interface CheckPaymentUnpaidResponse { - // The order was neither claimed nor paid. - order_status: "unpaid"; - - // when was the order created - creation_time: Timestamp; - - // Order summary text. - summary: string; - // Total amount of the order (to be paid by the customer). - total_amount: Amount; - - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - - // Fulfillment URL of an already paid order. Only given if under this - // session an already paid order with a fulfillment URL exists. - already_paid_fulfillment_url?: string; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; - - // We do we NOT return the contract terms here because they may not - // exist in case the wallet did not yet claim them. + interface DepositConfirmationDetail { + deposit_confirmation_serial_id: number; + h_contract_terms: string; + h_policy: string; + h_wire: string; + exchange_timestamp: string; + refund_deadline: string; + wire_deadline: string; + total_without_fee: string; + coin_pubs: string; + coin_sigs: string; + merchant_pub: string; + merchant_sig: string; + exchange_pub: string; + exchange_sig: string; + suppressed: string; + ancient: string; } - interface RefundDetails { - // Reason given for the refund. - reason: string; - - // When was the refund approved. - timestamp: Timestamp; - - // Set to true if a refund is still available for the wallet for this payment. - pending: boolean; + } - // Total amount that was refunded (minus a refund fee). - amount: Amount; + namespace Emergency { + class ClassEmergency{ + data: EmergencyDetail[]; } - interface TransactionWireTransfer { - // Responsible exchange. - exchange_url: string; - - // 32-byte wire transfer identifier. - wtid: Base32; - // Execution time of the wire transfer. - execution_time: Timestamp; - - // Total amount that has been wire transferred - // to the merchant. - amount: Amount; - - // Was this transfer confirmed by the merchant via the - // POST /transfers API, or is it merely claimed by the exchange? - confirmed: boolean; + interface SummaryResponse { + responseData: EmergencyDetail[]; } - interface TransactionWireReport { - // Numerical error code. - code: number; - - // Human-readable error description. - hint: string; - // Numerical error code from the exchange. - exchange_ec: number; - - // HTTP status code received from the exchange. - exchange_hc: number; + interface EmergencyDetail + { + row_id: number; + denompub_h: string; + denom_risk: string; + denom_loss: string; + deposit_start: number; + deposit_end: number; + value: string; + } + } - // Public key of the coin for which we got the exchange error. - coin_pub: CoinPublicKey; + namespace EmergencyByCount { + class ClassEmergencyByCount{ + data: EmergencyByCountDetail[]; } - interface OrderHistory { - // timestamp-sorted array of all orders matching the query. - // The order of the sorting depends on the sign of delta. - orders: OrderHistoryEntry[]; + interface SummaryResponse { + responseData: EmergencyByCountDetail[]; } - interface OrderHistoryEntry { - // order ID of the transaction related to this entry. - order_id: string; - // row ID of the order in the database + interface EmergencyByCountDetail + { row_id: number; - - // when the order was created - timestamp: Timestamp; - - // the amount of money the order is for - amount: Amount; - - // the summary of the order - summary: string; - - // whether some part of the order is refundable, - // that is the refund deadline has not yet expired - // and the total amount refunded so far is below - // the value of the original transaction. - refundable: boolean; - - // whether the order has been paid or not - paid: boolean; + denompub_h: string; + num_issued: number; + num_known: number; + risk: string; + start: number; + deposit_end: number; + value: string; + suppressed: boolean; } + } - interface PostOrderRequest { - // The order must at least contain the minimal - // order detail, but can override all - order: Order; - - // if set, the backend will then set the refund deadline to the current - // time plus the specified delay. If it's not set, refunds will not be - // possible. - refund_delay?: RelativeTime; - - // specifies the payment target preferred by the client. Can be used - // to select among the various (active) wire methods supported by the instance. - payment_target?: string; - - // specifies that some products are to be included in the - // order from the inventory. For these inventory management - // is performed (so the products must be in stock) and - // details are completed from the product data of the backend. - inventory_products?: MinimalInventoryProduct[]; - - // Specifies a lock identifier that was used to - // lock a product in the inventory. Only useful if - // manage_inventory is set. Used in case a frontend - // reserved quantities of the individual products while - // the shopping card was being built. Multiple UUIDs can - // be used in case different UUIDs were used for different - // products (i.e. in case the user started with multiple - // shopping sessions that were combined during checkout). - lock_uuids?: UUID[]; - - // Should a token for claiming the order be generated? - // False can make sense if the ORDER_ID is sufficiently - // high entropy to prevent adversarial claims (like it is - // if the backend auto-generates one). Default is 'true'. - create_token?: boolean; - - // OTP device ID to associate with the order. - // This parameter is optional. - otp_id?: string; + namespace FeeTimeInconsistency { + class ClassFeeTimeInconsistency{ + data: FeeTimeInconsistencyDetail[]; } - type Order = MinimalOrderDetail | ContractTerms; - - interface MinimalOrderDetail { - // Amount to be paid by the customer - amount: Amount; - // Short summary of the order - summary: string; - - // URL that will show that the order was successful after - // it has been paid for. Optional. When POSTing to the - // merchant, the placeholder "${ORDER_ID}" will be - // replaced with the actual order ID (useful if the - // order ID is generated server-side and needs to be - // in the URL). - fulfillment_url?: string; + interface SummaryResponse { + responseData: FeeTimeInconsistencyDetail[]; } - interface MinimalInventoryProduct { - // Which product is requested (here mandatory!) - product_id: string; - - // How many units of the product are requested - quantity: Integer; + interface FeeTimeInconsistencyDetail + { + row_id: number; + type: string; + time: string; + diagnostic: string; + suppressed: boolean; } - interface PostOrderResponse { - // Order ID of the response that was just created - order_id: string; + } - // Token that authorizes the wallet to claim the order. - // Provided only if "create_token" was set to 'true' - // in the request. - token?: ClaimToken; + namespace HistoricDenominationRevenue { + class ClassHistoricDenominationRevenue { + data: HistoricDenominationRevenueDetail[]; } - interface OutOfStockResponse { - // Product ID of an out-of-stock item - product_id: string; - - // Requested quantity - requested_quantity: Integer; - - // Available quantity (must be below requested_quanitity) - available_quantity: Integer; - // When do we expect the product to be again in stock? - // Optional, not given if unknown. - restock_expected?: Timestamp; - } - - interface ForgetRequest { - // Array of valid JSON paths to forgettable fields in the order's - // contract terms. - fields: string[]; + interface SummaryResponse { + responseData: HistoricDenominationRevenueDetail[]; } - interface RefundRequest { - // Amount to be refunded - refund: Amount; - // Human-readable refund justification - reason: string; - } - interface MerchantRefundResponse { - // URL (handled by the backend) that the wallet should access to - // trigger refund processing. - // taler://refund/... - taler_refund_uri: string; - - // Contract hash that a client may need to authenticate an - // HTTP request to obtain the above URI in a wallet-friendly way. - h_contract: HashCode; + interface HistoricDenominationRevenueDetail + { + denom_pub_hash: string; + revenue_timestamp: number; + revenue_balance: string; + loss_balance: string; + suppressed: boolean; } } - namespace Rewards { - // GET /private/reserves - interface RewardReserveStatus { - // Array of all known reserves (possibly empty!) - reserves: ReserveStatusEntry[]; + namespace HistoricReserveSummary { + class ClassHistoricReserveSummary { + data: HistoricReserveSummaryDetail[]; } - interface ReserveStatusEntry { - // Public key of the reserve - reserve_pub: EddsaPublicKey; - - // Timestamp when it was established - creation_time: Timestamp; - // Timestamp when it expires - expiration_time: Timestamp; - - // Initial amount as per reserve creation call - merchant_initial_amount: Amount; - - // Initial amount as per exchange, 0 if exchange did - // not confirm reserve creation yet. - exchange_initial_amount: Amount; - - // Amount picked up so far. - pickup_amount: Amount; - - // Amount approved for rewards that exceeds the pickup_amount. - committed_amount: Amount; - - // Is this reserve active (false if it was deleted but not purged) - active: boolean; + interface SummaryResponse { + responseData: HistoricReserveSummaryDetail[]; } - interface ReserveCreateRequest { - // Amount that the merchant promises to put into the reserve - initial_balance: Amount; - - // Exchange the merchant intends to use for reward - exchange_url: string; - - // Desired wire method, for example "iban" or "x-taler-bank" - wire_method: string; + interface HistoricReserveSummaryDetail + { + denom_pub_hash: string; + revenue_timestamp: number; + revenue_balance: string; + loss_balance: string; + suppressed: boolean; } - interface ReserveCreateConfirmation { - // Public key identifying the reserve - reserve_pub: EddsaPublicKey; + } - // Wire accounts of the exchange where to transfer the funds. - accounts: WireAccount[]; + namespace MisattributionInInconsistency { + class ClassMisattributionInInconsistency { + data: MisattributionInInconsistencyDetail[]; } - interface RewardCreateRequest { - // Amount that the customer should be reward - amount: Amount; - - // Justification for giving the reward - justification: string; - // URL that the user should be directed to after rewarding, - // will be included in the reward_token. - next_url: string; + interface SummaryResponse { + responseData: MisattributionInInconsistencyDetail[]; } - interface RewardCreateConfirmation { - // Unique reward identifier for the reward that was created. - reward_id: HashCode; - // taler://reward URI for the reward - taler_reward_uri: string; - - // URL that will directly trigger processing - // the reward when the browser is redirected to it - reward_status_url: string; - - // when does the reward expire - reward_expiration: Timestamp; + interface MisattributionInInconsistencyDetail + { + row_id: number; + amount: string; + bank_row: number; + reserve_pub: string; + suppressed: boolean; } + } - interface ReserveDetail { - // Timestamp when it was established. - creation_time: Timestamp; - - // Timestamp when it expires. - expiration_time: Timestamp; - - // Initial amount as per reserve creation call. - merchant_initial_amount: Amount; - - // Initial amount as per exchange, 0 if exchange did - // not confirm reserve creation yet. - exchange_initial_amount: Amount; - - // Amount picked up so far. - pickup_amount: Amount; - - // Amount approved for rewards that exceeds the pickup_amount. - committed_amount: Amount; - - // Array of all rewards created by this reserves (possibly empty!). - // Only present if asked for explicitly. - rewards?: RewardStatusEntry[]; - - // Is this reserve active (false if it was deleted but not purged)? - active: boolean; - - // Array of wire accounts of the exchange that could - // be used to fill the reserve, can be NULL - // if the reserve is inactive or was already filled - accounts?: WireAccount[]; - - // URL of the exchange hosting the reserve, - // NULL if the reserve is inactive - exchange_url: string; + namespace Progress { + class ClassProgress { + data: ProgressDetail[]; } - interface RewardStatusEntry { - // Unique identifier for the reward. - reward_id: HashCode; - - // Total amount of the reward that can be withdrawn. - total_amount: Amount; - - // Human-readable reason for why the reward was granted. - reason: string; + interface SummaryResponse { + responseData: ProgressDetail[]; } - interface RewardDetails { - // Amount that we authorized for this reward. - total_authorized: Amount; - - // Amount that was picked up by the user already. - total_picked_up: Amount; - - // Human-readable reason given when authorizing the reward. - reason: string; - - // Timestamp indicating when the reward is set to expire (may be in the past). - expiration: Timestamp; - - // Reserve public key from which the reward is funded. - reserve_pub: EddsaPublicKey; + interface ProgressDetail + { + progress_key: string; + progress_offset: number; + suppressed: boolean; + } + } - // Array showing the pickup operations of the wallet (possibly empty!). - // Only present if asked for explicitly. - pickups?: PickupDetail[]; + namespace PurseNotClosedInconsistency { + class ClassPurseNotClosedInconsistency { + data: PurseNotClosedInconsistencyDetail[]; } - interface PickupDetail { - // Unique identifier for the pickup operation. - pickup_id: HashCode; - // Number of planchets involved. - num_planchets: Integer; + interface SummaryResponse { + responseData: PurseNotClosedInconsistencyDetail[]; + } - // Total amount requested for this pickup_id. - requested_amount: Amount; + interface PurseNotClosedInconsistencyDetail + { + row_id: number; + purse_pub: string, + amount: string; + expiration_date: number; + suppressed: boolean; } } - namespace Transfers { - interface TransferList { - // list of all the transfers that fit the filter that we know - transfers: TransferDetails[]; + namespace Purses { + class ClassPurses { + data: PursesDetail[]; } - interface TransferDetails { - // how much was wired to the merchant (minus fees) - credit_amount: Amount; - - // raw wire transfer identifier identifying the wire transfer (a base32-encoded value) - wtid: string; - - // target account that received the wire transfer - payto_uri: string; - - // base URL of the exchange that made the wire transfer - exchange_url: string; - // Serial number identifying the transfer in the merchant backend. - // Used for filgering via offset. - transfer_serial_id: number; - - // Time of the execution of the wire transfer by the exchange, according to the exchange - // Only provided if we did get an answer from the exchange. - execution_time?: Timestamp; - - // True if we checked the exchange's answer and are happy with it. - // False if we have an answer and are unhappy, missing if we - // do not have an answer from the exchange. - verified?: boolean; - - // True if the merchant uses the POST /transfers API to confirm - // that this wire transfer took place (and it is thus not - // something merely claimed by the exchange). - confirmed?: boolean; + interface SummaryResponse { + responseData: PursesDetail[]; } - interface TransferInformation { - // how much was wired to the merchant (minus fees) - credit_amount: Amount; - - // raw wire transfer identifier identifying the wire transfer (a base32-encoded value) - wtid: WireTransferIdentifierRawP; - - // target account that received the wire transfer - payto_uri: string; - - // base URL of the exchange that made the wire transfer - exchange_url: string; + interface PursesDetail + { + auditor_purses_rowid: number; + purse_pub: string; + balance: string; + target: string, + expiration_date: number; + suppressed: boolean; } } - namespace OTP { - interface OtpDeviceAddDetails { - // Device ID to use. - otp_device_id: string; - - // Human-readable description for the device. - otp_device_description: string; - - // A base64-encoded key - otp_key: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; + namespace RefreshesHanging { + class ClassRefreshesHanging { + data: RefreshesHangingDetail[]; } - interface OtpDevicePatchDetails { - // Human-readable description for the device. - otp_device_description: string; - - // A base64-encoded key - otp_key: string | undefined; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; + interface SummaryResponse { + responseData: RefreshesHangingDetail[]; } - interface OtpDeviceSummaryResponse { - // Array of devices that are present in our backend. - otp_devices: OtpDeviceEntry[]; + interface RefreshesHangingDetail + { + row_id: number; + amount: string; + coin_pub: string; + suppressed: boolean; } - interface OtpDeviceEntry { - // Device identifier. - otp_device_id: string; + } - // Human-readable description for the device. - device_description: string; + namespace ReserveBalanceInsufficientInconsistency { + class ClassReserveBalanceInsufficientInconsistency { + data: ReserveBalanceInsufficientInconsistencyDetail[]; } - interface OtpDeviceDetails { - // Human-readable description for the device. - device_description: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; + interface SummaryResponse { + responseData: ReserveBalanceInsufficientInconsistencyDetail[]; } - + interface ReserveBalanceInsufficientInconsistencyDetail + { + row_id: number; + reserve_pub: string; + inconsistency_gain: boolean; + inconsistency_amount: string; + suppressed: boolean; + } } - namespace Template { - interface TemplateAddDetails { - // Template ID to use. - template_id: string; - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; + namespace ReserveBalanceSummaryWrongInconsistency { + class ClassReserveBalanceSummaryWrongInconsistency { + data: ReserveBalanceSummaryWrongInconsistencyDetail[]; } - interface TemplateContractDetails { - // Human-readable summary for the template. - summary?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: Amount; - // Minimum age buyer must have (in years). Default is 0. - minimum_age: Integer; - - // The time the customer need to pay before his order will be deleted. - // It is deleted if the customer did not pay and if the duration is over. - pay_duration: RelativeTime; + interface SummaryResponse { + responseData: ReserveBalanceSummaryWrongInconsistencyDetail[]; } - interface TemplatePatchDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - // Additional information in a separate template. - template_contract: TemplateContractDetails; + interface ReserveBalanceSummaryWrongInconsistencyDetail + { + row_id: number; + reserve_pub: string; + exchange_amount: string; + auditor_amount: string; + suppressed: boolean; } + } - interface TemplateSummaryResponse { - // List of templates that are present in our backend. - templates: TemplateEntry[]; + namespace ReserveInInconsistency { + class ClassReserveInInconsistency { + data: ReserveInInconsistencyDetail[]; } - interface TemplateEntry { - // Template identifier, as found in the template. - template_id: string; - - // Human-readable description for the template. - template_description: string; + interface SummaryResponse { + responseData: ReserveInInconsistencyDetail[]; } - interface TemplateDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; + interface ReserveInInconsistencyDetail + { + row_id: number; + amount_exchange_expected: string; + amount_wired: string; + reserve_pub: string; + timestamp: number; + account: string; + diagnostic: string; + suppressed: boolean; } + } - interface UsingTemplateDetails { - // Subject of the template - summary?: string; + namespace ReserveNotClosedInconsistency { + class ClassReserveNotClosedInconsistency { + data: ReserveNotClosedInconsistencyDetail[]; + } - // The amount entered by the customer. - amount?: Amount; + interface SummaryResponse { + responseData: ReserveNotClosedInconsistencyDetail[]; } - interface UsingTemplateResponse { - // After enter the request. The user will be pay with a taler URL. - order_id: string; - token: string; + interface ReserveNotClosedInconsistencyDetail + { + row_id: number; + reserve_pub: string; + balance: string; + expiration_time: number; + diagnostic: string; + suppressed: boolean; } } - namespace Webhooks { - type MerchantWebhookType = "pay" | "refund"; - interface WebhookAddDetails { - // Webhook ID to use. - webhook_id: string; - - // The event of the webhook: why the webhook is used. - event_type: MerchantWebhookType; - - // URL of the webhook where the customer will be redirected. - url: string; + namespace Reserves { + class ClassReserves{ + data: ReservesDetail[]; + } - // Method used by the webhook - http_method: string; + interface SummaryResponse { + responseData: ReservesDetail[]; + } - // Header template of the webhook - header_template?: string; + interface ReservesDetail + { + auditor_reserves_rowid: number; + reserve_pub: string; + reserve_balance: string; + reserve_loss: string; + withdraw_fee_balance: string; + close_fee_balance: string; + purse_fee_balance: string; + open_fee_balance: string; + history_fee_balance: string; + expiration_date: number; + origin_account: string; + suppressed: boolean; + } + } - // Body template by the webhook - body_template?: string; + namespace RowInconsistency { + class ClassRowInconsistency { + data: RowInconsistencyDetail[]; } - interface WebhookPatchDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - // URL of the webhook where the customer will be redirected. - url: string; + interface SummaryResponse { + responseData: RowInconsistencyDetail[]; + } - // Method used by the webhook - http_method: string; + interface RowInconsistencyDetail + { + row_id: number; + row_table: string; + diagnostic: string; + suppressed: boolean; + } + } - // Header template of the webhook - header_template?: string; + namespace RowMinorInconsistency { + class ClassRowMinorInconsistency { + data: RowMinorInconsistencyDetail[]; + } - // Body template by the webhook - body_template?: string; + interface SummaryResponse { + responseData: RowMinorInconsistencyDetail[]; } - interface WebhookSummaryResponse { - // List of webhooks that are present in our backend. - webhooks: WebhookEntry[]; + + interface RowMinorInconsistencyDetail + { + row_id: number; + row_table: string; + diagnostic: string; + suppressed: boolean; } - interface WebhookEntry { - // Webhook identifier, as found in the webhook. - webhook_id: string; + } - // The event of the webhook: why the webhook is used. - event_type: string; + namespace WireFormatInconsistency { + class ClassWireFormatInconsistency { + data: WireFormatInconsistencyDetail[]; } - interface WebhookDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - // URL of the webhook where the customer will be redirected. - url: string; + interface SummaryResponse { + responseData: WireFormatInconsistencyDetail[]; + } - // Method used by the webhook - http_method: string; + interface WireFormatInconsistencyDetail + { + row_id: number; + amount: string; + wire_offset: string; + diagnostic: string; + suppressed: boolean; + } + } - // Header template of the webhook - header_template?: string; + namespace WireOutInconsistency { + class ClassWireOutInconsistency{ + data: WireOutInconsistencyDetail[]; + } - // Body template by the webhook - body_template?: string; + interface SummaryResponse { + responseData: WireOutInconsistencyDetail[]; } - } - interface ContractTerms { - // Human-readable description of the whole purchase - summary: string; - - // Map from IETF BCP 47 language tags to localized summaries - summary_i18n?: { [lang_tag: string]: string }; - - // Unique, free-form identifier for the proposal. - // Must be unique within a merchant instance. - // For merchants that do not store proposals in their DB - // before the customer paid for them, the order_id can be used - // by the frontend to restore a proposal from the information - // encoded in it (such as a short product identifier and timestamp). - order_id: string; - - // Total price for the transaction. - // The exchange will subtract deposit fees from that amount - // before transferring it to the merchant. - amount: Amount; - - // The URL for this purchase. Every time is is visited, the merchant - // will send back to the customer the same proposal. Clearly, this URL - // can be bookmarked and shared by users. - fulfillment_url?: string; - - // Maximum total deposit fee accepted by the merchant for this contract - max_fee: Amount; - - // List of products that are part of the purchase (see Product). - products: Product[]; - - // Time when this contract was generated - timestamp: TalerProtocolTimestamp; - - // After this deadline has passed, no refunds will be accepted. - refund_deadline: TalerProtocolTimestamp; - - // After this deadline, the merchant won't accept payments for the contact - pay_deadline: TalerProtocolTimestamp; - - // Transfer deadline for the exchange. Must be in the - // deposit permissions of coins used to pay for this order. - wire_transfer_deadline: TalerProtocolTimestamp; - - // Merchant's public key used to sign this proposal; this information - // is typically added by the backend Note that this can be an ephemeral key. - merchant_pub: EddsaPublicKey; - - // Base URL of the (public!) merchant backend API. - // Must be an absolute URL that ends with a slash. - merchant_base_url: string; - - // More info about the merchant, see below - merchant: Merchant; - - // The hash of the merchant instance's wire details. - h_wire: HashCode; - - // Wire transfer method identifier for the wire method associated with h_wire. - // The wallet may only select exchanges via a matching auditor if the - // exchange also supports this wire method. - // The wire transfer fees must be added based on this wire transfer method. - wire_method: string; - - // Any exchanges audited by these auditors are accepted by the merchant. - auditors: Auditor[]; - - // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - exchanges: Exchange[]; - - // Delivery location for (all!) products. - delivery_location?: Location; - - // Time indicating when the order should be delivered. - // May be overwritten by individual products. - delivery_date?: TalerProtocolTimestamp; - - // Nonce generated by the wallet and echoed by the merchant - // in this field when the proposal is generated. - nonce: string; - - // Specifies for how long the wallet should try to get an - // automatic refund for the purchase. If this field is - // present, the wallet should wait for a few seconds after - // the purchase and then automatically attempt to obtain - // a refund. The wallet should probe until "delay" - // after the payment was successful (i.e. via long polling - // or via explicit requests with exponential back-off). - // - // In particular, if the wallet is offline - // at that time, it MUST repeat the request until it gets - // one response from the merchant after the delay has expired. - // If the refund is granted, the wallet MUST automatically - // recover the payment. This is used in case a merchant - // knows that it might be unable to satisfy the contract and - // desires for the wallet to attempt to get the refund without any - // customer interaction. Note that it is NOT an error if the - // merchant does not grant a refund. - auto_refund?: RelativeTime; - - // Extra data that is only interpreted by the merchant frontend. - // Useful when the merchant needs to store extra information on a - // contract without storing it separately in their database. - extra?: any; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; + interface WireOutInconsistencyDetail + { + row_id: number; + destination_account: string; + expected: string; + claimed: string; + suppressed: boolean; + } } -} +}
\ No newline at end of file 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 diff --git a/packages/auditor-backoffice-ui/src/index.tsx b/packages/auditor-backoffice-ui/src/index.tsx index 7fdf7c1c3..fc956e8aa 100644 --- a/packages/auditor-backoffice-ui/src/index.tsx +++ b/packages/auditor-backoffice-ui/src/index.tsx @@ -21,4 +21,4 @@ import "./scss/main.scss"; const app = document.getElementById("app"); -render(<Application />, app as any); +render(<Application />, app as any);
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/default/Table.tsx b/packages/auditor-backoffice-ui/src/paths/default/Table.tsx new file mode 100644 index 000000000..9bb75907d --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/default/Table.tsx @@ -0,0 +1,155 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; +import { StateUpdater, useState } from "preact/hooks"; +import { useEntityContext, useEntityDataContext } from "../../context/entity.js"; + +interface Props { + onSuppress: (id: any) => void; +} + +export function CardTable({onSuppress}: Props): any { + + const data = useEntityDataContext(); + const [rowSelection, rowSelectionHandler] = useState<string | undefined>( + undefined, + ); + const { i18n } = useTranslationContext(); + const { title, endpoint, entity } = useEntityContext(); + + return ( + <div class="card has-table"> + <header class="card-header"> + <p class="card-header-title"> + <span class="icon"> + <i class="mdi mdi-shopping" /> + </span> + <i18n.Translate>{title}</i18n.Translate> + </p> + <div class="card-header-icon" aria-label="more options"> + </div> + </header> + <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} + /> + ) : ( + <EmptyTable /> + )} + </div> + </div> + </div> + </div> + ); +} + +interface TableProps { + data: any; + onSuppress: (id: any) => void; +} + +function Table({ + data, + onSuppress, + }: TableProps): VNode { + const { i18n } = useTranslationContext(); + const { entity } = useEntityContext(); + type Entity = typeof entity; + let count = 0; + + return ( + <div class="table-container"> + <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> + <thead> + <tr> + {Object.keys(data[0]).map((i: Entity) => { + const paramName = i[0].toUpperCase() + i.replace("_", " ").slice(1, i.count); + return ( + <Fragment key={count.toString() + i}> + <th> + <i18n.Translate>{paramName}</i18n.Translate> + </th> + </Fragment>); + })} + </tr> + </thead> + <tbody> + {data.map((key: Entity, value: string) => { + return ( + <tr> + {Object.keys(data[0]).map((i: Entity) => { + return ( + <Fragment> + <td> + {(key[i] == false) ? "false" : key[i]} + </td> + </Fragment> + ); + })} + <td class="is-actions-cell right-sticky"> + <div class="buttons is-right"> + <span + class="has-tooltip-bottom" + data-tooltip={i18n.str`suppress`} + > + <button + class="button is-small is-success " + type="button" + onClick={(): void => onSuppress(key["row_id"])} + > + {<i18n.Translate>Suppress</i18n.Translate>} + </button> + </span> + </div> + </td> + </tr> + ); + }) + } + </tbody> + </table> + </div> + ); +} + +function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); + return ( + <div class="content has-text-grey has-text-centered"> + <p> + <span class="icon is-large"> + <i class="mdi mdi-emoticon-happy mdi-48px" /> + </span> + </p> + <p> + <i18n.Translate> + There are no entries yet + </i18n.Translate> + </p> + </div> + ); +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/default/index.tsx b/packages/auditor-backoffice-ui/src/paths/default/index.tsx new file mode 100644 index 000000000..1b7758190 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/default/index.tsx @@ -0,0 +1,130 @@ +/* + 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/> + */ + +/** + * @author Nic Eigel + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { + ErrorType, + HttpError, + useTranslationContext, +} from "@gnu-taler/web-util/browser"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { Loading } from "../../components/exception/loading.js"; +import { NotificationCard } from "../../components/menu/index.js"; +import { AuditorBackend, WithId } from "../../declaration.js"; +import { Notification } from "../../utils/types.js"; +import { CardTable } from "./Table.js"; +import { HttpStatusCode } from "@gnu-taler/taler-util"; +import { EntityDataContextProvider, useEntityContext } from "../../context/entity.js"; +import { getEntityList, useEntityAPI } from "../../hooks/entity.js"; +import { useMemo } from "preact/hooks"; +import { ConfirmModal, DeleteModal } from "../../components/modal/index.js"; +import { route } from "preact-router"; +import { Paths } from "../../InstanceRoutes.js"; + + +interface Props { + onNotFound: () => VNode; + onLoadError: (e: HttpError<AuditorBackend.ErrorDetail>) => VNode; +} + +export default function DefaultList({ + onLoadError, + onNotFound, + }: Props): VNode { + const { endpoint, entity } = useEntityContext(); + const result = getEntityList({ endpoint, entity }); + const { updateEntity } = useEntityAPI(); + const [suppressing, setSuppressing] = + useState<typeof entity & WithId | null>(null); + const [notif, setNotif] = useState<Notification | undefined>(undefined); + const { i18n } = useTranslationContext(); + + if (result.loading) return <Loading />; + if (!result.ok) { + if ( + result.type === ErrorType.CLIENT && + result.status === HttpStatusCode.Unauthorized + ) + return onNotFound(); + return onLoadError(result); + } + + let data = result.data; + const value = useMemo( + () => ({ data }), + [data], + ); + + function onReturn(): void { + route(Paths.detail_view); + } + + return ( + + <section class="section is-main-section"> + <button + class="button is-fullwidth" + onClick={onReturn} + >Back + </button><br /> + + <NotificationCard notification={notif} /> + + <EntityDataContextProvider value={value}> + <CardTable + onSuppress={(e: typeof entity & WithId) => + setSuppressing(e) + } + /> + </EntityDataContextProvider> + + {suppressing && ( + <ConfirmModal + label={`Suppress row`} + description={`Suppress the row`} + danger + active + onCancel={() => setSuppressing(null)} + onConfirm={async (): Promise<void> => { + try { + await updateEntity(suppressing); + setNotif({ + message: i18n.str`Entity row with id: ${suppressing} has been suppressed`, + type: "SUCCESS", + }); + } catch (error) { + setNotif({ + message: i18n.str`Failed to suppress row`, + type: "ERROR", + description: error instanceof Error ? error.message : undefined, + }); + } + setSuppressing(null); + }} + > + <p class="warning"> + Suppressing a row <b>cannot be undone</b> in this GUI. + </p> + </ConfirmModal> + )} + </section> + ); +} diff --git a/packages/auditor-backoffice-ui/src/paths/details/ListPage.tsx b/packages/auditor-backoffice-ui/src/paths/details/ListPage.tsx new file mode 100644 index 000000000..60ae7b578 --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/details/ListPage.tsx @@ -0,0 +1,346 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode, Fragment } from "preact"; +import { route, Route } from "preact-router"; +import { Paths, Redirect } from "../../InstanceRoutes.js"; +import { AuditorBackend } from "../../declaration.js"; + +export interface ListPageProps { + onShowAll: () => void; + onShowNotPaid: () => void; + onShowPaid: () => void; + onShowRefunded: () => void; + onShowNotWired: () => void; + onShowWired: () => void; + onCopyURL: (id: string) => void; + isAllActive: string; + isPaidActive: string; + isNotPaidActive: string; + isRefundedActive: string; + isNotWiredActive: string; + isWiredActive: string; + + jumpToDate?: Date; + onSelectDate: (date?: Date) => void; + + onLoadMoreBefore?: () => void; + hasMoreBefore?: boolean; + hasMoreAfter?: boolean; + onLoadMoreAfter?: () => void; + + onCreate: () => void; +} + +export function ListPage(): VNode { + const { i18n } = useTranslationContext(); + + return ( + <Fragment> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.amount_arithmethic_inconsistency_list) } + value={"Amount arithmetic inconsistencies"} + >Amount arithmetic inconsistencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.bad_sig_losses_list) } + value={"Bad signature losses"} + >Bad signature losses + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.closure_lag_list) } + >Closure Lags + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.coin_inconsistency_list) } + >Coin inconsistencies + </button> + </div> + </div> + </div> + </div> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.denomination_key_validity_withdraw_inconsistency_list) } + >Denominations key validity + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.denomination_without_sig_list) } + >Denominations without signature + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <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 + </button> + </div> + </div> + </div> + </div> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.emergency_list) } + >Emergencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.emergency_by_count_list) } + >Emergencies by count + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.fee_time_inconsistency_list) } + >Fee time inconsistencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.misattribution_in_inconsistency_list) } + >Misattribution in inconsistencies + </button> + </div> + </div> + </div> + </div> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.purse_not_closed_inconsistency_list) } + >Purses not closed + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.purse_list) } + >Purses + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <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) } + >Reserve balances insufficient + </button> + </div> + </div> + </div> + </div> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.reserve_balance_summary_wrong_inconsistency_list) } + >Reserve balances summary wrong + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.reserve_in_inconsistency_list) } + >Reserves in + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.reserve_not_closed_inconsistency_list) } + >Reserves not closed + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.reserves_list) } + >Reserves + </button> + </div> + </div> + </div> + </div> + + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.row_inconsistency_list) } + >Row inconsistencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.row_minor_inconsistency_list) } + >Row minor inconsistencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.wire_format_inconsistency_list) } + >Wire format inconsistencies + </button> + </div> + </div> + </div> + <div class="column"> + <div class="card"> + <div class="card-body"> + <button + class="button is-fullwidth" + onClick={(e) => route(Paths.wire_out_inconsistency_list) } + >Wire out inconsistencies + </button> + </div> + </div> + </div> + </div> + + </Fragment> + ); +} diff --git a/packages/auditor-backoffice-ui/src/paths/instance/transfers/update/index.tsx b/packages/auditor-backoffice-ui/src/paths/details/index.tsx index 719f99209..f99dae7e5 100644 --- a/packages/auditor-backoffice-ui/src/paths/instance/transfers/update/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/details/index.tsx @@ -16,11 +16,24 @@ /** * + * @author Nic Eigel * @author Sebastian Javier Marchano (sebasjm) */ import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { NotificationCard } from "../../components/menu/index.js"; +import { Notification } from "../../utils/types.js"; +import { ListPage } from "./ListPage.js"; -export default function UpdateTransfer(): VNode { - return <div>order transfer page</div>; -} +export default function DetailsDashboard(): VNode { + + const [notif, setNotif] = useState<Notification | undefined>(undefined); + + return ( + <section class="section is-main-section"> + <NotificationCard notification={notif} /> + <ListPage /> + </section> + ); +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/finance/ListPage.tsx b/packages/auditor-backoffice-ui/src/paths/finance/ListPage.tsx new file mode 100644 index 000000000..88ca6bcfd --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/finance/ListPage.tsx @@ -0,0 +1,214 @@ +/* + 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/> + */ + +/** + * + * @author Nic Eigel + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode, Fragment } from "preact"; + +export function ListPage(data: any): VNode { + const { i18n } = useTranslationContext(); + + let balances = data.data.data[0][4].data.balances; + let coinBalances = [ + "Total recoup loss", + "Coin refund fee revenue", + "Coin deposit fee revenue", + "Coin melt fee revenue", + "Coin irregular loss", + "Coins reported emergency risk by amount", + "Coins emergencies loss by count", + "Coins emergencies loss", + "Coins total arithmetic delta minus", + "Coins total arithmetic delta plus", + "Total escrowed", + "Total refresh hanging", + ]; + let reserveBalances = [ + "Total balance summary delta minus", + "Total balance reserve not closed", + "Reserves total arithmetic delta minus", + "Reserves total arithmetic delta plus", + "Reserves total bad signature loss", + "Reserves history fee revenue", + "Reserves open fee revenue", + ]; + let i = 0; + + return ( + <Fragment> + <div class="columns"> + <div class="column is-half"> + <div class="columns"> + <div class="column"> + <div class="card"> + <div class="card-content"> + <table class="table is-striped is-fullwidth is-dark"> + <tbody> + <tr> + <th>Finding</th> + <td class="has-text-right"><b>Count</b></td> + <td class="has-text-right"><b>Gain/Loss</b></td> + </tr> + { + data["data"]["data"][0].map((x: any) => { + 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") { + //TODO fix + let gains = 0; + if (value == null) + value = 0; + else + value = Object.keys(value).length; + + return ( + <tr class="is-link"> + <td>{paramName}</td> + <td class="has-text-right"><p + class={value == 0 ? "text-success" : "text-danger"}>{String(value)}</p></td> + <td class="has-text-right"><p + class={gains == 0 ? "text-success" : "text-danger"}>{String(gains)}</p></td> + </tr> + ); + } else { + <tr class="is-link"> + <td>{paramName}</td> + <td class="has-text-right"><p + class={value == 0 ? "text-success" : "text-danger"}>{String(value)}</p></td> + <td class="has-text-right"><p>{ + //TODO + }</p></td> + </tr>; + } + }) + } + </tbody> + </table> + </div> + </div> + <div class="card"> + <div class="card-content"> + <table class="table is-striped is-fullwidth is-dark"> + <tbody> + <tr> + <th>Summary</th> + <td class="has-text-right"><b>Value</b></td> + </tr> + <tr> + <td>Total gain/loss</td> + <td class="has-text-right">{ + //TODO fix + }</td> + </tr> + <tr> + <td>Pending gain/loss</td> + <td class="has-text-right">{ + //TODO fix + }</td> + </tr> + <tr> + <td>Transaction count</td> + <td class="has-text-right">{ + //TODO fix + }</td> + </tr> + <tr> + <td>Transactions pending</td> + <td class="has-text-right">{ + //TODO fix + }</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + </div> + <div class="column is-half"> + <div class="card"> + <div class="card-content"> + <p class="has-text-weight-bold">Helper coin</p> + <table class="table is-striped is-fullwidth is-dark"> + <tbody> + <tr> + <th>Balance</th> + <td><b>Value</b></td> + </tr> + { + balances.map((x: any) => { + let key = x.balance_key; + let balanceName = key[0].toUpperCase() + key.split("_").join(" ").split("-").join(" ").slice(1, key.length); + + if(coinBalances.includes(balanceName)) + { + let value = balances[i].balance_value.replace(":", " "); + i=i+1; + return ( + <tr class="is-link"> + <td>{balanceName}</td> + <td><p>{value}</p></td> + </tr> + ); + } else { + return null; + } + }) + } + </tbody> + </table> + <p class="has-text-weight-bold">Helper reserve</p> + <table class="table is-striped is-fullwidth is-dark"> + <tbody> + <tr> + <th>Balance</th> + <td><b>Value</b></td> + </tr> + { + balances.map((x: any) => { + let key = x.balance_key; + let balanceName = key[0].toUpperCase() + key.split("_").join(" ").split("-").join(" ").slice(1, key.length); + + if(reserveBalances.includes(balanceName)) + { + let value = balances[i].balance_value.replace(":", " "); + i = i+1; + return ( + <tr class="is-link"> + <td>{balanceName}</td> + <td><p>{value}</p></td> + </tr> + ); + } else { + return null; + } + }) + } + </tbody> + </table> + </div> + </div> + </div> + </div> + </Fragment> + ); +} diff --git a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/update/index.tsx b/packages/auditor-backoffice-ui/src/paths/finance/index.tsx index 2d3e7bd6b..b0d07aa0f 100644 --- a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/update/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/finance/index.tsx @@ -16,43 +16,41 @@ /** * + * @author Nic Eigel * @author Sebastian Javier Marchano (sebasjm) */ import { ErrorType, - HttpError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Loading } from "../../../../components/exception/loading.js"; -import { NotificationCard } from "../../../../components/menu/index.js"; -import { MerchantBackend } from "../../../../declaration.js"; -import { useProductAPI, useProductDetails } from "../../../../hooks/product.js"; -import { Notification } from "../../../../utils/types.js"; -import { UpdatePage } from "./UpdatePage.js"; +import { Loading } from "../../components/exception/loading.js"; +import { NotificationCard } from "../../components/menu/index.js"; +import { Notification } from "../../utils/types.js"; +import { ListPage } from "./ListPage.js"; import { HttpStatusCode } from "@gnu-taler/taler-util"; +import { getKeyFiguresData } from "../../hooks/finance.js"; + -export type Entity = MerchantBackend.Products.ProductAddDetail; interface Props { - onBack?: () => void; - onConfirm: () => void; onUnauthorized: () => VNode; onNotFound: () => VNode; - onLoadError: (e: HttpError<MerchantBackend.ErrorDetail>) => VNode; - pid: string; + onSelect: (id: string) => void; + onCreate: () => void; } -export default function UpdateProduct({ - pid, - onConfirm, - onBack, - onUnauthorized, - onNotFound, - onLoadError, -}: Props): VNode { - const { updateProduct } = useProductAPI(); - const result = useProductDetails(pid); + +export default function FinanceDashboard({ + onUnauthorized, + // onLoadError, + onCreate, + onSelect, + onNotFound, + }: Props): VNode { + + const result = getKeyFiguresData(); + const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); @@ -69,27 +67,14 @@ export default function UpdateProduct({ result.status === HttpStatusCode.NotFound ) return onNotFound(); - return onLoadError(result); + else + return onNotFound(); } return ( - <Fragment> + <section class="section is-main-section"> <NotificationCard notification={notif} /> - <UpdatePage - product={{ ...result.data, product_id: pid }} - onBack={onBack} - onUpdate={(data) => { - return updateProduct(pid, data) - .then(onConfirm) - .catch((error) => { - setNotif({ - message: i18n.str`could not create product`, - type: "ERROR", - description: error.message, - }); - }); - }} - /> - </Fragment> + <ListPage data={result} /> + </section> ); -} +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx b/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx deleted file mode 100644 index a99cfd2ef..000000000 --- a/packages/auditor-backoffice-ui/src/paths/instance/deposit_confirmations/list/index.tsx +++ /dev/null @@ -1,126 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - * @author Nic Eigel - */ - -import { - ErrorType, - HttpError, - useTranslationContext, -} from "@gnu-taler/web-util/browser"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { Loading } from "../../../../components/exception/loading.js"; -import { NotificationCard } from "../../../../components/menu/index.js"; -import { AuditorBackend, WithId } from "../../../../declaration.js"; -import { - useDepositConfirmation, - useDepositConfirmationAPI, -} from "../../../../hooks/deposit_confirmations.js"; -import { Notification } from "../../../../utils/types.js"; -import { CardTable } from "./Table.js"; -import { HttpStatusCode } from "@gnu-taler/taler-util"; -import { ConfirmModal, DeleteModal } from "../../../../components/modal/index.js"; -import { JumpToElementById } from "../../../../components/form/JumpToElementById.js"; - -interface Props { - onUnauthorized: () => VNode; - onNotFound: () => VNode; - onCreate: () => void; - onSelect: (id: string) => void; - onLoadError: (e: HttpError<AuditorBackend.ErrorDetail>) => VNode; -} -export default function DepositConfirmationList({ - onUnauthorized, - onLoadError, - onCreate, - onSelect, - onNotFound, -}: Props): VNode { - const result = useDepositConfirmation(); - const { deleteDepositConfirmation, updateDepositConfirmation, getDepositConfirmation } = useDepositConfirmationAPI(); - const [deleting, setDeleting] = - useState<AuditorBackend.DepositConfirmation.DepositConfirmationDetail & WithId | null>(null); - const [notif, setNotif] = useState<Notification | undefined>(undefined); - - const { i18n } = useTranslationContext(); - - if (result.loading) return <Loading />; - if (!result.ok) { - if ( - result.type === ErrorType.CLIENT && - result.status === HttpStatusCode.Unauthorized - ) - return onUnauthorized(); - if ( - result.type === ErrorType.CLIENT && - result.status === HttpStatusCode.NotFound - ) - return onNotFound(); - return onLoadError(result); - } - - return ( - <section class="section is-main-section"> - <NotificationCard notification={notif} /> - - <JumpToElementById - testIfExist={getDepositConfirmation} - onSelect={onSelect} - description={i18n.str`jump to deposit_confirmation with the given serial ID`} - placeholder={i18n.str`serial id`} - /> - - {deleting && ( - <ConfirmModal - label={`Delete deposit-confirmation`} - description={`Delete the deposit-cofirmation "${deleting.serial_id}"`} - danger - active - onCancel={() => setDeleting(null)} - onConfirm={async (): Promise<void> => { - try { - await deleteDepositConfirmation(deleting.serial_id); - setNotif({ - message: i18n.str`Deposit-confirmation "${deleting.serial_id}" (ID: ${deleting.serial_id}) has been deleted`, - type: "SUCCESS", - }); - } catch (error) { - setNotif({ - message: i18n.str`Failed to delete deposit-confirmation`, - type: "ERROR", - description: error instanceof Error ? error.message : undefined, - }); - } - setDeleting(null); - }} - > - <p> - If you delete the deposit-confirmation (ID:{" "} - <b>{deleting.serial_id}</b>), the stock and related information will be lost - </p> - <p class="warning"> - Deleting a deposit-confirmation <b>cannot be undone</b>. - </p> - </ConfirmModal> - )} - </section> - ); -} diff --git a/packages/auditor-backoffice-ui/src/paths/login/index.tsx b/packages/auditor-backoffice-ui/src/paths/login/index.tsx index f8990d377..c99dc6050 100644 --- a/packages/auditor-backoffice-ui/src/paths/login/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/login/index.tsx @@ -16,187 +16,198 @@ /** * + * @author Nic Eigel * @author Sebastian Javier Marchano (sebasjm) */ import { useTranslationContext } from "@gnu-taler/web-util/browser"; -import { ComponentChildren, h, VNode } from "preact"; -import { useCallback, useEffect, useState } from "preact/hooks"; -import { useBackendContext } from "../../context/backend.js"; -import { useInstanceContext } from "../../context/instance.js"; -import { AccessToken, LoginToken } from "../../declaration.js"; -import { useCredentialsChecker } from "../../hooks/backend.js"; - -interface Props { - onConfirm: (token: LoginToken | undefined) => void; -} - -function normalizeToken(r: string): AccessToken { - return `secret-token:${r}` as AccessToken; -} - -export function LoginPage({ onConfirm }: Props): VNode { - const { url: backendURL } = useBackendContext(); - const { admin, id } = useInstanceContext(); - const { requestNewLoginToken } = useCredentialsChecker(); +import { ComponentChildren, Fragment, h, VNode } from "preact"; +import { useCallback, useState } from "preact/hooks"; +import { useBackendContext, useBackendTokenContext } from "../../context/backend.js"; +import { NotificationCard } from "../../components/menu/index.js"; +import { Notification } from "../../utils/types.js"; +import { useBackendToken } from "../../hooks/backend.js"; +import { Route } from "preact-router"; +import { Paths, Redirect } from "../../InstanceRoutes.js"; + +export function LoginPage(): VNode { const [token, setToken] = useState(""); - + const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); - - const doLogin = useCallback(async function doLoginImpl() { - const secretToken = normalizeToken(token); - const baseUrl = id === undefined ? backendURL : `${backendURL}/instances/${id}` - const result = await requestNewLoginToken(baseUrl, secretToken); - if (result.valid) { - const { token, expiration } = result - onConfirm({ token, expiration }); + + const result = useBackendToken(); + if (!result.ok) { + } + if (result.ok) { + //TODO fixme + const { token } = useBackendTokenContext(); + /* return ( + <Route path="/" component={Redirect} to={Paths.key_figures}/> + );*/ } else { - onConfirm(undefined); + setNotif({ + message: "Your password is incorrect", + type: "ERROR", + }); } - }, [id, token]) - - if (admin && id !== "default") { - //admin trying to access another instance - return (<div class="columns is-centered" style={{ margin: "auto" }}> - <div class="column is-two-thirds "> - <div class="modal-card" style={{ width: "100%", margin: 0 }}> - <header - class="modal-card-head" - style={{ border: "1px solid", borderBottom: 0 }} - > - <p class="modal-card-title">{i18n.str`Login required`}</p> - </header> - <section - class="modal-card-body" - style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} - > - <p> - <i18n.Translate>Need the access token for the instance.</i18n.Translate> - </p> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <i18n.Translate>Access Token</i18n.Translate> - </label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control is-expanded"> - <input - class="input" - type="password" - placeholder={"current access token"} - name="token" - onKeyPress={(e) => - e.keyCode === 13 - ? doLogin() - : null - } - value={token} - onInput={(e): void => setToken(e?.currentTarget.value)} - /> - </p> - </div> + }, [token]); + + return ( + <Route path="/" component={Redirect} to={Paths.key_figures}/> + ); + + return ( + <div class="columns is-centered" style={{ margin: "auto" }}> + <div class="column is-two-thirds "> + <div class="modal-card" style={{ width: "100%", margin: 0 }}> + <header + class="modal-card-head" + style={{ border: "1px solid", borderBottom: 0 }} + > + <p class="modal-card-title">{i18n.str`Token required`}</p> + </header> + <section + class="modal-card-body" + style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} + > + + <p> + <i18n.Translate>Need the access token for the API.</i18n.Translate> + </p> + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <i18n.Translate>Access Token</i18n.Translate> + </label> + </div> + <div class="field-body"> + <div class="field"> + <p class="control is-expanded"> + <input + class="input" + type="password" + placeholder={"current access token"} + name="token" + onKeyPress={(e) => + e.keyCode === 13 + ? doLogin() + : null + } + value={token} + onInput={(e): void => setToken(e?.currentTarget.value)} + /> + </p> </div> </div> - </section> - <footer - class="modal-card-foot " - style={{ - justifyContent: "flex-end", - border: "1px solid", - borderTop: 0, - }} + </div> + </section> + <footer + class="modal-card-foot " + style={{ + justifyContent: "flex-end", + border: "1px solid", + borderTop: 0, + }} + > + <AsyncButton + onClick={() => doLogin()} > - <AsyncButton - onClick={doLogin} - > - <i18n.Translate>Confirm</i18n.Translate> - </AsyncButton> - </footer> - </div> + <i18n.Translate>Confirm</i18n.Translate> + </AsyncButton> + </footer> </div> - </div>) - } + </div> + </div>); - return ( - <div class="columns is-centered" style={{ margin: "auto" }}> - <div class="column is-two-thirds "> - <div class="modal-card" style={{ width: "100%", margin: 0 }}> - <header - class="modal-card-head" - style={{ border: "1px solid", borderBottom: 0 }} - > - <p class="modal-card-title">{i18n.str`Login required`}</p> - </header> - <section - class="modal-card-body" - style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} - > - <i18n.Translate>Please enter your access token.</i18n.Translate> - <div class="field is-horizontal"> - <div class="field-label is-normal"> - <label class="label"> - <i18n.Translate>Access Token</i18n.Translate> - </label> - </div> - <div class="field-body"> - <div class="field"> - <p class="control is-expanded"> - <input - class="input" - type="password" - placeholder={"current access token"} - name="token" - onKeyPress={(e) => - e.keyCode === 13 - ? doLogin() - : null - } - value={token} - onInput={(e): void => setToken(e?.currentTarget.value)} - /> - </p> + return (<Fragment> + <NotificationCard notification={notif} /> + <div class="columns is-centered" style={{ margin: "auto" }}> + <div class="column is-two-thirds "> + <div class="modal-card" style={{ width: "100%", margin: 0 }}> + <header + class="modal-card-head" + style={{ border: "1px solid", borderBottom: 0 }} + > + <p class="modal-card-title">{i18n.str`Login required`}</p> + </header> + <section + class="modal-card-body" + style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} + > + <i18n.Translate>Please enter your access token.</i18n.Translate> + + <div class="field is-horizontal"> + <div class="field-label is-normal"> + <label class="label"> + <i18n.Translate>Access Token</i18n.Translate> + </label> + </div> + <div class="field-body"> + + <div class="field"> + <p class="control is-expanded"> + <input + class="input" + type="password" + placeholder={"current access token"} + name="token" + onKeyPress={(e) => + e.keyCode === 13 + ? doLogin() + : null + } + value={token} + onInput={(e): void => setToken(e?.currentTarget.value)} + /> + </p> + </div> </div> </div> - </div> - </section> - <footer - class="modal-card-foot " - style={{ - justifyContent: "space-between", - border: "1px solid", - borderTop: 0, - }} - > - <div /> - <AsyncButton - type="is-info" - onClick={doLogin} + </section> + <footer + class="modal-card-foot " + style={{ + justifyContent: "space-between", + border: "1px solid", + borderTop: 0, + }} > - <i18n.Translate>Confirm</i18n.Translate> - </AsyncButton> - </footer> + <div /> + <AsyncButton + type="is-info" + onClick={doLogin} + > + <i18n.Translate>Confirm</i18n.Translate> + </AsyncButton> + + </footer> + </div> </div> </div> - </div> + </Fragment> + ); } -function AsyncButton({ onClick, disabled, type = "", children }: { type?: string, disabled?: boolean, onClick: () => Promise<void>, children: ComponentChildren }): VNode { - const [running, setRunning] = useState(false) +function AsyncButton({ onClick, disabled, type = "", children }: { + type?: string, + disabled?: boolean, + onClick: () => Promise<void>, + children: ComponentChildren +}): VNode { + const [running, setRunning] = useState(false); return <button class={"button " + type} disabled={disabled || running} onClick={() => { - setRunning(true) + setRunning(true); onClick().then(() => { - setRunning(false) + setRunning(false); }).catch(() => { - setRunning(false) - }) + setRunning(false); + }); }}> {children} - </button> + </button>; } diff --git a/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx b/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx index 68adb79bf..114b95219 100644 --- a/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/notfound/index.tsx @@ -23,12 +23,12 @@ import { h, VNode } from "preact"; import { Link } from "preact-router"; export default function NotFoundPage(): VNode { - return ( - <div> - <p>That page doesn't exist.</p> - <Link href="/"> - <h4>Back to Home</h4> - </Link> - </div> - ); + return ( + <div> + <p>That page doesn't exist.</p> + <Link href="/"> + <h4>Back to Home</h4> + </Link> + </div> + ); } diff --git a/packages/auditor-backoffice-ui/src/paths/operations/ListPage.tsx b/packages/auditor-backoffice-ui/src/paths/operations/ListPage.tsx new file mode 100644 index 000000000..7f0579b2b --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/operations/ListPage.tsx @@ -0,0 +1,72 @@ +/* + 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/> + */ + +/** + * + * @author Nic Eigel + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode, Fragment } from "preact"; + +export function ListPage(data: any): VNode { + const { i18n } = useTranslationContext(); + + return ( + <Fragment> + <div class="columns is-fullwidth"> + <div class="column is-fullwidth"> + <div class="card"> + <div class="card-content"> + <table class="table is-striped is-fullwidth"> + <tbody> + <tr> + <th>Finding</th> + <td class="has-text-right"><b>Count</b></td> + <td class="has-text-right"><b>Time difference (s)</b></td> + <td class="has-text-right"><b>Diagnostic</b></td> + </tr> + { + data["data"]["data"][0].map((x: any) => { + 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); + return ( + <tr class="is-link"> + <td>{paramName}</td> + <td className="has-text-right"><p + class={value == 0 ? "text-success" : "text-danger"}>{String(value)}</p></td> + <td className="has-text-right">{//TODO + }</td> + <td>{//TODO + }</td> + </tr> + ); + }) + } + </tbody> + </table> + </div> + </div> + </div> + </div> + </Fragment> + ); +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/instance/products/update/index.tsx b/packages/auditor-backoffice-ui/src/paths/operations/index.tsx index 2d3e7bd6b..c05b271fe 100644 --- a/packages/auditor-backoffice-ui/src/paths/instance/products/update/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/operations/index.tsx @@ -16,43 +16,41 @@ /** * + * @author Nic Eigel * @author Sebastian Javier Marchano (sebasjm) */ import { ErrorType, - HttpError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Loading } from "../../../../components/exception/loading.js"; -import { NotificationCard } from "../../../../components/menu/index.js"; -import { MerchantBackend } from "../../../../declaration.js"; -import { useProductAPI, useProductDetails } from "../../../../hooks/product.js"; -import { Notification } from "../../../../utils/types.js"; -import { UpdatePage } from "./UpdatePage.js"; +import { Loading } from "../../components/exception/loading.js"; +import { NotificationCard } from "../../components/menu/index.js"; +import { Notification } from "../../utils/types.js"; +import { ListPage } from "./ListPage.js"; import { HttpStatusCode } from "@gnu-taler/taler-util"; +import { getOperationData } from "../../hooks/operational.js"; + -export type Entity = MerchantBackend.Products.ProductAddDetail; interface Props { - onBack?: () => void; - onConfirm: () => void; onUnauthorized: () => VNode; onNotFound: () => VNode; - onLoadError: (e: HttpError<MerchantBackend.ErrorDetail>) => VNode; - pid: string; + onSelect: (id: string) => void; + onCreate: () => void; } -export default function UpdateProduct({ - pid, - onConfirm, - onBack, - onUnauthorized, - onNotFound, - onLoadError, -}: Props): VNode { - const { updateProduct } = useProductAPI(); - const result = useProductDetails(pid); + +export default function OperationsDashboard({ + onUnauthorized, + // onLoadError, + onCreate, + onSelect, + onNotFound, + }: Props): VNode { + + const result = getOperationData(); + const [notif, setNotif] = useState<Notification | undefined>(undefined); const { i18n } = useTranslationContext(); @@ -69,27 +67,14 @@ export default function UpdateProduct({ result.status === HttpStatusCode.NotFound ) return onNotFound(); - return onLoadError(result); + else + return onNotFound(); } return ( - <Fragment> + <section class="section is-main-section"> <NotificationCard notification={notif} /> - <UpdatePage - product={{ ...result.data, product_id: pid }} - onBack={onBack} - onUpdate={(data) => { - return updateProduct(pid, data) - .then(onConfirm) - .catch((error) => { - setNotif({ - message: i18n.str`could not create product`, - type: "ERROR", - description: error.message, - }); - }); - }} - /> - </Fragment> + <ListPage data={result} /> + </section> ); -} +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/security/ListPage.tsx b/packages/auditor-backoffice-ui/src/paths/security/ListPage.tsx new file mode 100644 index 000000000..74f83bd4a --- /dev/null +++ b/packages/auditor-backoffice-ui/src/paths/security/ListPage.tsx @@ -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/> + */ + +/** + * + * @author Nic Eigel + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { useTranslationContext } from "@gnu-taler/web-util/browser"; +import { h, VNode, Fragment } from "preact"; + +export function ListPage(data: any): VNode { + const { i18n } = useTranslationContext(); + + return ( + <Fragment> + <div class="columns is-fullwidth"> + <div class="column is-fullwidth"> + <div class="card"> + <div class="card-content"> + <table class="table is-striped is-fullwidth"> + <tbody> + <tr> + <th>Finding</th> + <td class="has-text-right"><b>Count</b></td> + <td class="has-text-right"><b>Expiration dates</b></td> + </tr> + { + data["data"]["data"][0].map((x: any) => { + 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); + return ( + <tr class="is-link"> + <td>{paramName}</td> + <td class="has-text-right"><p + class={value == 0 ? "text-success" : "text-danger"}>{String(value)}</p></td> + <td class="has-text-right">{ + //TODO + }</td> + </tr> + ); + }) + } + </tbody> + </table> + </div> + </div> + </div> + </div> + </Fragment> + ); +} diff --git a/packages/auditor-backoffice-ui/src/paths/instance/templates/qr/index.tsx b/packages/auditor-backoffice-ui/src/paths/security/index.tsx index 65ccc2dcc..99c98a5e7 100644 --- a/packages/auditor-backoffice-ui/src/paths/instance/templates/qr/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/security/index.tsx @@ -16,46 +16,43 @@ /** * + * @author Nic Eigel * @author Sebastian Javier Marchano (sebasjm) */ import { ErrorType, - HttpError, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { Fragment, h, VNode } from "preact"; +import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Loading } from "../../../../components/exception/loading.js"; -import { NotificationCard } from "../../../../components/menu/index.js"; -import { MerchantBackend } from "../../../../declaration.js"; -import { - useTemplateAPI, - useTemplateDetails, -} from "../../../../hooks/templates.js"; -import { Notification } from "../../../../utils/types.js"; -import { QrPage } from "./QrPage.js"; +import { Loading } from "../../components/exception/loading.js"; +import { NotificationCard } from "../../components/menu/index.js"; +import { Notification } from "../../utils/types.js"; +import { ListPage } from "./ListPage.js"; import { HttpStatusCode } from "@gnu-taler/taler-util"; - -export type Entity = MerchantBackend.Transfers.TransferInformation; +import { getCriticalData } from "../../hooks/critical.js"; interface Props { - onBack?: () => void; onUnauthorized: () => VNode; onNotFound: () => VNode; - onLoadError: (e: HttpError<MerchantBackend.ErrorDetail>) => VNode; - tid: string; + onSelect: (id: string) => void; + onCreate: () => void; } -export default function TemplateQrPage({ - tid, - onBack, - onLoadError, - onNotFound, - onUnauthorized, -}: Props): VNode { - const result = useTemplateDetails(tid); +export default function SecurityDashboard({ + onUnauthorized, + // onLoadError, + onCreate, + onSelect, + onNotFound, + }: Props): VNode { + + const result = getCriticalData(); + const [notif, setNotif] = useState<Notification | undefined>(undefined); + const { i18n } = useTranslationContext(); + if (result.loading) return <Loading />; if (!result.ok) { if ( @@ -68,13 +65,14 @@ export default function TemplateQrPage({ result.status === HttpStatusCode.NotFound ) return onNotFound(); - return onLoadError(result); + else + return onNotFound(); } return ( - <> + <section class="section is-main-section"> <NotificationCard notification={notif} /> - <QrPage contract={result.data.template_contract} id={tid} onBack={onBack} /> - </> + <ListPage data={result} /> + </section> ); -} +}
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/paths/settings/index.tsx b/packages/auditor-backoffice-ui/src/paths/settings/index.tsx index 093c3d09d..77a56a794 100644 --- a/packages/auditor-backoffice-ui/src/paths/settings/index.tsx +++ b/packages/auditor-backoffice-ui/src/paths/settings/index.tsx @@ -1,8 +1,28 @@ +/* + 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/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + * @author Nic Eigel + */ + import { useLang, useTranslationContext } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; -import { FormErrors, FormProvider } from "../../components/form/FormProvider.js"; -import { InputSelector } from "../../components/form/InputSelector.js"; -import { InputToggle } from "../../components/form/InputToggle.js"; +import { FormErrors, FormProvider } from "../../components/forms/FormProvider.js"; import { LangSelector } from "../../components/menu/LangSelector.js"; import { Settings, useSettings } from "../../hooks/useSettings.js"; @@ -16,7 +36,7 @@ function getBrowserLang(): string | undefined { export function Settings({ onClose }: { onClose?: () => void }): VNode { const { i18n } = useTranslationContext() const borwserLang = getBrowserLang() - //const { update } = useLang() + const { update } = useLang(undefined, {}) const [value, updateValue] = useSettings() const errors: FormErrors<Settings> = { @@ -60,38 +80,13 @@ export function Settings({ onClose }: { onClose?: () => void }): VNode { data-tooltip={i18n.str`generate random secret key`} class="button is-info mr-2" onClick={(e) => { - //update(borwserLang.substring(0, 2)) + update(borwserLang.substring(0, 2)) }} > <i18n.Translate>Set default</i18n.Translate> </button>} </div> </div> - <InputToggle<Settings> - label={i18n.str`Advance order creation`} - tooltip={i18n.str`Shows more options in the order creation form`} - name="advanceOrderMode" - /> - <InputSelector<Settings> - name="dateFormat" - label={i18n.str`Date format`} - expand={true} - help={ - value.dateFormat === "dmy" ? "31/12/2001" : value.dateFormat === "mdy" ? "12/31/2001" : value.dateFormat === "ymd" ? "2001/12/31" : "" - } - toStr={(e) => { - if (e === "ymd") return "year month day" - if (e === "mdy") return "month day year" - if (e === "dmy") return "day month year" - return "choose one" - }} - values={[ - "ymd", - "mdy", - "dmy", - ]} - tooltip={i18n.str`how the date is going to be displayed`} - /> </FormProvider> </div> </div> diff --git a/packages/auditor-backoffice-ui/src/stories.tsx b/packages/auditor-backoffice-ui/src/stories.tsx deleted file mode 100644 index 8bb06b8cb..000000000 --- a/packages/auditor-backoffice-ui/src/stories.tsx +++ /dev/null @@ -1,48 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ -import { strings } from "./i18n/strings.js"; - -import * as admin from "./paths/admin/index.stories.js"; -import * as instance from "./paths/instance/index.stories.js"; -import * as components from "./components/index.stories.js"; - -import { renderStories } from "@gnu-taler/web-util/browser"; - -import "./scss/main.scss"; - -function SortStories(a: any, b: any): number { - return (a?.order ?? 0) - (b?.order ?? 0); -} - -function main(): void { - renderStories( - { admin, instance, components }, - { - strings, - }, - ); -} - -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", main); -} else { - main(); -} diff --git a/packages/auditor-backoffice-ui/src/sw.js b/packages/auditor-backoffice-ui/src/sw.js deleted file mode 100644 index bf52db6fa..000000000 --- a/packages/auditor-backoffice-ui/src/sw.js +++ /dev/null @@ -1,25 +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/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -// import { getFiles, setupPrecaching, setupRouting } from 'preact-cli/sw/'; - -// setupRouting(); -// setupPrecaching(getFiles()); diff --git a/packages/auditor-backoffice-ui/src/utils/amount.ts b/packages/auditor-backoffice-ui/src/utils/amount.ts index fda997619..0796087ac 100644 --- a/packages/auditor-backoffice-ui/src/utils/amount.ts +++ b/packages/auditor-backoffice-ui/src/utils/amount.ts @@ -12,13 +12,13 @@ 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 { amountFractionalBase, AmountJson, Amounts, } from "@gnu-taler/taler-util"; -import { MerchantBackend } from "../declaration.js"; +import { AuditorBackend } from "../declaration.js"; /** * merge refund with the same description and a difference less than one minute @@ -26,7 +26,7 @@ import { MerchantBackend } from "../declaration.js"; * @param cur new refund to add to the list * @returns list with the new refund, may be merged with the last */ -export function mergeRefunds( +/*export function mergeRefunds( prev: MerchantBackend.Orders.RefundDetails[], cur: MerchantBackend.Orders.RefundDetails, ): MerchantBackend.Orders.RefundDetails[] { @@ -69,3 +69,4 @@ export function rate(a: AmountJson, b: AmountJson): number { function toFloat(amount: AmountJson): number { return amount.value + amount.fraction / amountFractionalBase; } +*/
\ No newline at end of file diff --git a/packages/auditor-backoffice-ui/src/utils/table.ts b/packages/auditor-backoffice-ui/src/utils/table.ts index 306328aa1..1322ad804 100644 --- a/packages/auditor-backoffice-ui/src/utils/table.ts +++ b/packages/auditor-backoffice-ui/src/utils/table.ts @@ -13,14 +13,14 @@ 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 { WithId } from "../declaration.js"; - +*/ /** * * @author Sebastian Javier Marchano (sebasjm) */ - +/* export interface Actions<T extends WithId> { element: T; type: "DELETE" | "UPDATE"; @@ -40,7 +40,7 @@ export function buildActions<T extends WithId>( .filter(notEmpty) .map((id) => ({ element: id, type: action })); } - +*/ /** * For any object or array, return the same object if is not empty. * not empty: @@ -48,10 +48,10 @@ export function buildActions<T extends WithId>( * - for objects: at least one property not undefined * @param obj * @returns - */ + *//* export function undefinedIfEmpty< T extends Record<string, unknown> | Array<unknown>, >(obj: T | undefined): T | undefined { if (obj === undefined) return undefined; return Object.values(obj).some((v) => v !== undefined) ? obj : undefined; -} +}*/ |