mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-09-24 06:44:43 -04:00
feat(gui): Review logs before sending feedback (#301)
* add review buttons that open the attached logs before submitting feedback * add redact switches to redact transaction id's from attached logs
This commit is contained in:
parent
3fa31ba139
commit
e8084d65ec
9 changed files with 255 additions and 85 deletions
|
@ -53,7 +53,7 @@ export function parseCliLogString(log: string): CliLog | string {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(log);
|
const parsed = JSON.parse(log);
|
||||||
if (isCliLog(parsed)) {
|
if (isCliLog(parsed)) {
|
||||||
return parsed;
|
return parsed as CliLog;
|
||||||
} else {
|
} else {
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
|
@ -61,3 +61,4 @@ export function parseCliLogString(log: string): CliLog | string {
|
||||||
return log;
|
return log;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,50 +7,49 @@ import {
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
IconButton,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Paper,
|
Paper,
|
||||||
Select,
|
Select,
|
||||||
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { useSnackbar } from "notistack";
|
import { useSnackbar } from "notistack";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import TruncatedText from "renderer/components/other/TruncatedText";
|
import TruncatedText from "renderer/components/other/TruncatedText";
|
||||||
import { store } from "renderer/store/storeRenderer";
|
import { store } from "renderer/store/storeRenderer";
|
||||||
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
import { useActiveSwapInfo, useAppSelector } from "store/hooks";
|
||||||
import { parseDateString } from "utils/parseUtils";
|
import { logsToRawString, parseDateString } from "utils/parseUtils";
|
||||||
import { submitFeedbackViaHttp } from "../../../api";
|
import { submitFeedbackViaHttp } from "../../../api";
|
||||||
import LoadingButton from "../../other/LoadingButton";
|
import LoadingButton from "../../other/LoadingButton";
|
||||||
import { PiconeroAmount } from "../../other/Units";
|
import { PiconeroAmount } from "../../other/Units";
|
||||||
import { getLogsOfSwap } from "renderer/rpc";
|
import { getLogsOfSwap, redactLogs } from "renderer/rpc";
|
||||||
import logger from "utils/logger";
|
import logger from "utils/logger";
|
||||||
|
import { Label, Visibility } from "@material-ui/icons";
|
||||||
|
import CliLogsBox from "renderer/components/other/RenderedCliLog";
|
||||||
|
import { CliLog, parseCliLogString } from "models/cliModel";
|
||||||
|
|
||||||
async function submitFeedback(body: string, swapId: string | number, submitDaemonLogs: boolean) {
|
async function submitFeedback(body: string, swapId: string | null, swapLogs: string | null, daemonLogs: string | null) {
|
||||||
let attachedBody = "";
|
let attachedBody = "";
|
||||||
|
|
||||||
if (swapId !== 0 && typeof swapId === "string") {
|
if (swapId !== null) {
|
||||||
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
const swapInfo = store.getState().rpc.state.swapInfos[swapId];
|
||||||
|
|
||||||
if (swapInfo === undefined) {
|
if (swapInfo === undefined) {
|
||||||
throw new Error(`Swap with id ${swapId} not found`);
|
throw new Error(`Swap with id ${swapId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve logs for the specific swap
|
attachedBody = `${JSON.stringify(swapInfo, null, 4)}\n\nLogs: ${swapLogs ?? ""}`;
|
||||||
const logs = await getLogsOfSwap(swapId, false);
|
|
||||||
|
|
||||||
attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs.logs
|
|
||||||
.map((l) => JSON.stringify(l))
|
|
||||||
.join("\n====\n")}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submitDaemonLogs) {
|
if (daemonLogs !== null) {
|
||||||
const logs = store.getState().rpc?.logs ?? [];
|
attachedBody += `\n\nDaemon Logs: ${daemonLogs ?? ""}`;
|
||||||
attachedBody += `\n\nDaemon Logs: ${logs
|
|
||||||
.map((l) => JSON.stringify(l))
|
|
||||||
.join("\n====\n")}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Sending feedback with attachement: \`\n${attachedBody}\``)
|
||||||
await submitFeedbackViaHttp(body, attachedBody);
|
await submitFeedbackViaHttp(body, attachedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,14 +57,14 @@ async function submitFeedback(body: string, swapId: string | number, submitDaemo
|
||||||
* This component is a dialog that allows the user to submit feedback to the
|
* This component is a dialog that allows the user to submit feedback to the
|
||||||
* developers. The user can enter a message and optionally attach logs from a
|
* developers. The user can enter a message and optionally attach logs from a
|
||||||
* specific swap.
|
* specific swap.
|
||||||
* selectedSwap = 0 means no swap is attached
|
* selectedSwap = null means no swap is attached
|
||||||
*/
|
*/
|
||||||
function SwapSelectDropDown({
|
function SwapSelectDropDown({
|
||||||
selectedSwap,
|
selectedSwap,
|
||||||
setSelectedSwap,
|
setSelectedSwap,
|
||||||
}: {
|
}: {
|
||||||
selectedSwap: string | number;
|
selectedSwap: string | null;
|
||||||
setSelectedSwap: (swapId: string | number) => void;
|
setSelectedSwap: (swapId: string | null) => void;
|
||||||
}) {
|
}) {
|
||||||
const swaps = useAppSelector((state) =>
|
const swaps = useAppSelector((state) =>
|
||||||
Object.values(state.rpc.state.swapInfos),
|
Object.values(state.rpc.state.swapInfos),
|
||||||
|
@ -73,15 +72,16 @@ function SwapSelectDropDown({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
value={selectedSwap}
|
value={selectedSwap ?? ""}
|
||||||
label="Attach logs"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onChange={(e) => setSelectedSwap(e.target.value as string)}
|
onChange={(e) => setSelectedSwap(e.target.value as string || null)}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
displayEmpty
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>Do not attach a swap</MenuItem>
|
<MenuItem value="">Do not attach a swap</MenuItem>
|
||||||
{swaps.map((swap) => (
|
{swaps.map((swap) => (
|
||||||
<MenuItem value={swap.swap_id} key={swap.swap_id}>
|
<MenuItem value={swap.swap_id} key={swap.swap_id}>
|
||||||
Swap <TruncatedText>{swap.swap_id}</TruncatedText> from{" "}
|
Swap{" "}<TruncatedText>{swap.swap_id}</TruncatedText>{" "}from{" "}
|
||||||
{new Date(parseDateString(swap.start_date)).toDateString()} (
|
{new Date(parseDateString(swap.start_date)).toDateString()} (
|
||||||
<PiconeroAmount amount={swap.xmr_amount} />)
|
<PiconeroAmount amount={swap.xmr_amount} />)
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -105,24 +105,92 @@ export default function FeedbackDialog({
|
||||||
|
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
|
||||||
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
|
const [selectedSwap, setSelectedSwap] = useState<
|
||||||
string | number
|
string | null
|
||||||
>(currentSwapId?.swap_id || 0);
|
>(currentSwapId?.swap_id || null);
|
||||||
|
const [swapLogs, setSwapLogs] = useState<(string | CliLog)[] | null>(null);
|
||||||
const [attachDaemonLogs, setAttachDaemonLogs] = useState(true);
|
const [attachDaemonLogs, setAttachDaemonLogs] = useState(true);
|
||||||
|
|
||||||
|
const [daemonLogs, setDaemonLogs] = useState<(string | CliLog)[] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Reset logs if no swap is selected
|
||||||
|
if (selectedSwap === null) {
|
||||||
|
setSwapLogs(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the logs from the rust backend and update the state
|
||||||
|
getLogsOfSwap(selectedSwap, false).then((response) => setSwapLogs(response.logs.map(parseCliLogString)))
|
||||||
|
}, [selectedSwap]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (attachDaemonLogs === false) {
|
||||||
|
setDaemonLogs(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDaemonLogs(store.getState().rpc?.logs)
|
||||||
|
}, [attachDaemonLogs]);
|
||||||
|
|
||||||
|
// Whether to display the log editor
|
||||||
|
const [swapLogsEditorOpen, setSwapLogsEditorOpen] = useState(false);
|
||||||
|
const [daemonLogsEditorOpen, setDaemonLogsEditorOpen] = useState(false);
|
||||||
|
|
||||||
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
|
||||||
|
|
||||||
|
const clearState = () => {
|
||||||
|
setBodyText("");
|
||||||
|
setAttachDaemonLogs(false);
|
||||||
|
setSelectedSwap(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendFeedback = async () => {
|
||||||
|
if (pending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setPending(true);
|
||||||
|
await submitFeedback(bodyText, selectedSwap, logsToRawString(swapLogs), logsToRawString(daemonLogs));
|
||||||
|
enqueueSnackbar("Feedback submitted successfully!", {
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
|
clearState()
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Failed to submit feedback: ${e}`);
|
||||||
|
enqueueSnackbar(`Failed to submit feedback (${e})`, {
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setPending(false);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSwapLogsRedacted = async (redact: boolean) => {
|
||||||
|
setSwapLogs((await getLogsOfSwap(selectedSwap, redact)).logs.map(parseCliLogString))
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDaemonLogsRedacted = async (redact: boolean) => {
|
||||||
|
if (!redact)
|
||||||
|
return setDaemonLogs(store.getState().rpc?.logs)
|
||||||
|
|
||||||
|
const redactedLogs = await redactLogs(daemonLogs);
|
||||||
|
setDaemonLogs(redactedLogs)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
<DialogTitle>Submit Feedback</DialogTitle>
|
<DialogTitle>Submit Feedback</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<ul>
|
||||||
Got something to say? Drop us a message below. If you had an issue
|
<li>Got something to say? Drop us a message below. </li>
|
||||||
with a specific swap, select it from the dropdown to attach the logs.
|
<li>If you had an issue with a specific swap, select it from the dropdown to attach the logs.
|
||||||
It will help us figure out what went wrong.
|
It will help us figure out what went wrong.
|
||||||
<br />
|
</li>
|
||||||
We appreciate you taking the time to share your thoughts! Every feedback is read by a core developer!
|
<li>We appreciate you taking the time to share your thoughts! Every message is read by a core developer!</li>
|
||||||
</DialogContentText>
|
</ul>
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -145,50 +213,61 @@ export default function FeedbackDialog({
|
||||||
fullWidth
|
fullWidth
|
||||||
error={bodyTooLong}
|
error={bodyTooLong}
|
||||||
/>
|
/>
|
||||||
<SwapSelectDropDown
|
<Box style={{
|
||||||
selectedSwap={selectedAttachedSwap}
|
display: "flex",
|
||||||
setSelectedSwap={setSelectedAttachedSwap}
|
flexDirection: "row",
|
||||||
/>
|
justifyContent: "space-between",
|
||||||
<Paper variant="outlined" style={{ padding: "0.5rem" }}>
|
gap: "1rem",
|
||||||
<FormControlLabel
|
}}>
|
||||||
control={
|
|
||||||
<Checkbox
|
<SwapSelectDropDown
|
||||||
color="primary"
|
selectedSwap={selectedSwap}
|
||||||
checked={attachDaemonLogs}
|
setSelectedSwap={setSelectedSwap}
|
||||||
onChange={(e) => setAttachDaemonLogs(e.target.checked)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Attach daemon logs"
|
|
||||||
/>
|
/>
|
||||||
</Paper>
|
<Tooltip title="View the logs">
|
||||||
|
<Box style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||||
|
<IconButton onClick={() => setSwapLogsEditorOpen(true)} disabled={selectedSwap === null}>
|
||||||
|
<Visibility />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<LogViewer open={swapLogsEditorOpen} setOpen={setSwapLogsEditorOpen} logs={swapLogs} redact={setSwapLogsRedacted} />
|
||||||
|
<Box style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: "1rem",
|
||||||
|
}}>
|
||||||
|
<Paper variant="outlined" style={{ padding: "0.5rem", width: "100%" }} >
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={attachDaemonLogs}
|
||||||
|
onChange={(e) => setAttachDaemonLogs(e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Attach logs from the current session"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
<Tooltip title="View the logs">
|
||||||
|
<Box style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
|
||||||
|
<IconButton onClick={() => setDaemonLogsEditorOpen(true)} disabled={attachDaemonLogs === false}>
|
||||||
|
<Visibility />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
<LogViewer open={daemonLogsEditorOpen} setOpen={setDaemonLogsEditorOpen} logs={daemonLogs} redact={setDaemonLogsRedacted} />
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={() => { clearState(); onClose() }}>Cancel</Button>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={sendFeedback}
|
||||||
if (pending) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setPending(true);
|
|
||||||
await submitFeedback(bodyText, selectedAttachedSwap, attachDaemonLogs);
|
|
||||||
enqueueSnackbar("Feedback submitted successfully!", {
|
|
||||||
variant: "success",
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(`Failed to submit feedback: ${e}`);
|
|
||||||
enqueueSnackbar(`Failed to submit feedback (${e})`, {
|
|
||||||
variant: "error",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setPending(false);
|
|
||||||
}
|
|
||||||
onClose();
|
|
||||||
}}
|
|
||||||
loading={pending}
|
loading={pending}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
|
@ -197,3 +276,44 @@ export default function FeedbackDialog({
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function LogViewer(
|
||||||
|
{ open,
|
||||||
|
setOpen,
|
||||||
|
logs,
|
||||||
|
redact
|
||||||
|
}: {
|
||||||
|
open: boolean,
|
||||||
|
setOpen: (_: boolean) => void,
|
||||||
|
logs: (string | CliLog)[] | null,
|
||||||
|
redact: (_: boolean) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={() => setOpen(false)} fullWidth>
|
||||||
|
<DialogContent>
|
||||||
|
<Box>
|
||||||
|
<DialogContentText>
|
||||||
|
<Box style={{ display: "flex", flexDirection: "row", alignItems: "center" }}>
|
||||||
|
<Typography>
|
||||||
|
These are the logs that would be attached to your feedback message and provided to us developers.
|
||||||
|
They help us narrow down the problem you encountered.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
|
</Box>
|
||||||
|
</DialogContentText>
|
||||||
|
|
||||||
|
<CliLogsBox label="Logs" logs={logs} topRightButton={<Paper style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center', paddingLeft: "0.5rem" }} variant="outlined">
|
||||||
|
Redact
|
||||||
|
<Switch color="primary" onChange={(_, checked: boolean) => redact(checked)} />
|
||||||
|
</Paper>} />
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button variant="contained" color="primary" onClick={() => setOpen(false)}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog >
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box, Chip, Typography } from "@material-ui/core";
|
import { Box, Chip, Typography } from "@material-ui/core";
|
||||||
import { CliLog } from "models/cliModel";
|
import { CliLog } from "models/cliModel";
|
||||||
import { useMemo, useState } from "react";
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
import { logsToRawString } from "utils/parseUtils";
|
import { logsToRawString } from "utils/parseUtils";
|
||||||
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
|
import ScrollablePaperTextBox from "./ScrollablePaperTextBox";
|
||||||
|
|
||||||
|
@ -61,9 +61,11 @@ function RenderedCliLog({ log }: { log: CliLog }) {
|
||||||
export default function CliLogsBox({
|
export default function CliLogsBox({
|
||||||
label,
|
label,
|
||||||
logs,
|
logs,
|
||||||
|
topRightButton = null,
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
logs: (CliLog | string)[];
|
logs: (CliLog | string)[];
|
||||||
|
topRightButton?: ReactNode
|
||||||
}) {
|
}) {
|
||||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
|
||||||
|
@ -82,6 +84,7 @@ export default function CliLogsBox({
|
||||||
copyValue={logsToRawString(logs)}
|
copyValue={logsToRawString(logs)}
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
setSearchQuery={setSearchQuery}
|
setSearchQuery={setSearchQuery}
|
||||||
|
topRightButton={topRightButton}
|
||||||
rows={memoizedLogs.map((log) =>
|
rows={memoizedLogs.map((log) =>
|
||||||
typeof log === "string" ? (
|
typeof log === "string" ? (
|
||||||
<Typography key={log} component="pre">
|
<Typography key={log} component="pre">
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Box, Divider, IconButton, Paper, Typography } from "@material-ui/core";
|
||||||
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined";
|
||||||
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
|
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
|
||||||
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
|
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
|
||||||
import { ReactNode, useRef } from "react";
|
import { ReactNode, useEffect, useRef } from "react";
|
||||||
import { VList, VListHandle } from "virtua";
|
import { VList, VListHandle } from "virtua";
|
||||||
import { ExpandableSearchBox } from "./ExpandableSearchBox";
|
import { ExpandableSearchBox } from "./ExpandableSearchBox";
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export default function ScrollablePaperTextBox({
|
||||||
copyValue,
|
copyValue,
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
setSearchQuery = null,
|
setSearchQuery = null,
|
||||||
|
topRightButton = null,
|
||||||
minHeight = MIN_HEIGHT,
|
minHeight = MIN_HEIGHT,
|
||||||
}: {
|
}: {
|
||||||
rows: ReactNode[];
|
rows: ReactNode[];
|
||||||
|
@ -22,6 +23,7 @@ export default function ScrollablePaperTextBox({
|
||||||
searchQuery: string | null;
|
searchQuery: string | null;
|
||||||
setSearchQuery?: ((query: string) => void) | null;
|
setSearchQuery?: ((query: string) => void) | null;
|
||||||
minHeight?: string;
|
minHeight?: string;
|
||||||
|
topRightButton?: ReactNode | null
|
||||||
}) {
|
}) {
|
||||||
const virtuaEl = useRef<VListHandle | null>(null);
|
const virtuaEl = useRef<VListHandle | null>(null);
|
||||||
|
|
||||||
|
@ -48,7 +50,10 @@ export default function ScrollablePaperTextBox({
|
||||||
width: "100%",
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography>{title}</Typography>
|
<Box style={{ display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<Typography>{title}</Typography>
|
||||||
|
{topRightButton}
|
||||||
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box
|
<Box
|
||||||
style={{
|
style={{
|
||||||
|
@ -69,12 +74,12 @@ export default function ScrollablePaperTextBox({
|
||||||
<IconButton onClick={onCopy} size="small">
|
<IconButton onClick={onCopy} size="small">
|
||||||
<FileCopyOutlinedIcon />
|
<FileCopyOutlinedIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={scrollToBottom} size="small">
|
|
||||||
<KeyboardArrowDownIcon />
|
|
||||||
</IconButton>
|
|
||||||
<IconButton onClick={scrollToTop} size="small">
|
<IconButton onClick={scrollToTop} size="small">
|
||||||
<KeyboardArrowUpIcon />
|
<KeyboardArrowUpIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
<IconButton onClick={scrollToBottom} size="small">
|
||||||
|
<KeyboardArrowDownIcon />
|
||||||
|
</IconButton>
|
||||||
{searchQuery !== undefined && setSearchQuery !== undefined && (
|
{searchQuery !== undefined && setSearchQuery !== undefined && (
|
||||||
<ExpandableSearchBox query={searchQuery} setQuery={setSearchQuery} />
|
<ExpandableSearchBox query={searchQuery} setQuery={setSearchQuery} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -25,6 +25,8 @@ import {
|
||||||
GetDataDirArgs,
|
GetDataDirArgs,
|
||||||
ResolveApprovalArgs,
|
ResolveApprovalArgs,
|
||||||
ResolveApprovalResponse,
|
ResolveApprovalResponse,
|
||||||
|
RedactArgs,
|
||||||
|
RedactResponse,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
rpcSetBalance,
|
rpcSetBalance,
|
||||||
|
@ -40,6 +42,8 @@ import { getNetwork, isTestnet } from "store/config";
|
||||||
import { Blockchain, Network } from "store/features/settingsSlice";
|
import { Blockchain, Network } from "store/features/settingsSlice";
|
||||||
import { setStatus } from "store/features/nodesSlice";
|
import { setStatus } from "store/features/nodesSlice";
|
||||||
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
import { discoveredMakersByRendezvous } from "store/features/makersSlice";
|
||||||
|
import { CliLog } from "models/cliModel";
|
||||||
|
import { logsToRawString, parseLogsFromString } from "utils/parseUtils";
|
||||||
|
|
||||||
export const PRESET_RENDEZVOUS_POINTS = [
|
export const PRESET_RENDEZVOUS_POINTS = [
|
||||||
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
|
||||||
|
@ -164,6 +168,18 @@ export async function getLogsOfSwap(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call the rust backend to redact logs.
|
||||||
|
export async function redactLogs(
|
||||||
|
logs: (string | CliLog)[]
|
||||||
|
): Promise<(string | CliLog)[]> {
|
||||||
|
const response = await invoke<RedactArgs, RedactResponse>("redact", {
|
||||||
|
text: logsToRawString(logs)
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(response.text.split("\n").length)
|
||||||
|
return parseLogsFromString(response.text);
|
||||||
|
}
|
||||||
|
|
||||||
export async function listSellersAtRendezvousPoint(
|
export async function listSellersAtRendezvousPoint(
|
||||||
rendezvousPointAddress: string,
|
rendezvousPointAddress: string,
|
||||||
): Promise<ListSellersResponse> {
|
): Promise<ListSellersResponse> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
||||||
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
import { getLogsAndStringsFromRawFileString } from "utils/parseUtils";
|
import { parseLogsFromString } from "utils/parseUtils";
|
||||||
import { CliLog } from "models/cliModel";
|
import { CliLog } from "models/cliModel";
|
||||||
import logger from "utils/logger";
|
import logger from "utils/logger";
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export const rpcSlice = createSlice({
|
||||||
reducers: {
|
reducers: {
|
||||||
receivedCliLog(slice, action: PayloadAction<TauriLogEvent>) {
|
receivedCliLog(slice, action: PayloadAction<TauriLogEvent>) {
|
||||||
const buffer = action.payload.buffer;
|
const buffer = action.payload.buffer;
|
||||||
const logs = getLogsAndStringsFromRawFileString(buffer);
|
const logs = parseLogsFromString(buffer);
|
||||||
const logsWithoutExisting = logs.filter(log => !slice.logs.includes(log));
|
const logsWithoutExisting = logs.filter(log => !slice.logs.includes(log));
|
||||||
slice.logs = slice.logs.concat(logsWithoutExisting);
|
slice.logs = slice.logs.concat(logsWithoutExisting);
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,7 +52,7 @@ export function getLinesOfString(data: string): string[] {
|
||||||
.filter((l) => l.length > 0);
|
.filter((l) => l.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLogsAndStringsFromRawFileString(
|
export function parseLogsFromString(
|
||||||
rawFileData: string,
|
rawFileData: string,
|
||||||
): (CliLog | string)[] {
|
): (CliLog | string)[] {
|
||||||
return getLinesOfString(rawFileData).map(parseCliLogString);
|
return getLinesOfString(rawFileData).map(parseCliLogString);
|
||||||
|
|
|
@ -9,8 +9,8 @@ use swap::cli::{
|
||||||
CheckElectrumNodeResponse, CheckMoneroNodeArgs, CheckMoneroNodeResponse,
|
CheckElectrumNodeResponse, CheckMoneroNodeArgs, CheckMoneroNodeResponse,
|
||||||
ExportBitcoinWalletArgs, GetDataDirArgs, GetHistoryArgs, GetLogsArgs,
|
ExportBitcoinWalletArgs, GetDataDirArgs, GetHistoryArgs, GetLogsArgs,
|
||||||
GetMoneroAddressesArgs, GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs,
|
GetMoneroAddressesArgs, GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs,
|
||||||
MoneroRecoveryArgs, ResolveApprovalArgs, ResumeSwapArgs, SuspendCurrentSwapArgs,
|
MoneroRecoveryArgs, RedactArgs, ResolveApprovalArgs, ResumeSwapArgs,
|
||||||
WithdrawBtcArgs,
|
SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||||
},
|
},
|
||||||
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings},
|
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle, TauriSettings},
|
||||||
Context, ContextBuilder,
|
Context, ContextBuilder,
|
||||||
|
@ -186,6 +186,7 @@ pub fn run() {
|
||||||
get_wallet_descriptor,
|
get_wallet_descriptor,
|
||||||
get_data_dir,
|
get_data_dir,
|
||||||
resolve_approval_request,
|
resolve_approval_request,
|
||||||
|
redact
|
||||||
])
|
])
|
||||||
.setup(setup)
|
.setup(setup)
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
|
@ -227,6 +228,7 @@ tauri_command!(get_logs, GetLogsArgs);
|
||||||
tauri_command!(list_sellers, ListSellersArgs);
|
tauri_command!(list_sellers, ListSellersArgs);
|
||||||
tauri_command!(cancel_and_refund, CancelAndRefundArgs);
|
tauri_command!(cancel_and_refund, CancelAndRefundArgs);
|
||||||
tauri_command!(resolve_approval_request, ResolveApprovalArgs);
|
tauri_command!(resolve_approval_request, ResolveApprovalArgs);
|
||||||
|
tauri_command!(redact, RedactArgs);
|
||||||
|
|
||||||
// These commands require no arguments
|
// These commands require no arguments
|
||||||
tauri_command!(get_wallet_descriptor, ExportBitcoinWalletArgs, no_args);
|
tauri_command!(get_wallet_descriptor, ExportBitcoinWalletArgs, no_args);
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::bitcoin::{wallet, CancelTimelock, ExpiredTimelocks, PunishTimelock, T
|
||||||
use crate::cli::api::tauri_bindings::{TauriEmitter, TauriSwapProgressEvent};
|
use crate::cli::api::tauri_bindings::{TauriEmitter, TauriSwapProgressEvent};
|
||||||
use crate::cli::api::Context;
|
use crate::cli::api::Context;
|
||||||
use crate::cli::{list_sellers as list_sellers_impl, EventLoop, Seller, SellerStatus};
|
use crate::cli::{list_sellers as list_sellers_impl, EventLoop, Seller, SellerStatus};
|
||||||
use crate::common::get_logs;
|
use crate::common::{get_logs, redact};
|
||||||
use crate::libp2p_ext::MultiAddrExt;
|
use crate::libp2p_ext::MultiAddrExt;
|
||||||
use crate::monero::wallet_rpc::MoneroDaemon;
|
use crate::monero::wallet_rpc::MoneroDaemon;
|
||||||
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
use crate::network::quote::{BidQuote, ZeroQuoteReceived};
|
||||||
|
@ -420,6 +420,29 @@ impl Request for GetLogsArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Best effort redaction of logs, e.g. wallet addresses, swap-ids
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct RedactArgs {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct RedactResponse {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request for RedactArgs {
|
||||||
|
type Response = RedactResponse;
|
||||||
|
|
||||||
|
async fn request(self, _: Arc<Context>) -> Result<Self::Response> {
|
||||||
|
Ok(RedactResponse {
|
||||||
|
text: redact(&self.text),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct GetMoneroAddressesArgs;
|
pub struct GetMoneroAddressesArgs;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue