summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/components')
-rw-r--r--packages/taler-wallet-webextension/src/components/BalanceTable.tsx61
-rw-r--r--packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx200
-rw-r--r--packages/taler-wallet-webextension/src/components/HistoryItem.tsx68
-rw-r--r--packages/taler-wallet-webextension/src/components/Modal.tsx3
-rw-r--r--packages/taler-wallet-webextension/src/components/PendingTransactions.tsx81
-rw-r--r--packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx2
-rw-r--r--packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx15
-rw-r--r--packages/taler-wallet-webextension/src/components/WalletActivity.tsx1320
8 files changed, 1072 insertions, 678 deletions
diff --git a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
index b8bcaa391..6dd577b88 100644
--- a/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
+++ b/packages/taler-wallet-webextension/src/components/BalanceTable.tsx
@@ -15,8 +15,10 @@
*/
import { Amounts, ScopeType, WalletBalance } from "@gnu-taler/taler-util";
-import { VNode, h } from "preact";
-import { TableWithRoundRows as TableWithRoundedRows } from "./styled/index.js";
+import { Fragment, VNode, h } from "preact";
+import {
+ TableWithRoundRows as TableWithRoundedRows
+} from "./styled/index.js";
export function BalanceTable({
balances,
@@ -26,32 +28,37 @@ export function BalanceTable({
goToWalletHistory: (currency: string) => void;
}): VNode {
return (
- <TableWithRoundedRows>
- {balances.map((entry, idx) => {
- const av = Amounts.parseOrThrow(entry.available);
+ <Fragment>
+ <TableWithRoundedRows>
+ {balances.map((entry, idx) => {
+ const av = Amounts.parseOrThrow(entry.available);
- return (
- <tr
- key={idx}
- onClick={() => goToWalletHistory(av.currency)}
- style={{ cursor: "pointer" }}
- >
- <td>{av.currency}</td>
- <td
- style={{
- fontSize: "2em",
- textAlign: "right",
- width: "100%",
- }}
+ return (
+ <tr
+ key={idx}
+ onClick={() => goToWalletHistory(av.currency)}
+ style={{ cursor: "pointer" }}
>
- {Amounts.stringifyValue(av, 2)}
- <div style={{ fontSize: "small", color: "grey" }}>
- {entry.scopeInfo.type === ScopeType.Exchange || entry.scopeInfo.type === ScopeType.Auditor ? entry.scopeInfo.url : undefined}
- </div>
- </td>
- </tr>
- );
- })}
- </TableWithRoundedRows>
+ <td>{av.currency}</td>
+ <td
+ style={{
+ fontSize: "2em",
+ textAlign: "right",
+ width: "100%",
+ }}
+ >
+ {Amounts.stringifyValue(av, 2)}
+ <div style={{ fontSize: "small", color: "grey" }}>
+ {entry.scopeInfo.type === ScopeType.Exchange ||
+ entry.scopeInfo.type === ScopeType.Auditor
+ ? entry.scopeInfo.url
+ : undefined}
+ </div>
+ </td>
+ </tr>
+ );
+ })}
+ </TableWithRoundedRows>
+ </Fragment>
);
}
diff --git a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
index bf77174df..8b6377fc5 100644
--- a/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
+++ b/packages/taler-wallet-webextension/src/components/BankDetailsByPaytoType.tsx
@@ -21,11 +21,9 @@ import {
segwitMinAmount,
stringifyPaytoUri,
TranslatedString,
- WithdrawalExchangeAccountDetails
+ WithdrawalExchangeAccountDetails,
} from "@gnu-taler/taler-util";
-import {
- useTranslationContext
-} from "@gnu-taler/web-util/browser";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { CopiedIcon, CopyIcon } from "../svg/index.js";
@@ -36,72 +34,94 @@ import { Button } from "../mui/Button.js";
export interface BankDetailsProps {
subject: string;
amount: AmountJson;
- accounts: WithdrawalExchangeAccountDetails[],
+ accounts: WithdrawalExchangeAccountDetails[];
}
export function BankDetailsByPaytoType({
subject,
amount,
- accounts,
+ accounts: unsortedAccounts,
}: BankDetailsProps): VNode {
const { i18n } = useTranslationContext();
- const [index, setIndex] = useState(0)
- // const [currency, setCurrency] = useState(amount.currency)
- if (!accounts.length) {
- return <div>the exchange account list is empty</div>
+ const [index, setIndex] = useState(0);
+
+ if (!unsortedAccounts.length) {
+ return <div>the exchange account list is empty</div>;
}
+
+ const accounts = unsortedAccounts.sort((a, b) => {
+ return (b.priority ?? 0) - (a.priority ?? 0);
+ });
+
const selectedAccount = accounts[index];
- const altCurrency = selectedAccount.currencySpecification?.name
+ const altCurrency = selectedAccount.currencySpecification?.name;
const payto = parsePaytoUri(selectedAccount.paytoUri);
if (!payto) return <Fragment />;
- payto.params["amount"] = altCurrency ? selectedAccount.transferAmount! : Amounts.stringify(amount);
+ payto.params["amount"] = altCurrency
+ ? selectedAccount.transferAmount!
+ : Amounts.stringify(amount);
payto.params["message"] = subject;
-
- function Frame({ title, children }: { title: TranslatedString, children: ComponentChildren }): VNode {
- return <section
- style={{
- textAlign: "left",
- border: "solid 1px black",
- padding: 8,
- borderRadius: 4,
- }}
- >
- <div style={{ display: "flex", width: "100%", justifyContent: "space-between" }}>
- <p style={{ marginTop: 0 }}>
- {title}
- </p>
- <div>
-
+ function Frame({
+ title,
+ children,
+ }: {
+ title: TranslatedString;
+ children: ComponentChildren;
+ }): VNode {
+ return (
+ <section
+ style={{
+ textAlign: "left",
+ border: "solid 1px black",
+ padding: 8,
+ borderRadius: 4,
+ }}
+ >
+ <div
+ style={{
+ display: "flex",
+ width: "100%",
+ justifyContent: "space-between",
+ }}
+ >
+ <p style={{ marginTop: 0 }}>{title}</p>
+ <div></div>
</div>
- </div>
- {children}
+ {children}
- {accounts.length > 1 ?
- <Fragment>
- {accounts.map((ac, acIdx) => {
- return <Button variant={acIdx === index ? "contained" : "outlined"}
- onClick={async () => {
- setIndex(acIdx)
- }}
- >
- <i18n.Translate>Account #{acIdx+1} ({ac.currencySpecification?.name ?? amount.currency})</i18n.Translate>
- </Button>
- })}
+ {accounts.length > 1 ? (
+ <Fragment>
+ {accounts.map((ac, acIdx) => {
+ const accountLabel = ac.bankLabel ?? `Account #${acIdx + 1}`;
+ return (
+ <Button
+ key={acIdx}
+ variant={acIdx === index ? "contained" : "outlined"}
+ onClick={async () => {
+ setIndex(acIdx);
+ }}
+ >
+ {accountLabel} (
+ {ac.currencySpecification?.name ?? amount.currency})
+ </Button>
+ );
+ })}
- {/* <Button variant={currency === altCurrency ? "contained" : "outlined"}
+ {/* <Button variant={currency === altCurrency ? "contained" : "outlined"}
onClick={async () => {
setCurrency(altCurrency)
}}
>
<i18n.Translate>{altCurrency}</i18n.Translate>
</Button> */}
- </Fragment>
- : undefined}
- </section>
+ </Fragment>
+ ) : undefined}
+ </section>
+ );
}
if (payto.isKnown && payto.targetType === "bitcoin") {
@@ -157,7 +177,9 @@ export function BankDetailsByPaytoType({
}
const accountPart = !payto.isKnown ? (
- <Row name={i18n.str`Account`} value={payto.targetPath} />
+ <Fragment>
+ <Row name={i18n.str`Account`} value={payto.targetPath} />
+ </Fragment>
) : payto.targetType === "x-taler-bank" ? (
<Fragment>
<Row name={i18n.str`Bank host`} value={payto.host} />
@@ -172,30 +194,68 @@ export function BankDetailsByPaytoType({
</Fragment>
) : undefined;
- const receiver = payto.params["receiver-name"] || payto.params["receiver"] || undefined;
+ const receiver =
+ payto.params["receiver-name"] || payto.params["receiver"] || undefined;
return (
<Frame title={i18n.str`Bank transfer details`}>
<table>
<tbody>
- {accountPart}
+ <tr>
+ <td colSpan={3}>
+ <i18n.Translate>Step 1:</i18n.Translate>
+ &nbsp;
+ <i18n.Translate>
+ Copy this code and paste it into the subject/purpose field in
+ your banking app or bank website
+ </i18n.Translate>
+ </td>
+ </tr>
<Row name={i18n.str`Subject`} value={subject} literal />
- <Row
- name={i18n.str`Amount`}
- value={<Amount value={altCurrency ? selectedAccount.transferAmount! : amount} hideCurrency />}
- />
-
+ <tr>
+ <td colSpan={3}>
+ <i18n.Translate>Step 2:</i18n.Translate>
+ &nbsp;
+ <i18n.Translate>
+ If you don't already have it in your banking favourites list,
+ then copy and paste this IBAN and the name into the receiver
+ fields in your banking app or website
+ </i18n.Translate>
+ </td>
+ </tr>
+ {accountPart}
{receiver ? (
<Row name={i18n.str`Receiver name`} value={receiver} />
) : undefined}
<tr>
<td colSpan={3}>
+ <i18n.Translate>Step 3:</i18n.Translate>
+ &nbsp;
+ <i18n.Translate>
+ Finish the wire transfer setting the amount in your banking app
+ or website, then this withdrawal will proceed automatically.
+ </i18n.Translate>
+ </td>
+ </tr>
+ <Row
+ name={i18n.str`Amount`}
+ value={
+ <Amount
+ value={altCurrency ? selectedAccount.transferAmount! : amount}
+ hideCurrency
+ />
+ }
+ />
+
+ <tr>
+ <td colSpan={3}>
<WarningBox style={{ margin: 0 }}>
<span>
<i18n.Translate>
- Make sure ALL data is correct, including the subject; otherwise, the money will not
- arrive in this wallet. You can use the copy buttons (<CopyIcon />) to prevent typing errors
+ Make sure ALL data is correct, including the subject;
+ otherwise, the money will not arrive in this wallet. You can
+ use the copy buttons (<CopyIcon />) to prevent typing errors
or the "payto://" URI below to copy just one value.
</i18n.Translate>
</span>
@@ -204,22 +264,20 @@ export function BankDetailsByPaytoType({
</tr>
<tr>
- <td>
- <pre>
- <b>
- <a
- target="_bank"
- rel="noreferrer"
- title="RFC 8905 for designating targets for payments"
- href="https://tools.ietf.org/html/rfc8905"
- >
- Payto URI
- </a>
- </b>
- </pre>
- </td>
- <td width="100%" style={{ wordBreak: "break-all" }}>
- {stringifyPaytoUri(payto)}
+ <td colSpan={2} width="100%" style={{ wordBreak: "break-all" }}>
+ <i18n.Translate>
+ Alternative if your bank already supports PayTo URI, you can use
+ this{" "}
+ <a
+ target="_bank"
+ rel="noreferrer"
+ title="RFC 8905 for designating targets for payments"
+ href="https://tools.ietf.org/html/rfc8905"
+ >
+ PayTo URI
+ </a>{" "}
+ link instead
+ </i18n.Translate>
</td>
<td>
<CopyButton getContent={() => stringifyPaytoUri(payto)} />
diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
index 4b44365ea..9be9326b2 100644
--- a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
@@ -23,6 +23,8 @@ import {
TransactionType,
WithdrawalType,
TransactionMajorState,
+ DenomLossEventType,
+ parsePaytoUri,
} from "@gnu-taler/taler-util";
import { h, VNode } from "preact";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -134,10 +136,6 @@ export function HistoryItem(props: { tx: Transaction }): VNode {
}
/>
);
- case TransactionType.Reward:
- return (
- <div>not supported</div>
- );
case TransactionType.Refresh:
return (
<Layout
@@ -155,13 +153,16 @@ export function HistoryItem(props: { tx: Transaction }): VNode {
}
/>
);
- case TransactionType.Deposit:
+ case TransactionType.Deposit:{
+ const payto = parsePaytoUri(tx.targetPaytoUri);
+ const title = payto === undefined || !payto.isKnown ? tx.targetPaytoUri :
+ payto.params["receiver-name"] ;
return (
<Layout
id={tx.transactionId}
amount={tx.amountEffective}
debitCreditIndicator={"debit"}
- title={tx.targetPaytoUri}
+ title={title}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"D"}
currentState={tx.txState.major}
@@ -172,6 +173,7 @@ export function HistoryItem(props: { tx: Transaction }): VNode {
}
/>
);
+ }
case TransactionType.PeerPullCredit:
return (
<Layout
@@ -240,6 +242,56 @@ export function HistoryItem(props: { tx: Transaction }): VNode {
}
/>
);
+ case TransactionType.DenomLoss: {
+ switch (tx.lossEventType) {
+ case DenomLossEventType.DenomExpired: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination expired`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ case DenomLossEventType.DenomVanished: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination vanished`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ case DenomLossEventType.DenomUnoffered: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination unoffered`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ default: {
+ assertUnreachable(tx.lossEventType);
+ }
+ }
+ break;
+ }
case TransactionType.Recoup:
throw Error("recoup transaction not implemented");
default: {
@@ -256,12 +308,12 @@ function Layout(props: LayoutProps): VNode {
style={{
backgroundColor:
props.currentState === TransactionMajorState.Pending ||
- props.currentState === TransactionMajorState.Dialog
+ props.currentState === TransactionMajorState.Dialog
? "lightcyan"
: props.currentState === TransactionMajorState.Failed
? "#ff000040"
: props.currentState === TransactionMajorState.Aborted ||
- props.currentState === TransactionMajorState.Aborting
+ props.currentState === TransactionMajorState.Aborting
? "#00000010"
: "inherit",
alignItems: "center",
diff --git a/packages/taler-wallet-webextension/src/components/Modal.tsx b/packages/taler-wallet-webextension/src/components/Modal.tsx
index 5553c72df..f8c0f1651 100644
--- a/packages/taler-wallet-webextension/src/components/Modal.tsx
+++ b/packages/taler-wallet-webextension/src/components/Modal.tsx
@@ -52,7 +52,7 @@ const Body = styled.div`
export function Modal({ title, children, onClose }: Props): VNode {
return (
- <div style={{ position: "fixed", top: 0, width: "100%", height: "100%" }}>
+ <div style={{ top: 0, width: "100%", height: "100%" }}>
<FullSize onClick={onClose?.onClick}>
<div
@@ -64,6 +64,7 @@ export function Modal({ title, children, onClose }: Props): VNode {
margin: "auto",
borderRadius: 8,
padding: 8,
+ zIndex: 100,
// overflow: "scroll",
}}
>
diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
index 372ca7cb7..c94010ede 100644
--- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
+++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx
@@ -42,7 +42,10 @@ interface Props extends JSX.HTMLAttributes {
*/
const cache = { tx: [] as Transaction[] };
-export function PendingTransactions({ goToTransaction, goToURL }: Props): VNode {
+export function PendingTransactions({
+ goToTransaction,
+ goToURL,
+}: Props): VNode {
const api = useBackendContext();
const state = useAsyncAsHook(() =>
api.wallet.call(WalletApiOperation.GetTransactions, {}),
@@ -59,8 +62,8 @@ export function PendingTransactions({ goToTransaction, goToURL }: Props): VNode
!state || state.hasError
? cache.tx
: state.response.transactions.filter(
- (t) => t.txState.major === TransactionMajorState.Pending,
- );
+ (t) => t.txState.major === TransactionMajorState.Pending,
+ );
if (state && !state.hasError) {
cache.tx = transactions;
@@ -87,50 +90,52 @@ export function PendingTransactionsView({
transactions: Transaction[];
}): VNode {
const { i18n } = useTranslationContext();
- const kycTransaction = transactions.find(tx => tx.kycUrl)
+ const kycTransaction = transactions.find((tx) => tx.kycUrl);
if (kycTransaction) {
- return <div
- style={{
- backgroundColor: "lightcyan",
- display: "flex",
- justifyContent: "center",
- }}
- >
- <Banner
- titleHead={i18n.str`KYC requirement`}
+ return (
+ <div
style={{
- backgroundColor: "lightred",
- maxHeight: 150,
- padding: 8,
- flexGrow: 1,
- maxWidth: 500,
- overflowY: transactions.length > 3 ? "scroll" : "hidden",
+ backgroundColor: "#fff3cd",
+ color: "#664d03",
+ display: "flex",
+ justifyContent: "center",
}}
>
- <Grid
- container
- item
- xs={1}
- wrap="nowrap"
- role="button"
- spacing={1}
- alignItems="center"
- onClick={() => {
- goToURL(kycTransaction.kycUrl ?? "#")
+ <Banner
+ titleHead={i18n.str`KYC requirement`}
+ style={{
+ backgroundColor: "lightred",
+ maxHeight: 150,
+ padding: 8,
+ flexGrow: 1, //#fff3cd //#ffecb5
+ maxWidth: 500,
+ overflowY: transactions.length > 3 ? "scroll" : "hidden",
}}
>
- <Grid item>
- <Typography inline bold>
- One or more transaction require a KYC step to complete
- </Typography>
+ <Grid
+ container
+ item
+ xs={1}
+ wrap="nowrap"
+ role="button"
+ spacing={1}
+ alignItems="center"
+ onClick={() => {
+ goToURL(kycTransaction.kycUrl ?? "#");
+ }}
+ >
+ <Grid item>
+ <Typography inline bold>
+ One or more transaction require a KYC step to complete
+ </Typography>
+ </Grid>
</Grid>
-
- </Grid>
- </Banner>
- </div>
+ </Banner>
+ </div>
+ );
}
- if (!goToTransaction) return <Fragment />
+ if (!goToTransaction) return <Fragment />;
return (
<div
diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
index 99e2d0a76..0e23d5850 100644
--- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
+++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.stories.tsx
@@ -81,7 +81,7 @@ export const ShowingSimpleOrder = tests.createExample(ShowView, {
contractTerms: cd,
});
export const Error = tests.createExample(ErrorView, {
- proposalId: "asd",
+ transactionId: "asd",
error: {
hasError: true,
message: "message",
diff --git a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
index b0f43d0d9..e655def39 100644
--- a/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
+++ b/packages/taler-wallet-webextension/src/components/ShowFullContractTermPopup.tsx
@@ -17,6 +17,7 @@ import {
AbsoluteTime,
Duration,
Location,
+ TransactionIdStr,
WalletContractData,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
@@ -83,7 +84,7 @@ export namespace States {
}
export interface Error {
status: "error";
- proposalId: string;
+ transactionId: string;
error: HookError;
hideHandler: ButtonHandler;
}
@@ -99,17 +100,17 @@ export namespace States {
}
interface Props {
- proposalId: string;
+ transactionId: TransactionIdStr;
}
-function useComponentState({ proposalId }: Props): State {
+function useComponentState({ transactionId }: Props): State {
const api = useBackendContext();
const [show, setShow] = useState(false);
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(async () => {
if (!show) return undefined;
return await api.wallet.call(WalletApiOperation.GetContractTermsDetails, {
- proposalId,
+ transactionId,
});
}, [show]);
@@ -127,7 +128,7 @@ function useComponentState({ proposalId }: Props): State {
}
if (!hook) return { status: "loading", hideHandler };
if (hook.hasError)
- return { status: "error", proposalId, error: hook, hideHandler };
+ return { status: "error", transactionId, error: hook, hideHandler };
if (!hook.response) return { status: "loading", hideHandler };
return {
status: "show",
@@ -160,7 +161,7 @@ export function LoadingView({ hideHandler }: States.Loading): VNode {
export function ErrorView({
hideHandler,
error,
- proposalId,
+ transactionId,
}: States.Error): VNode {
const { i18n } = useTranslationContext();
return (
@@ -170,7 +171,7 @@ export function ErrorView({
i18n,
i18n.str`Could not load purchase proposal details`,
error,
- { proposalId },
+ { transactionId },
)}
/>
</Modal>
diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
index 60839e1f0..69a2c0675 100644
--- a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
+++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
@@ -22,7 +22,7 @@ import {
TalerErrorDetail,
TaskProgressNotification,
WalletNotification,
- assertUnreachable
+ assertUnreachable,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -37,61 +37,80 @@ import { WxApiType } from "../wxApi.js";
import { Modal } from "./Modal.js";
import { Time } from "./Time.js";
-interface Props extends JSX.HTMLAttributes {
-}
+interface Props extends JSX.HTMLAttributes {}
-export function WalletActivity({ }: Props): VNode {
- const { i18n } = useTranslationContext()
- const [settings, updateSettings] = useSettings()
+export function WalletActivity({}: Props): VNode {
+ const { i18n } = useTranslationContext();
+ const [settings, updateSettings] = useSettings();
const api = useBackendContext();
useEffect(() => {
- document.body.style.marginBottom = "250px"
+ document.body.style.marginBottom = "250px";
return () => {
- document.body.style.marginBottom = "0px"
- }
- })
- const [table, setTable] = useState<"tasks" | "events">("tasks")
+ document.body.style.marginBottom = "0px";
+ };
+ });
+ const [table, setTable] = useState<"tasks" | "events">("tasks");
return (
- <div style={{ position: "fixed", bottom: 0, background: "white", zIndex: 1, height: 250, overflowY: "scroll", width: "100%" }}>
- <div style={{ display: "flex", justifyContent: "space-between", float: "right" }}>
+ <div
+ style={{
+ position: "fixed",
+ bottom: 0,
+ background: "white",
+ zIndex: 1,
+ height: 250,
+ overflowY: "scroll",
+ width: "100%",
+ }}
+ >
+ <div
+ style={{
+ display: "flex",
+ justifyContent: "space-between",
+ float: "right",
+ }}
+ >
<div />
<div>
- <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => {
- updateSettings("showWalletActivity", false)
- }}>
+ <div
+ style={{ padding: 4, margin: 2, border: "solid 1px black" }}
+ onClick={() => {
+ updateSettings("showWalletActivity", false);
+ }}
+ >
close
</div>
</div>
</div>
<div style={{ display: "flex", justifyContent: "space-around" }}>
- <Button variant={table === "tasks" ? "contained" : "outlined"}
+ <Button
+ variant={table === "tasks" ? "contained" : "outlined"}
style={{ margin: 4 }}
onClick={async () => {
- setTable("tasks")
+ setTable("tasks");
}}
>
<i18n.Translate>Tasks</i18n.Translate>
</Button>
- <Button variant={table === "events" ? "contained" : "outlined"}
+ <Button
+ variant={table === "events" ? "contained" : "outlined"}
style={{ margin: 4 }}
onClick={async () => {
- setTable("events")
+ setTable("events");
}}
>
<i18n.Translate>Events</i18n.Translate>
</Button>
-
</div>
{(function (): VNode {
switch (table) {
case "events": {
- return <ObservabilityEventsTable />
+ return <ObservabilityEventsTable />;
}
case "tasks": {
- return <ActiveTasksTable />
+ return <ActiveTasksTable />;
}
default: {
- assertUnreachable(table)
+ assertUnreachable(table);
}
}
})()}
@@ -99,406 +118,571 @@ export function WalletActivity({ }: Props): VNode {
);
}
-interface MoreInfoPRops { events: (WalletNotification & { when: AbsoluteTime })[], onClick: (content: VNode) => void }
+interface MoreInfoPRops {
+ events: (WalletNotification & { when: AbsoluteTime })[];
+ onClick: (content: VNode) => void;
+}
type Notif = {
id: string;
events: (WalletNotification & { when: AbsoluteTime })[];
description: string;
start: AbsoluteTime;
end: AbsoluteTime;
- reference: {
- eventType: NotificationType,
- referenceType: "task" | "transaction" | "operation" | "exchange",
- id: string;
- } | undefined,
+ reference:
+ | {
+ eventType: NotificationType;
+ referenceType: "task" | "transaction" | "operation" | "exchange";
+ id: string;
+ }
+ | undefined;
MoreInfo: (p: MoreInfoPRops) => VNode;
-}
+};
function ShowBalanceChange({ events }: MoreInfoPRops): VNode {
if (!events.length) return <Fragment />;
const not = events[0];
if (not.type !== NotificationType.BalanceChange) return <Fragment />;
- return <Fragment>
- <dt>Transaction</dt>
- <dd>
- <a title={not.hintTransactionId} href={Pages.balanceTransaction({ tid: not.hintTransactionId })}>{not.hintTransactionId.substring(0, 10)}</a>
- </dd>
- </Fragment>
+ return (
+ <Fragment>
+ <dt>Transaction</dt>
+ <dd>
+ <a
+ title={not.hintTransactionId}
+ href={Pages.balanceTransaction({ tid: not.hintTransactionId })}
+ >
+ {not.hintTransactionId.substring(0, 10)}
+ </a>
+ </dd>
+ </Fragment>
+ );
}
function ShowBackupOperationError({ events, onClick }: MoreInfoPRops): VNode {
if (!events.length) return <Fragment />;
const not = events[0];
if (not.type !== NotificationType.BackupOperationError) return <Fragment />;
- return <Fragment>
- <dt>Error</dt>
- <dd>
- <a href="#" onClick={(e) => {
- e.preventDefault();
- const error = not.error
- onClick(<Fragment>
- <dl>
- <dt>Code</dt>
- <dd>{TalerErrorCode[error.code]} ({error.code})</dd>
- <dt>Hint</dt>
- <dd>{error.hint ?? "--"}</dd>
- <dt>Time</dt>
- <dd><Time
- timestamp={error.when}
- format="yyyy/MM/dd HH:mm:ss"
- /></dd>
- </dl>
- <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
- {JSON.stringify(error, undefined, 2)}
- </pre>
- </Fragment>)
- }}>{TalerErrorCode[not.error.code]}</a>
- </dd>
- </Fragment>
-}
-
-function ShowTransactionStateTransition({ events, onClick }: MoreInfoPRops): VNode {
- if (!events.length) return <Fragment />;
- const not = events[0];
- if (not.type !== NotificationType.TransactionStateTransition) return <Fragment />;
- return <Fragment>
- <dt>Old state</dt>
- <dd>
- {not.oldTxState.major} - {not.oldTxState.minor ?? ""}
- </dd>
- <dt>New state</dt>
- <dd>
- {not.newTxState.major} - {not.newTxState.minor ?? ""}
- </dd>
- <dt>Transaction</dt>
- <dd>
- <a title={not.transactionId} href={Pages.balanceTransaction({ tid: not.transactionId })}>{not.transactionId.substring(0, 10)}</a>
- </dd>
- {not.errorInfo ? <Fragment>
+ return (
+ <Fragment>
<dt>Error</dt>
<dd>
- <a href="#" onClick={(e) => {
- if (!not.errorInfo) return;
- e.preventDefault();
- const error = not.errorInfo;
- onClick(<Fragment>
- <dl>
- <dt>Code</dt>
- <dd>{TalerErrorCode[error.code]} ({error.code})</dd>
- <dt>Hint</dt>
- <dd>{error.hint ?? "--"}</dd>
- <dt>Message</dt>
- <dd>{error.message ?? "--"}</dd>
- </dl>
- </Fragment>)
-
- }}>{TalerErrorCode[not.errorInfo.code]}</a>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ const error = not.error;
+ onClick(
+ <Fragment>
+ <dl>
+ <dt>Code</dt>
+ <dd>
+ {TalerErrorCode[error.code]} ({error.code})
+ </dd>
+ <dt>Hint</dt>
+ <dd>{error.hint ?? "--"}</dd>
+ <dt>Time</dt>
+ <dd>
+ <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" />
+ </dd>
+ </dl>
+ <pre
+ style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ >
+ {JSON.stringify(error, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
+ >
+ {TalerErrorCode[not.error.code]}
+ </a>
</dd>
- </Fragment> : undefined}
- <dt>Experimental</dt>
- <dd>
- <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
- {JSON.stringify(not.experimentalUserData, undefined, 2)}
- </pre>
- </dd>
-
-
- </Fragment>
+ </Fragment>
+ );
}
-function ShowExchangeStateTransition({ events, onClick }: MoreInfoPRops): VNode {
+
+function ShowTransactionStateTransition({
+ events,
+ onClick,
+}: MoreInfoPRops): VNode {
if (!events.length) return <Fragment />;
const not = events[0];
- if (not.type !== NotificationType.ExchangeStateTransition) return <Fragment />;
- return <Fragment>
- <dt>Exchange</dt>
- <dd>
- {not.exchangeBaseUrl}
- </dd>
- {not.oldExchangeState && not.newExchangeState.exchangeEntryStatus !== not.oldExchangeState?.exchangeEntryStatus && <Fragment>
- <dt>Entry status</dt>
+ if (not.type !== NotificationType.TransactionStateTransition)
+ return <Fragment />;
+ return (
+ <Fragment>
+ <dt>Old state</dt>
<dd>
- from {not.oldExchangeState.exchangeEntryStatus} to {not.newExchangeState.exchangeEntryStatus}
+ {not.oldTxState.major} - {not.oldTxState.minor ?? ""}
</dd>
- </Fragment>}
- {not.oldExchangeState && not.newExchangeState.exchangeUpdateStatus !== not.oldExchangeState?.exchangeUpdateStatus && <Fragment>
- <dt>Update status</dt>
+ <dt>New state</dt>
<dd>
- from {not.oldExchangeState.exchangeUpdateStatus} to {not.newExchangeState.exchangeUpdateStatus}
+ {not.newTxState.major} - {not.newTxState.minor ?? ""}
</dd>
- </Fragment>}
- {not.oldExchangeState && not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && <Fragment>
- <dt>Tos status</dt>
+ <dt>Transaction</dt>
<dd>
- from {not.oldExchangeState.tosStatus} to {not.newExchangeState.tosStatus}
+ <a
+ title={not.transactionId}
+ href={Pages.balanceTransaction({ tid: not.transactionId })}
+ >
+ {not.transactionId.substring(0, 10)}
+ </a>
</dd>
- </Fragment>}
- </Fragment>
+ {not.errorInfo ? (
+ <Fragment>
+ <dt>Error</dt>
+ <dd>
+ <a
+ href="#"
+ onClick={(e) => {
+ if (!not.errorInfo) return;
+ e.preventDefault();
+ const error = not.errorInfo;
+ onClick(
+ <Fragment>
+ <dl>
+ <dt>Code</dt>
+ <dd>
+ {TalerErrorCode[error.code]} ({error.code})
+ </dd>
+ <dt>Hint</dt>
+ <dd>{error.hint ?? "--"}</dd>
+ <dt>Message</dt>
+ <dd>{error.message ?? "--"}</dd>
+ </dl>
+ </Fragment>,
+ );
+ }}
+ >
+ {TalerErrorCode[not.errorInfo.code]}
+ </a>
+ </dd>
+ </Fragment>
+ ) : undefined}
+ <dt>Experimental</dt>
+ <dd>
+ <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+ {JSON.stringify(not.experimentalUserData, undefined, 2)}
+ </pre>
+ </dd>
+ </Fragment>
+ );
+}
+function ShowExchangeStateTransition({
+ events,
+ onClick,
+}: MoreInfoPRops): VNode {
+ if (!events.length) return <Fragment />;
+ const not = events[0];
+ if (not.type !== NotificationType.ExchangeStateTransition)
+ return <Fragment />;
+ return (
+ <Fragment>
+ <dt>Exchange</dt>
+ <dd>{not.exchangeBaseUrl}</dd>
+ {not.oldExchangeState &&
+ not.newExchangeState.exchangeEntryStatus !==
+ not.oldExchangeState?.exchangeEntryStatus && (
+ <Fragment>
+ <dt>Entry status</dt>
+ <dd>
+ from {not.oldExchangeState.exchangeEntryStatus} to{" "}
+ {not.newExchangeState.exchangeEntryStatus}
+ </dd>
+ </Fragment>
+ )}
+ {not.oldExchangeState &&
+ not.newExchangeState.exchangeUpdateStatus !==
+ not.oldExchangeState?.exchangeUpdateStatus && (
+ <Fragment>
+ <dt>Update status</dt>
+ <dd>
+ from {not.oldExchangeState.exchangeUpdateStatus} to{" "}
+ {not.newExchangeState.exchangeUpdateStatus}
+ </dd>
+ </Fragment>
+ )}
+ {not.oldExchangeState &&
+ not.newExchangeState.tosStatus !== not.oldExchangeState?.tosStatus && (
+ <Fragment>
+ <dt>Tos status</dt>
+ <dd>
+ from {not.oldExchangeState.tosStatus} to{" "}
+ {not.newExchangeState.tosStatus}
+ </dd>
+ </Fragment>
+ )}
+ </Fragment>
+ );
}
-type ObservaNotifWithTime = ((TaskProgressNotification | RequestProgressNotification) & {
+type ObservaNotifWithTime = (
+ | TaskProgressNotification
+ | RequestProgressNotification
+) & {
when: AbsoluteTime;
-})
+};
function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode {
// let prev: ObservaNotifWithTime;
- const asd = events.map(not => {
- if (not.type !== NotificationType.RequestObservabilityEvent && not.type !== NotificationType.TaskObservabilityEvent) return <Fragment />;
+ const asd = events.map((not) => {
+ if (
+ not.type !== NotificationType.RequestObservabilityEvent &&
+ not.type !== NotificationType.TaskObservabilityEvent
+ )
+ return <Fragment />;
const title = (function () {
switch (not.event.type) {
case ObservabilityEventType.HttpFetchFinishError:
case ObservabilityEventType.HttpFetchFinishSuccess:
- case ObservabilityEventType.HttpFetchStart: return "HTTP Request"
+ case ObservabilityEventType.HttpFetchStart:
+ return "HTTP Request";
case ObservabilityEventType.DbQueryFinishSuccess:
case ObservabilityEventType.DbQueryFinishError:
- case ObservabilityEventType.DbQueryStart: return "Database"
+ case ObservabilityEventType.DbQueryStart:
+ return "Database";
case ObservabilityEventType.RequestFinishSuccess:
case ObservabilityEventType.RequestFinishError:
- case ObservabilityEventType.RequestStart: return "Wallet"
+ case ObservabilityEventType.RequestStart:
+ return "Wallet";
case ObservabilityEventType.CryptoFinishSuccess:
case ObservabilityEventType.CryptoFinishError:
- case ObservabilityEventType.CryptoStart: return "Crypto"
- case ObservabilityEventType.TaskStart: return "Task start"
- case ObservabilityEventType.TaskStop: return "Task stop"
- case ObservabilityEventType.TaskReset: return "Task reset"
- case ObservabilityEventType.ShepherdTaskResult: return "Schedule"
- case ObservabilityEventType.DeclareTaskDependency: return "Task dependency"
+ case ObservabilityEventType.CryptoStart:
+ return "Crypto";
+ case ObservabilityEventType.TaskStart:
+ return "Task start";
+ case ObservabilityEventType.TaskStop:
+ return "Task stop";
+ case ObservabilityEventType.TaskReset:
+ return "Task reset";
+ case ObservabilityEventType.ShepherdTaskResult:
+ return "Schedule";
+ case ObservabilityEventType.DeclareTaskDependency:
+ return "Task dependency";
+ case ObservabilityEventType.Message:
+ return "Message";
}
})();
- return <ShowObervavilityDetails title={title} notif={not} onClick={onClick} />
-
- })
- return <table>
- <thead>
- <td>Event</td>
- <td>Info</td>
- <td>Start</td>
- <td>End</td>
- </thead>
- <tbody>
- {asd}
- </tbody>
- </table>
+ return (
+ <ShowObervavilityDetails title={title} notif={not} onClick={onClick} />
+ );
+ });
+ return (
+ <table>
+ <thead>
+ <td>Event</td>
+ <td>Info</td>
+ <td>Start</td>
+ <td>End</td>
+ </thead>
+ <tbody>{asd}</tbody>
+ </table>
+ );
}
-function ShowObervavilityDetails({ title, notif, onClick, prev }: { title: string, notif: ObservaNotifWithTime, prev?: ObservaNotifWithTime, onClick: (content: VNode) => void }): VNode {
+function ShowObervavilityDetails({
+ title,
+ notif,
+ onClick,
+ prev,
+}: {
+ title: string;
+ notif: ObservaNotifWithTime;
+ prev?: ObservaNotifWithTime;
+ onClick: (content: VNode) => void;
+}): VNode {
switch (notif.event.type) {
case ObservabilityEventType.HttpFetchStart:
case ObservabilityEventType.HttpFetchFinishError:
case ObservabilityEventType.HttpFetchFinishSuccess: {
- return <tr>
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
- >
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.url} {
- prev?.event.type === ObservabilityEventType.HttpFetchFinishSuccess ? `(${prev.event.status})`
- : prev?.event.type === ObservabilityEventType.HttpFetchFinishError ? <a href="#" onClick={(e) => {
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
e.preventDefault();
- if (prev.event.type !== ObservabilityEventType.HttpFetchFinishError) return;
- const error = prev.event.error
- onClick(<Fragment>
- <dl>
- <dt>Code</dt>
- <dd>{TalerErrorCode[error.code]} ({error.code})</dd>
- <dt>Hint</dt>
- <dd>{error.hint ?? "--"}</dd>
- <dt>Time</dt>
- <dd><Time
- timestamp={error.when}
- format="yyyy/MM/dd HH:mm:ss"
- /></dd>
- </dl>
- <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
- {JSON.stringify(error, undefined, 2)}
- </pre>
-
- </Fragment>)
- }}>fail</a> : undefined
- }
- </td>
- <td> <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- /></td>
- <td> <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- /></td>
- </tr>
-
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
+ >
+ {title}
+ </a>
+ </td>
+ <td>
+ {notif.event.url}{" "}
+ {prev?.event.type ===
+ ObservabilityEventType.HttpFetchFinishSuccess ? (
+ `(${prev.event.status})`
+ ) : prev?.event.type ===
+ ObservabilityEventType.HttpFetchFinishError ? (
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ if (
+ prev.event.type !==
+ ObservabilityEventType.HttpFetchFinishError
+ )
+ return;
+ const error = prev.event.error;
+ onClick(
+ <Fragment>
+ <dl>
+ <dt>Code</dt>
+ <dd>
+ {TalerErrorCode[error.code]} ({error.code})
+ </dd>
+ <dt>Hint</dt>
+ <dd>{error.hint ?? "--"}</dd>
+ <dt>Time</dt>
+ <dd>
+ <Time
+ timestamp={error.when}
+ format="yyyy/MM/dd HH:mm:ss"
+ />
+ </dd>
+ </dl>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify(error, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
+ >
+ fail
+ </a>
+ ) : undefined}
+ </td>
+ <td>
+ {" "}
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ {" "}
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
case ObservabilityEventType.DbQueryStart:
case ObservabilityEventType.DbQueryFinishSuccess:
case ObservabilityEventType.DbQueryFinishError: {
- return <tr>
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
>
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.location} {notif.event.name}
- </td>
- <td>
- <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- </tr>
+ {title}
+ </a>
+ </td>
+ <td>
+ {notif.event.location} {notif.event.name}
+ </td>
+ <td>
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
case ObservabilityEventType.TaskStart:
case ObservabilityEventType.TaskStop:
case ObservabilityEventType.DeclareTaskDependency:
case ObservabilityEventType.TaskReset: {
- return <tr>
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
>
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.taskId}
- </td>
- <td>
- <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- </tr>
+ {title}
+ </a>
+ </td>
+ <td>{notif.event.taskId}</td>
+ <td>
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
case ObservabilityEventType.ShepherdTaskResult: {
- return <tr>
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
>
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.resultType}
- </td>
- <td>
- <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- </tr>
-
+ {title}
+ </a>
+ </td>
+ <td>{notif.event.resultType}</td>
+ <td>
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
case ObservabilityEventType.CryptoStart:
case ObservabilityEventType.CryptoFinishSuccess:
case ObservabilityEventType.CryptoFinishError: {
- return <tr>
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
>
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.operation}
- </td>
- <td>
- <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- </tr>
+ {title}
+ </a>
+ </td>
+ <td>{notif.event.operation}</td>
+ <td>
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
case ObservabilityEventType.RequestStart:
case ObservabilityEventType.RequestFinishSuccess:
case ObservabilityEventType.RequestFinishError: {
- return <tr >
- <td><a href="#" onClick={(e) => {
- e.preventDefault();
- onClick(<Fragment>
- <pre
- style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
+ return (
+ <tr>
+ <td>
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ onClick(
+ <Fragment>
+ <pre
+ style={{
+ whiteSpace: "pre-wrap",
+ wordBreak: "break-word",
+ }}
+ >
+ {JSON.stringify({ event: notif, prev }, undefined, 2)}
+ </pre>
+ </Fragment>,
+ );
+ }}
>
- {JSON.stringify({ event: notif, prev }, undefined, 2)}
- </pre>
- </Fragment>);
- }}>{title}</a></td>
- <td>
- {notif.event.type}
- </td>
- <td>
- <Time
- timestamp={notif.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={prev?.when}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- </tr>
+ {title}
+ </a>
+ </td>
+ <td>{notif.event.type}</td>
+ <td>
+ <Time timestamp={notif.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ <Time timestamp={prev?.when} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ </tr>
+ );
}
+ case ObservabilityEventType.Message:
+ // FIXME
+ return <></>;
}
}
-function getNotificationFor(id: string, event: WalletNotification, start: AbsoluteTime, list: Notif[]): Notif | undefined {
- const eventWithTime = { ...event, when: start }
+function getNotificationFor(
+ id: string,
+ event: WalletNotification,
+ start: AbsoluteTime,
+ list: Notif[],
+): Notif | undefined {
+ const eventWithTime = { ...event, when: start };
switch (event.type) {
case NotificationType.BalanceChange: {
- return ({
+ return {
id,
events: [eventWithTime],
reference: {
@@ -509,28 +693,32 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
description: "Balance change",
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowBalanceChange
- })
+ MoreInfo: ShowBalanceChange,
+ };
}
case NotificationType.BackupOperationError: {
- return ({
+ return {
id,
events: [eventWithTime],
reference: undefined,
description: "Backup error",
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowBackupOperationError
- })
+ MoreInfo: ShowBackupOperationError,
+ };
}
case NotificationType.TransactionStateTransition: {
- const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.transactionId)
+ const found = list.find(
+ (a) =>
+ a.reference?.eventType === event.type &&
+ a.reference.id === event.transactionId,
+ );
if (found) {
found.end = start;
- found.events.unshift(eventWithTime)
- return undefined
+ found.events.unshift(eventWithTime);
+ return undefined;
}
- return ({
+ return {
id,
events: [eventWithTime],
reference: {
@@ -541,17 +729,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
description: event.type,
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowTransactionStateTransition
- })
+ MoreInfo: ShowTransactionStateTransition,
+ };
}
case NotificationType.ExchangeStateTransition: {
- const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.exchangeBaseUrl)
+ const found = list.find(
+ (a) =>
+ a.reference?.eventType === event.type &&
+ a.reference.id === event.exchangeBaseUrl,
+ );
if (found) {
found.end = start;
- found.events.unshift(eventWithTime)
- return undefined
+ found.events.unshift(eventWithTime);
+ return undefined;
}
- return ({
+ return {
id,
events: [eventWithTime],
description: "Exchange update",
@@ -562,17 +754,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
},
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowExchangeStateTransition
- })
+ MoreInfo: ShowExchangeStateTransition,
+ };
}
case NotificationType.TaskObservabilityEvent: {
- const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.taskId)
+ const found = list.find(
+ (a) =>
+ a.reference?.eventType === event.type &&
+ a.reference.id === event.taskId,
+ );
if (found) {
found.end = start;
- found.events.unshift(eventWithTime)
- return undefined
+ found.events.unshift(eventWithTime);
+ return undefined;
}
- return ({
+ return {
id,
events: [eventWithTime],
reference: {
@@ -583,17 +779,20 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
description: `Task update ${event.taskId}`,
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent
- })
+ MoreInfo: ShowObservabilityEvent,
+ };
}
case NotificationType.WithdrawalOperationTransition: {
- const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.uri)
+ const found = list.find(
+ (a) =>
+ a.reference?.eventType === event.type && a.reference.id === event.uri,
+ );
if (found) {
found.end = start;
- found.events.unshift(eventWithTime)
- return undefined
+ found.events.unshift(eventWithTime);
+ return undefined;
}
- return ({
+ return {
id,
events: [eventWithTime],
reference: {
@@ -604,17 +803,21 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
description: `Withdrawal operation updated`,
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent
- })
+ MoreInfo: ShowObservabilityEvent,
+ };
}
case NotificationType.RequestObservabilityEvent: {
- const found = list.find(a => a.reference?.eventType === event.type && a.reference.id === event.requestId)
+ const found = list.find(
+ (a) =>
+ a.reference?.eventType === event.type &&
+ a.reference.id === event.requestId,
+ );
if (found) {
found.end = start;
- found.events.unshift(eventWithTime)
- return undefined
+ found.events.unshift(eventWithTime);
+ return undefined;
}
- return ({
+ return {
id,
events: [eventWithTime],
reference: {
@@ -625,145 +828,183 @@ function getNotificationFor(id: string, event: WalletNotification, start: Absolu
description: `wallet.${event.operation}(${event.requestId})`,
start,
end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent
- })
+ MoreInfo: ShowObservabilityEvent,
+ };
}
+ case NotificationType.Idle:
+ return undefined;
default: {
- assertUnreachable(event)
+ assertUnreachable(event);
}
}
}
-
function refresh(api: WxApiType, onUpdate: (list: Notif[]) => void) {
- api.background.call("getNotifications", undefined).then(notif => {
-
- const list: Notif[] = []
- for (const n of notif) {
- if (n.notification.type === NotificationType.RequestObservabilityEvent &&
- n.notification.operation === "getActiveTasks") {
- //ignore monitor request
- continue;
- }
- const event = getNotificationFor(String(list.length), n.notification, n.when, list)
- // pepe.
- if (event) {
- list.unshift(event)
+ api.background
+ .call("getNotifications", undefined)
+ .then((notif) => {
+ const list: Notif[] = [];
+ for (const n of notif) {
+ if (
+ n.notification.type === NotificationType.RequestObservabilityEvent &&
+ n.notification.operation === "getActiveTasks"
+ ) {
+ //ignore monitor request
+ continue;
+ }
+ const event = getNotificationFor(
+ String(list.length),
+ n.notification,
+ n.when,
+ list,
+ );
+ // pepe.
+ if (event) {
+ list.unshift(event);
+ }
}
- }
- onUpdate(list);
- }).catch(error => {
- console.log(error)
- })
+ onUpdate(list);
+ })
+ .catch((error) => {
+ console.log(error);
+ });
}
-export function ObservabilityEventsTable({ }: {}): VNode {
- const { i18n } = useTranslationContext()
+export function ObservabilityEventsTable({}: {}): VNode {
+ const { i18n } = useTranslationContext();
const api = useBackendContext();
- const [notifications, setNotifications] = useState<Notif[]>([])
- const [showDetails, setShowDetails] = useState<VNode>()
+ const [notifications, setNotifications] = useState<Notif[]>([]);
+ const [showDetails, setShowDetails] = useState<VNode>();
useEffect(() => {
let lastTimeout: ReturnType<typeof setTimeout>;
function periodicRefresh() {
-
- refresh(api, setNotifications)
+ refresh(api, setNotifications);
lastTimeout = setTimeout(() => {
periodicRefresh();
- }, 1000)
+ }, 1000);
- //clear on unload
- return () => { clearTimeout(lastTimeout) }
+ //clear on unload
+ return () => {
+ clearTimeout(lastTimeout);
+ };
}
- return periodicRefresh()
+ return periodicRefresh();
}, [1]);
- return <div>
- <div style={{ display: "flex", justifyContent: "space-between" }}>
-
- <div style={{ padding: 4, margin: 2, border: "solid 1px black" }} onClick={() => {
- api.background.call("clearNotifications", undefined).then(d => {
- refresh(api, setNotifications)
- })
- }}>
- clear
+ return (
+ <div>
+ <div style={{ display: "flex", justifyContent: "space-between" }}>
+ <div
+ style={{ padding: 4, margin: 2, border: "solid 1px black" }}
+ onClick={() => {
+ api.background.call("clearNotifications", undefined).then((d) => {
+ refresh(api, setNotifications);
+ });
+ }}
+ >
+ clear
+ </div>
</div>
-
-
- </div>
- {showDetails && <Modal title="event details" onClose={{ onClick: (async () => { setShowDetails(undefined) }) as any }} >
- {showDetails}
- </Modal>}
- {notifications.map((not) => {
- return (
- <details key={not.id}>
- <summary>
- <div style={{ width: "90%", display: "inline-flex", justifyContent: "space-between", padding: 4 }}>
- <div style={{ padding: 4 }}>
- {not.description}
- </div>
- <div style={{ padding: 4 }}>
- <Time
- timestamp={not.start}
- format="yyyy/MM/dd HH:mm:ss"
- />
+ {showDetails && (
+ <Modal
+ title="event details"
+ onClose={{
+ onClick: (async () => {
+ setShowDetails(undefined);
+ }) as any,
+ }}
+ >
+ {showDetails}
+ </Modal>
+ )}
+ {notifications.map((not) => {
+ return (
+ <details key={not.id}>
+ <summary>
+ <div
+ style={{
+ width: "90%",
+ display: "inline-flex",
+ justifyContent: "space-between",
+ padding: 4,
+ }}
+ >
+ <div style={{ padding: 4 }}>{not.description}</div>
+ <div style={{ padding: 4 }}>
+ <Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" />
+ </div>
+ <div style={{ padding: 4 }}>
+ <Time timestamp={not.end} format="yyyy/MM/dd HH:mm:ss" />
+ </div>
</div>
- <div style={{ padding: 4 }}><Time
- timestamp={not.end}
- format="yyyy/MM/dd HH:mm:ss"
- /></div>
- </div>
- </summary>
- <not.MoreInfo events={not.events} onClick={(details) => {
- setShowDetails(details)
- }} />
- </details>
- );
- })}
- </div >
+ </summary>
+ <not.MoreInfo
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ </details>
+ );
+ })}
+ </div>
+ );
}
-function ErroDetailModal({ error, onClose }: { error: TalerErrorDetail, onClose: () => void }): VNode {
- return <Modal title="Full detail" onClose={{
- onClick: onClose as any
- }}>
- <dl>
- <dt>Code</dt>
- <dd>{TalerErrorCode[error.code]} ({error.code})</dd>
- <dt>Hint</dt>
- <dd>{error.hint ?? "--"}</dd>
- <dt>Time</dt>
- <dd><Time
- timestamp={error.when}
- format="yyyy/MM/dd HH:mm:ss"
- /></dd>
- </dl>
- <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
- {JSON.stringify(error, undefined, 2)}
- </pre>
- </Modal>
+function ErroDetailModal({
+ error,
+ onClose,
+}: {
+ error: TalerErrorDetail;
+ onClose: () => void;
+}): VNode {
+ return (
+ <Modal
+ title="Full detail"
+ onClose={{
+ onClick: onClose as any,
+ }}
+ >
+ <dl>
+ <dt>Code</dt>
+ <dd>
+ {TalerErrorCode[error.code]} ({error.code})
+ </dd>
+ <dt>Hint</dt>
+ <dd>{error.hint ?? "--"}</dd>
+ <dt>Time</dt>
+ <dd>
+ <Time timestamp={error.when} format="yyyy/MM/dd HH:mm:ss" />
+ </dd>
+ </dl>
+ <pre style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
+ {JSON.stringify(error, undefined, 2)}
+ </pre>
+ </Modal>
+ );
}
-export function ActiveTasksTable({ }: {}): VNode {
- const { i18n } = useTranslationContext()
+export function ActiveTasksTable({}: {}): VNode {
+ const { i18n } = useTranslationContext();
const api = useBackendContext();
const state = useAsyncAsHook(() => {
return api.wallet.call(WalletApiOperation.GetActiveTasks, {});
});
- const [showError, setShowError] = useState<TalerErrorDetail>()
+ const [showError, setShowError] = useState<TalerErrorDetail>();
const tasks = state && !state.hasError ? state.response.tasks : [];
useEffect(() => {
- if (!state || state.hasError) return
+ if (!state || state.hasError) return;
const lastTimeout = setTimeout(() => {
state.retry();
- }, 1000)
+ }, 1000);
return () => {
- clearTimeout(lastTimeout)
- }
- }, [tasks])
+ clearTimeout(lastTimeout);
+ };
+ }, [tasks]);
// const listenAllEvents = Array.from<NotificationType>({ length: 1 });
// listenAllEvents.includes = () => true
@@ -772,59 +1013,88 @@ export function ActiveTasksTable({ }: {}): VNode {
// state?.retry()
// });
// });
- return <Fragment>
- {showError && <ErroDetailModal error={showError} onClose={(async () => { setShowError(undefined) })} />}
+ return (
+ <Fragment>
+ {showError && (
+ <ErroDetailModal
+ error={showError}
+ onClose={async () => {
+ setShowError(undefined);
+ }}
+ />
+ )}
- <table style={{ width: "100%" }}>
- <thead>
- <tr>
- <th>
- <i18n.Translate>Type</i18n.Translate>
- </th>
- <th>
- <i18n.Translate>Id</i18n.Translate>
- </th>
- <th>
- <i18n.Translate>Since</i18n.Translate>
- </th>
- <th>
- <i18n.Translate>Next try</i18n.Translate>
- </th>
- <th>
- <i18n.Translate>Error</i18n.Translate>
- </th>
- <th>
- <i18n.Translate>Transaction</i18n.Translate>
- </th>
- </tr>
- </thead>
- <tbody>
- {tasks.map((task) => {
- const [type, id] = task.id.split(":")
- return (
- <tr>
- <td>{type}</td>
- <td title={id}>{id.substring(0, 10)}</td>
- <td>
- <Time
- timestamp={task.firstTry}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>
- <Time
- timestamp={task.nextTry}
- format="yyyy/MM/dd HH:mm:ss"
- />
- </td>
- <td>{!task.lastError?.code ? "" : <a href="#" onClick={(e) => { e.preventDefault(); setShowError(task.lastError) }}>{TalerErrorCode[task.lastError.code]}</a>}</td>
- <td>
- {task.transaction ? <a title={task.transaction} href={Pages.balanceTransaction({ tid: task.transaction })}>{task.transaction.substring(0, 10)}</a> : "--"}
- </td>
- </tr>
- );
- })}
- </tbody>
- </table>
- </Fragment>
-} \ No newline at end of file
+ <table style={{ width: "100%" }}>
+ <thead>
+ <tr>
+ <th>
+ <i18n.Translate>Type</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Id</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Since</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Next try</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Error</i18n.Translate>
+ </th>
+ <th>
+ <i18n.Translate>Transaction</i18n.Translate>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ {tasks.map((task) => {
+ const [type, id] = task.taskId.split(":");
+ return (
+ <tr>
+ <td>{type}</td>
+ <td title={id}>{id.substring(0, 10)}</td>
+ <td>
+ <Time
+ timestamp={task.firstTry}
+ format="yyyy/MM/dd HH:mm:ss"
+ />
+ </td>
+ <td>
+ <Time timestamp={task.nextTry} format="yyyy/MM/dd HH:mm:ss" />
+ </td>
+ <td>
+ {!task.lastError?.code ? (
+ ""
+ ) : (
+ <a
+ href="#"
+ onClick={(e) => {
+ e.preventDefault();
+ setShowError(task.lastError);
+ }}
+ >
+ {TalerErrorCode[task.lastError.code]}
+ </a>
+ )}
+ </td>
+ <td>
+ {task.transaction ? (
+ <a
+ title={task.transaction}
+ href={Pages.balanceTransaction({ tid: task.transaction })}
+ >
+ {task.transaction.substring(0, 10)}
+ </a>
+ ) : (
+ "--"
+ )}
+ </td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ </Fragment>
+ );
+}