diff --git a/src-gui/src/models/cliModel.ts b/src-gui/src/models/cliModel.ts index 31423ca7..587c6197 100644 --- a/src-gui/src/models/cliModel.ts +++ b/src-gui/src/models/cliModel.ts @@ -18,3 +18,26 @@ export interface CliLog { [index: string]: unknown; }[]; } + +function isCliLog(log: unknown): log is CliLog { + return ( + typeof log === "object" && + log !== null && + "timestamp" in log && + "level" in log && + "fields" in log + ); +} + +export function parseCliLogString(log: string): CliLog | string { + try { + const parsed = JSON.parse(log); + if (isCliLog(parsed)) { + return parsed; + } else { + return log; + } + } catch (err) { + return log; + } +} diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert.tsx index 8453331e..1fa17491 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert.tsx @@ -11,6 +11,7 @@ import { import { ReactNode } from "react"; import { exhaustiveGuard } from "utils/typescriptUtils"; import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration"; +import TruncatedText from "../other/TruncatedText"; import { SwapCancelRefundButton, SwapResumeButton, @@ -219,7 +220,7 @@ export default function SwapStatusAlert({ variant="filled" > - Swap {swap.swap_id.substring(0, 5)}... is unfinished + Swap {swap.swap_id} is unfinished diff --git a/src-gui/src/renderer/components/modal/DialogHeader.tsx b/src-gui/src/renderer/components/modal/DialogHeader.tsx index 043bef49..4cdb482b 100644 --- a/src-gui/src/renderer/components/modal/DialogHeader.tsx +++ b/src-gui/src/renderer/components/modal/DialogHeader.tsx @@ -1,4 +1,5 @@ import { DialogTitle, makeStyles, Typography } from "@material-ui/core"; +import { ReactNode } from "react"; const useStyles = makeStyles({ root: { @@ -8,7 +9,7 @@ const useStyles = makeStyles({ }); type DialogTitleProps = { - title: string; + title: ReactNode; }; export default function DialogHeader({ title }: DialogTitleProps) { diff --git a/src-gui/src/renderer/components/modal/feedback/FeedbackDialog.tsx b/src-gui/src/renderer/components/modal/feedback/FeedbackDialog.tsx index 093b0384..6b42c2ec 100644 --- a/src-gui/src/renderer/components/modal/feedback/FeedbackDialog.tsx +++ b/src-gui/src/renderer/components/modal/feedback/FeedbackDialog.tsx @@ -13,6 +13,7 @@ import { import { CliLog } from "models/cliModel"; import { useSnackbar } from "notistack"; import { useState } from "react"; +import TruncatedText from "renderer/components/other/TruncatedText"; import { store } from "renderer/store/storeRenderer"; import { useActiveSwapInfo, useAppSelector } from "store/hooks"; import { parseDateString } from "utils/parseUtils"; @@ -68,7 +69,7 @@ function SwapSelectDropDown({ Do not attach logs {swaps.map((swap) => ( - Swap {swap.swap_id.substring(0, 5)}... from{" "} + Swap {swap.swap_id} from{" "} {new Date(parseDateString(swap.start_date)).toDateString()} ( ) diff --git a/src-gui/src/renderer/components/modal/listSellers/ListSellersDialog.tsx b/src-gui/src/renderer/components/modal/listSellers/ListSellersDialog.tsx index e62e89ab..5a74e76f 100644 --- a/src-gui/src/renderer/components/modal/listSellers/ListSellersDialog.tsx +++ b/src-gui/src/renderer/components/modal/listSellers/ListSellersDialog.tsx @@ -14,6 +14,7 @@ import { import { Multiaddr } from "multiaddr"; import { useSnackbar } from "notistack"; import { ChangeEvent, useState } from "react"; +import TruncatedText from "renderer/components/other/TruncatedText"; import PromiseInvokeButton from "renderer/components/PromiseInvokeButton"; const PRESET_RENDEZVOUS_POINTS = [ @@ -108,10 +109,7 @@ export default function ListSellersDialog({ {rAddress}} onClick={() => setRendezvousAddress(rAddress)} /> ))} diff --git a/src-gui/src/renderer/components/modal/provider/ProviderInfo.tsx b/src-gui/src/renderer/components/modal/provider/ProviderInfo.tsx index 77be7dc5..6b5c7e28 100644 --- a/src-gui/src/renderer/components/modal/provider/ProviderInfo.tsx +++ b/src-gui/src/renderer/components/modal/provider/ProviderInfo.tsx @@ -1,6 +1,7 @@ import { Box, Chip, makeStyles, Tooltip, Typography } from "@material-ui/core"; import { VerifiedUser } from "@material-ui/icons"; import { ExtendedProviderStatus } from "models/apiModel"; +import TruncatedText from "renderer/components/other/TruncatedText"; import { MoneroBitcoinExchangeRate, SatsAmount, @@ -38,7 +39,7 @@ export default function ProviderInfo({ {provider.multiAddr} - {provider.peerId.substring(0, 8)}...{provider.peerId.slice(-8)} + {provider.peerId} Exchange rate:{" "} diff --git a/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx b/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx index 0836ea73..04243d7f 100644 --- a/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx +++ b/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx @@ -22,7 +22,7 @@ const useStyles = makeStyles((theme) => ({ fieldsOuter: { display: "flex", flexDirection: "column", - gap: theme.spacing(2), + gap: theme.spacing(1.5), }, })); @@ -51,8 +51,8 @@ export default function InitPage() { return ( - + (""); + const haveFundsBeenWithdrawn = withdrawTxId !== null; + function onCancel() { if (!pending) { setWithdrawTxId(null); @@ -31,34 +33,34 @@ export default function WithdrawDialog({ - {withdrawTxId === null ? ( + {haveFundsBeenWithdrawn ? ( + + ) : ( - ) : ( - )} - - {withdrawTxId === null && ( + {!haveFundsBeenWithdrawn && ( withdrawBtc(withdrawAddress)} - onPendingChange={(pending) => { - console.log("pending", pending); - setPending(pending); - }} - onSuccess={(txId) => { - setWithdrawTxId(txId); - }} + onPendingChange={setPending} + onSuccess={setWithdrawTxId} > Withdraw diff --git a/src-gui/src/renderer/components/other/TruncatedText.tsx b/src-gui/src/renderer/components/other/TruncatedText.tsx new file mode 100644 index 00000000..a5e3d585 --- /dev/null +++ b/src-gui/src/renderer/components/other/TruncatedText.tsx @@ -0,0 +1,14 @@ +export default function TruncatedText({ + children, + limit = 6, + ellipsis = "...", +}: { + children: string; + limit?: number; + ellipsis?: string; +}) { + const truncatedText = + children.length > limit ? children.slice(0, limit) + ellipsis : children; + + return truncatedText; +} diff --git a/src-gui/src/renderer/components/pages/history/table/HistoryRow.tsx b/src-gui/src/renderer/components/pages/history/table/HistoryRow.tsx index d325c93a..4f1748ca 100644 --- a/src-gui/src/renderer/components/pages/history/table/HistoryRow.tsx +++ b/src-gui/src/renderer/components/pages/history/table/HistoryRow.tsx @@ -11,6 +11,7 @@ import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp"; import { GetSwapInfoResponse } from "models/tauriModel"; import { useState } from "react"; +import TruncatedText from "renderer/components/other/TruncatedText"; import { PiconeroAmount, SatsAmount } from "../../../other/Units"; import HistoryRowActions from "./HistoryRowActions"; import HistoryRowExpanded from "./HistoryRowExpanded"; @@ -52,7 +53,9 @@ export default function HistoryRow(swap: GetSwapInfoResponse) { {expanded ? : } - {swap.swap_id} + + {swap.swap_id} + (null); + const [logs, setLogs] = useState<(CliLog | string)[] | null>(null); + + function onLogsReceived(response: GetLogsResponse) { + setLogs(response.logs.map(parseCliLogString)); + } return ( <> { - setLogs(data as CliLog[]); - }} - onInvoke={async () => { - throw new Error("Not implemented"); - }} + onSuccess={onLogsReceived} + onInvoke={() => getLogsOfSwap(swapId, false)} {...props} > - View log + View full logs + + getLogsOfSwap(swapId, true)} + {...props} + > + View redacted logs {logs && ( setLogs(null)} fullWidth maxWidth="lg"> @@ -37,7 +46,13 @@ export default function SwapLogFileOpenButton({ - + )} diff --git a/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx b/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx index 1137df24..0e00e2a6 100644 --- a/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx +++ b/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx @@ -9,6 +9,7 @@ import { } from "@material-ui/core"; import { ButtonProps } from "@material-ui/core/Button/Button"; import { BobStateName, GetSwapInfoResponseExt } from "models/tauriModelExt"; +import TruncatedText from "renderer/components/other/TruncatedText"; import PromiseInvokeButton from "renderer/components/PromiseInvokeButton"; import { getMoneroRecoveryKeys } from "renderer/rpc"; import { store } from "renderer/store/storeRenderer"; @@ -38,7 +39,9 @@ function MoneroRecoveryKeysDialog({ return ( + Recovery Keys for swap {swap_id} + /> diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts index ee878521..340eb516 100644 --- a/src-gui/src/renderer/rpc.ts +++ b/src-gui/src/renderer/rpc.ts @@ -5,6 +5,8 @@ import { BalanceResponse, BuyXmrArgs, BuyXmrResponse, + GetLogsArgs, + GetLogsResponse, GetSwapInfoResponse, MoneroRecoveryArgs, ResumeSwapArgs, @@ -132,3 +134,13 @@ export async function checkContextAvailability(): Promise { const available = await invokeNoArgs("is_context_available"); return available; } + +export async function getLogsOfSwap( + swapId: string, + redact: boolean, +): Promise { + return await invoke("get_logs", { + swap_id: swapId, + redact, + }); +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a218905d..0d7fe2f4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use swap::cli::{ api::{ request::{ - BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, MoneroRecoveryArgs, - ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs, + BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetLogsArgs, GetSwapInfosAllArgs, + MoneroRecoveryArgs, ResumeSwapArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs, }, tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle}, Context, ContextBuilder, @@ -168,6 +168,7 @@ pub fn run() { resume_swap, get_history, monero_recovery, + get_logs, suspend_current_swap, is_context_available, ]) @@ -206,6 +207,7 @@ tauri_command!(buy_xmr, BuyXmrArgs); tauri_command!(resume_swap, ResumeSwapArgs); tauri_command!(withdraw_btc, WithdrawBtcArgs); tauri_command!(monero_recovery, MoneroRecoveryArgs); +tauri_command!(get_logs, GetLogsArgs); // These commands require no arguments tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args); diff --git a/swap/src/cli/api/request.rs b/swap/src/cli/api/request.rs index 85b8cf5c..26f9b178 100644 --- a/swap/src/cli/api/request.rs +++ b/swap/src/cli/api/request.rs @@ -359,9 +359,10 @@ impl Request for GetSwapInfosAllArgs { #[typeshare] #[derive(Serialize, Deserialize, Debug)] pub struct GetLogsArgs { + #[typeshare(serialized_as = "Option")] pub swap_id: Option, pub redact: bool, - #[typeshare(serialized_as = "string")] + #[typeshare(serialized_as = "Option")] pub logs_dir: Option, }