a little bit more admin and settings

This commit is contained in:
Paul Miller
2023-04-21 11:51:10 -05:00
parent 37cac92a16
commit 7c9bd21517
14 changed files with 141 additions and 92 deletions

View File

@@ -1,7 +1,7 @@
import send from '~/assets/icons/send.svg'; import send from '~/assets/icons/send.svg';
import receive from '~/assets/icons/receive.svg'; import receive from '~/assets/icons/receive.svg';
import { Card, Hr, LoadingSpinner, SmallAmount, SmallHeader, VStack } from './layout'; import { Card, LoadingSpinner, SmallAmount, SmallHeader, VStack } from './layout';
import { For, JSX, Match, Show, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js'; import { For, Match, ParentComponent, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js';
import { useMegaStore } from '~/state/megaStore'; import { useMegaStore } from '~/state/megaStore';
import { MutinyInvoice } from '@mutinywallet/mutiny-wasm'; import { MutinyInvoice } from '@mutinywallet/mutiny-wasm';
import { prettyPrintTime } from '~/utils/prettyPrintTime'; import { prettyPrintTime } from '~/utils/prettyPrintTime';
@@ -34,7 +34,7 @@ type Utxo = {
is_spent: boolean is_spent: boolean
} }
function SubtleText(props: { children: any }) { const SubtleText: ParentComponent = (props) => {
return <h3 class='text-xs text-gray-500 uppercase'>{props.children}</h3> return <h3 class='text-xs text-gray-500 uppercase'>{props.children}</h3>
} }
@@ -50,7 +50,7 @@ function OnChainItem(props: { item: OnChainTx }) {
Mempool Link Mempool Link
</a> </a>
</JsonModal> </JsonModal>
<div class={THREE_COLUMNS} onclick={() => setOpen(!open())}> <div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
{isReceive() ? <img src={receive} alt="receive arrow" /> : <img src={send} alt="send arrow" />} {isReceive() ? <img src={receive} alt="receive arrow" /> : <img src={send} alt="send arrow" />}
<div class={CENTER_COLUMN}> <div class={CENTER_COLUMN}>
<h2 class={MISSING_LABEL}>Label Missing</h2> <h2 class={MISSING_LABEL}>Label Missing</h2>
@@ -76,7 +76,7 @@ function InvoiceItem(props: { item: MutinyInvoice }) {
return ( return (
<> <>
<JsonModal open={open()} data={props.item} title="Lightning Transaction" setOpen={setOpen} /> <JsonModal open={open()} data={props.item} title="Lightning Transaction" setOpen={setOpen} />
<div class={THREE_COLUMNS} onclick={() => setOpen(!open())}> <div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
{isSend() ? <img src={send} alt="send arrow" /> : <img src={receive} alt="receive arrow" />} {isSend() ? <img src={send} alt="send arrow" /> : <img src={receive} alt="receive arrow" />}
<div class={CENTER_COLUMN}> <div class={CENTER_COLUMN}>
<h2 class={MISSING_LABEL}>Label Missing</h2> <h2 class={MISSING_LABEL}>Label Missing</h2>
@@ -101,7 +101,7 @@ function Utxo(props: { item: Utxo }) {
return ( return (
<> <>
<JsonModal open={open()} data={props.item} title="Unspent Transaction Output" setOpen={setOpen} /> <JsonModal open={open()} data={props.item} title="Unspent Transaction Output" setOpen={setOpen} />
<div class={THREE_COLUMNS} onclick={() => setOpen(!open())}> <div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
<img src={receive} alt="receive arrow" /> <img src={receive} alt="receive arrow" />
<div class={CENTER_COLUMN}> <div class={CENTER_COLUMN}>
<h2 class={MISSING_LABEL}>Label Missing</h2> <h2 class={MISSING_LABEL}>Label Missing</h2>
@@ -138,9 +138,9 @@ export function Activity() {
return utxos; return utxos;
} }
const [transactions, { refetch: refetchTransactions }] = createResource(getTransactions); const [transactions, { refetch: _refetchTransactions }] = createResource(getTransactions);
const [invoices, { refetch: refetchInvoices }] = createResource(getInvoices); const [invoices, { refetch: _refetchInvoices }] = createResource(getInvoices);
const [utxos, { refetch: refetchUtxos }] = createResource(getUtXos); const [utxos, { refetch: _refetchUtxos }] = createResource(getUtXos);
return ( return (
<VStack> <VStack>

View File

@@ -1,4 +1,4 @@
import { Show, createResource } from "solid-js" import { Show } from "solid-js"
import { useMegaStore } from "~/state/megaStore" import { useMegaStore } from "~/state/megaStore"
import { satsToUsd } from "~/utils/conversions" import { satsToUsd } from "~/utils/conversions"

View File

@@ -1,4 +1,4 @@
import { For, createMemo, createResource, createSignal } from 'solid-js'; import { For, createMemo, createSignal } from 'solid-js';
import { Button } from '~/components/layout'; import { Button } from '~/components/layout';
import { useMegaStore } from '~/state/megaStore'; import { useMegaStore } from '~/state/megaStore';
import { satsToUsd } from '~/utils/conversions'; import { satsToUsd } from '~/utils/conversions';

View File

@@ -0,0 +1,50 @@
import { createSignal } from "solid-js";
import { ConfirmDialog } from "~/components/Dialog";
import { Button } from "~/components/layout";
import { showToast } from "~/components/Toaster";
export function deleteDb(name: string) {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = function () {
console.log("Deleted database successfully");
showToast({ title: "Deleted", description: `Deleted "${name}" database successfully` })
};
req.onerror = function () {
console.error("Couldn't delete database");
showToast(new Error("Couldn't delete database"))
};
req.onblocked = function () {
console.error("Couldn't delete database due to the operation being blocked");
showToast(new Error("Couldn't delete database due to the operation being blocked"))
};
}
export function DeleteEverything() {
async function resetNode() {
setConfirmLoading(true);
deleteDb("gossip")
localStorage.clear();
showToast({ title: "Deleted", description: `Deleted all data` })
setConfirmOpen(false);
setConfirmLoading(false);
setTimeout(() => {
window.location.reload();
}, 1000);
}
async function confirmReset() {
setConfirmOpen(true);
}
const [confirmOpen, setConfirmOpen] = createSignal(false);
const [confirmLoading, setConfirmLoading] = createSignal(false);
return (
<>
<Button onClick={confirmReset}>Delete Everything</Button>
<ConfirmDialog loading={confirmLoading()} isOpen={confirmOpen()} onConfirm={resetNode} onCancel={() => setConfirmOpen(false)}>
This will delete your node's state. This can't be undone!
</ConfirmDialog>
</>
)
}

View File

@@ -3,11 +3,11 @@ import { Card, Hr, SmallHeader, Button, InnerCard, VStack } from "~/components/l
import PeerConnectModal from "~/components/PeerConnectModal"; import PeerConnectModal from "~/components/PeerConnectModal";
import { For, Show, Suspense, createEffect, createResource, createSignal, onCleanup } from "solid-js"; import { For, Show, Suspense, createEffect, createResource, createSignal, onCleanup } from "solid-js";
import { MutinyChannel, MutinyPeer } from "@mutinywallet/mutiny-wasm"; import { MutinyChannel, MutinyPeer } from "@mutinywallet/mutiny-wasm";
import { Collapsible, TextField, toaster } from "@kobalte/core"; import { Collapsible, TextField } from "@kobalte/core";
import mempoolTxUrl from "~/utils/mempoolTxUrl"; import mempoolTxUrl from "~/utils/mempoolTxUrl";
import eify from "~/utils/eify"; import eify from "~/utils/eify";
import { ConfirmDialog } from "./Dialog"; import { ConfirmDialog } from "./Dialog";
import { ToastItem, showToast } from "./Toaster"; import { showToast } from "./Toaster";
// TODO: hopefully I don't have to maintain this type forever but I don't know how to pass it around otherwise // TODO: hopefully I don't have to maintain this type forever but I don't know how to pass it around otherwise
type RefetchPeersType = (info?: unknown) => MutinyPeer[] | Promise<MutinyPeer[] | undefined> | null | undefined type RefetchPeersType = (info?: unknown) => MutinyPeer[] | Promise<MutinyPeer[] | undefined> | null | undefined

View File

@@ -0,0 +1,27 @@
import { Match, Switch, createSignal } from "solid-js"
export function SeedWords(props: { words: string }) {
const [shouldShow, setShouldShow] = createSignal(false)
function toggleShow() {
setShouldShow(!shouldShow())
}
return (<pre class="flex items-center gap-4 bg-m-red p-4 rounded-xl overflow-hidden">
<Switch>
<Match when={!shouldShow()}>
<div onClick={toggleShow} class="cursor-pointer">
<code class="text-red">TAP TO REVEAL SEED WORDS</code>
</div>
</Match>
<Match when={shouldShow()}>
<div onClick={toggleShow} class="cursor-pointer overflow-hidden">
<p class="font-mono w-full whitespace-pre-wrap">
{props.words}
</p>
</div>
</Match>
</Switch>
</pre >)
}

View File

@@ -3,7 +3,7 @@ import { TextField } from '~/components/layout/TextField';
import { NodeManagerSettingStrings, getExistingSettings } from '~/logic/nodeManagerSetup'; import { NodeManagerSettingStrings, getExistingSettings } from '~/logic/nodeManagerSetup';
import { Button } from '~/components/layout'; import { Button } from '~/components/layout';
import { createSignal } from 'solid-js'; import { createSignal } from 'solid-js';
import { deleteDb } from '~/routes/Settings'; import { deleteDb } from '~/components/DeleteEverything';
import { showToast } from './Toaster'; import { showToast } from './Toaster';
import eify from '~/utils/eify'; import eify from '~/utils/eify';
import { ConfirmDialog } from "~/components/Dialog"; import { ConfirmDialog } from "~/components/Dialog";
@@ -11,12 +11,12 @@ import { useMegaStore } from '~/state/megaStore';
export function SettingsStringsEditor() { export function SettingsStringsEditor() {
const existingSettings = getExistingSettings(); const existingSettings = getExistingSettings();
const [settingsForm, { Form, Field, FieldArray }] = createForm<NodeManagerSettingStrings>({ initialValues: existingSettings }); const [_settingsForm, { Form, Field }] = createForm<NodeManagerSettingStrings>({ initialValues: existingSettings });
const [confirmOpen, setConfirmOpen] = createSignal(false); const [confirmOpen, setConfirmOpen] = createSignal(false);
const [settingsTemp, setSettingsTemp] = createSignal<NodeManagerSettingStrings>(); const [settingsTemp, setSettingsTemp] = createSignal<NodeManagerSettingStrings>();
const [state, actions] = useMegaStore(); const [_store, actions] = useMegaStore();
async function handleSubmit(values: NodeManagerSettingStrings) { async function handleSubmit(values: NodeManagerSettingStrings) {
try { try {
@@ -56,7 +56,10 @@ export function SettingsStringsEditor() {
} }
return <Form onSubmit={handleSubmit} class="flex flex-col gap-4"> return <Form onSubmit={handleSubmit} class="flex flex-col gap-4">
<ConfirmDialog loading={false} isOpen={confirmOpen()} onConfirm={confirmStateReset} onCancel={() => setConfirmOpen(false)} /> <ConfirmDialog loading={false} isOpen={confirmOpen()} onConfirm={confirmStateReset} onCancel={() => setConfirmOpen(false)}>
Are you sure? Changing networks will delete your node's state. This can't be undone!
</ConfirmDialog>
<h2 class="text-2xl font-light">Don't trust us! Use your own servers to back Mutiny.</h2>
<Field name="network"> <Field name="network">
{(field, props) => ( {(field, props) => (
// TODO: make a cool select component // TODO: make a cool select component

View File

@@ -1,5 +1,5 @@
import { cva, VariantProps } from "class-variance-authority"; import { cva, VariantProps } from "class-variance-authority";
import { children, JSX, ParentComponent, Show, splitProps, Switch } from "solid-js"; import { children, JSX, ParentComponent, Show, splitProps } from "solid-js";
import { Dynamic } from "solid-js/web"; import { Dynamic } from "solid-js/web";
import { A } from "solid-start"; import { A } from "solid-start";
import { LoadingSpinner } from "."; import { LoadingSpinner } from ".";

View File

@@ -92,8 +92,8 @@ const LargeHeader: ParentComponent = (props) => {
return (<h1 class="text-4xl font-semibold uppercase border-b-2 border-b-white my-4">{props.children}</h1>) return (<h1 class="text-4xl font-semibold uppercase border-b-2 border-b-white my-4">{props.children}</h1>)
} }
const VStack: ParentComponent = (props) => { const VStack: ParentComponent<{ biggap?: boolean }> = (props) => {
return (<div class="flex flex-col gap-4">{props.children}</div>) return (<div class={`flex flex-col gap-${props.biggap ? "8" : "4"}`}>{props.children}</div>)
} }
const SmallAmount: ParentComponent<{ amount: number | bigint }> = (props) => { const SmallAmount: ParentComponent<{ amount: number | bigint }> = (props) => {

View File

@@ -73,7 +73,7 @@ export default function WaitlistForm() {
</h2> </h2>
<Form onSubmit={newHandleSubmit} class="flex flex-col gap-8"> <Form onSubmit={newHandleSubmit} class="flex flex-col gap-8">
<Field name="user_type"> <Field name="user_type">
{(field, props) => ( {(field, _props) => (
// TODO: there's probably a "real" way to do this with modular-forms // TODO: there's probably a "real" way to do this with modular-forms
<StyledRadioGroup value={field.value || "nostr"} onValueChange={(newValue) => setValue(waitlistForm, "user_type", newValue as "nostr" | "email")} choices={COMMUNICATION_METHODS} /> <StyledRadioGroup value={field.value || "nostr"} onValueChange={(newValue) => setValue(waitlistForm, "user_type", newValue as "nostr" | "email")} choices={COMMUNICATION_METHODS} />
)} )}

View File

@@ -1,18 +1,25 @@
import { DeleteEverything } from "~/components/DeleteEverything";
import KitchenSink from "~/components/KitchenSink"; import KitchenSink from "~/components/KitchenSink";
import NavBar from "~/components/NavBar"; import NavBar from "~/components/NavBar";
import { Card, DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout"; import { Card, DefaultMain, LargeHeader, NodeManagerGuard, SafeArea, SmallHeader, VStack } from "~/components/layout";
export default function Admin() { export default function Admin() {
return ( return (
<SafeArea> <NodeManagerGuard>
<DefaultMain> <SafeArea>
<LargeHeader>Admin</LargeHeader> <DefaultMain>
<VStack> <LargeHeader>Admin</LargeHeader>
<Card><p>If you know what you're doing you're in the right place!</p></Card> <VStack>
<KitchenSink /> <Card><p>If you know what you're doing you're in the right place!</p></Card>
</VStack> <KitchenSink />
</DefaultMain> <div class='rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden'>
<NavBar activeTab="none" /> <SmallHeader>Danger zone</SmallHeader>
</SafeArea> <DeleteEverything />
</div>
</VStack>
</DefaultMain>
<NavBar activeTab="none" />
</SafeArea>
</NodeManagerGuard>
) )
} }

View File

@@ -1,7 +1,7 @@
import Reader from "~/components/Reader"; import Reader from "~/components/Reader";
import { createEffect, createSignal, Show } from "solid-js"; import { createEffect, createSignal, Show } from "solid-js";
import { useNavigate } from "solid-start"; import { useNavigate } from "solid-start";
import { Button, SafeArea } from "~/components/layout"; import { Button } from "~/components/layout";
export default function Scanner() { export default function Scanner() {
const [scanResult, setScanResult] = createSignal<string>(); const [scanResult, setScanResult] = createSignal<string>();

View File

@@ -1,66 +1,28 @@
import { createSignal } from "solid-js"; import { ButtonLink, DefaultMain, LargeHeader, NodeManagerGuard, SafeArea, VStack } from "~/components/layout";
import { useNavigate } from "solid-start";
import { ConfirmDialog } from "~/components/Dialog";
import KitchenSink from "~/components/KitchenSink";
import { Button, Card, DefaultMain, Hr, LargeHeader, SafeArea } from "~/components/layout";
import NavBar from "~/components/NavBar"; import NavBar from "~/components/NavBar";
import { SeedWords } from "~/components/SeedWords";
import { SettingsStringsEditor } from "~/components/SettingsStringsEditor"; import { SettingsStringsEditor } from "~/components/SettingsStringsEditor";
import { showToast } from "~/components/Toaster";
import { useMegaStore } from "~/state/megaStore"; import { useMegaStore } from "~/state/megaStore";
export function deleteDb(name: string) {
const req = indexedDB.deleteDatabase(name);
req.onsuccess = function () {
console.log("Deleted database successfully");
showToast({ title: "Deleted", description: `Deleted "${name}" database successfully` })
};
req.onerror = function () {
console.error("Couldn't delete database");
showToast(new Error("Couldn't delete database"))
};
req.onblocked = function () {
console.error("Couldn't delete database due to the operation being blocked");
showToast(new Error("Couldn't delete database due to the operation being blocked"))
};
}
export default function Settings() { export default function Settings() {
const navigate = useNavigate(); const [store, _actions] = useMegaStore();
const [_, actions] = useMegaStore();
async function resetNode() {
setConfirmLoading(true);
deleteDb("gossip")
localStorage.clear();
showToast({ title: "Deleted", description: `Deleted all data` })
setConfirmOpen(false);
setConfirmLoading(false);
setTimeout(() => {
window.location.reload();
}, 1000);
}
async function confirmReset() {
setConfirmOpen(true);
}
const [confirmOpen, setConfirmOpen] = createSignal(false);
const [confirmLoading, setConfirmLoading] = createSignal(false);
return ( return (
<SafeArea> <NodeManagerGuard>
<DefaultMain> <SafeArea>
<LargeHeader>Settings</LargeHeader> <DefaultMain>
<SettingsStringsEditor /> <LargeHeader>Settings</LargeHeader>
<Card title="Random utilities"> <VStack biggap>
{/* <Button onClick={clearWaitlistId}>Clear waitlist_id</Button> */} <VStack>
{/* <Button onClick={setTestWaitlistId}>Use test waitlist_id</Button> */} <p class="text-2xl font-light">Write down these words or you'll die!</p>
<Button onClick={confirmReset}>Delete Everything</Button> <SeedWords words={store.node_manager?.show_seed() || ""} />
<ConfirmDialog loading={confirmLoading()} isOpen={confirmOpen()} onConfirm={resetNode} onCancel={() => setConfirmOpen(false)} /> </VStack>
</Card> <SettingsStringsEditor />
</DefaultMain> <ButtonLink href="/admin">"I know what I'm doing"</ButtonLink>
<NavBar activeTab="settings" /> </VStack>
</SafeArea> </DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</NodeManagerGuard>
) )
} }

View File

@@ -1,5 +1,5 @@
export function prettyPrintTime(ts: number) { export function prettyPrintTime(ts: number) {
const options = { const options: Intl.DateTimeFormatOptions = {
weekday: 'long', weekday: 'long',
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
@@ -8,5 +8,5 @@ export function prettyPrintTime(ts: number) {
minute: 'numeric' minute: 'numeric'
}; };
return new Date(ts * 1000).toLocaleString('en-US', options as any); return new Date(ts * 1000).toLocaleString('en-US', options);
} }