forked from coracle/flotilla
feat: show per-relay failure, pending breakdown on outgoing messages
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import {stopPropagation} from "svelte/legacy"
|
||||
import {noop} from "@welshman/lib"
|
||||
import type {AbstractThunk} from "@welshman/app"
|
||||
import {retryThunk, thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
|
||||
import {flattenThunks, getFailedThunkUrls, publishThunk, thunkIsComplete} from "@welshman/app"
|
||||
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Tippy from "@lib/components/Tippy.svelte"
|
||||
@@ -16,40 +16,45 @@
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||
|
||||
const retry = () => {
|
||||
thunk = retryThunk(thunk)
|
||||
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
|
||||
|
||||
if (showToastOnRetry) {
|
||||
pushToast({
|
||||
timeout: 30_000,
|
||||
children: {
|
||||
component: ThunkToast,
|
||||
props: {thunk},
|
||||
},
|
||||
})
|
||||
const retry = (url: string) => {
|
||||
for (const child of flattenThunks([thunk])) {
|
||||
if (!child.options.relays.includes(url)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const retried = publishThunk({...child.options, relays: [url]})
|
||||
|
||||
if (showToastOnRetry) {
|
||||
pushToast({
|
||||
timeout: 30_000,
|
||||
children: {
|
||||
component: ThunkToast,
|
||||
props: {thunk: retried},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const failedUrls = $derived(getFailedThunkUrls($thunk))
|
||||
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
|
||||
</script>
|
||||
|
||||
{#if showFailure}
|
||||
{@const url = failedUrls[0]}
|
||||
{@const {status, detail: message} = $thunk.results[url]}
|
||||
<button
|
||||
class="flex w-full justify-end px-1 text-xs {restProps.class}"
|
||||
onclick={stopPropagation(noop)}>
|
||||
<Tippy
|
||||
class="flex items-center"
|
||||
component={ThunkStatusDetail}
|
||||
props={{url, message, status, retry}}
|
||||
params={{interactive: true}}>
|
||||
props={{thunk, retry}}
|
||||
params={{interactive: true, maxWidth: "none"}}>
|
||||
{#snippet children()}
|
||||
<span class="flex cursor-pointer items-center gap-1 text-error">
|
||||
<Icon icon={Danger} size={3} />
|
||||
<span class="flex cursor-pointer items-center gap-1 opacity-75">
|
||||
<Icon icon={Danger} class="text-error" size={3} />
|
||||
<span>Failed to send!</span>
|
||||
</span>
|
||||
{/snippet}
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
|
||||
interface Props {
|
||||
thunk: AbstractThunk
|
||||
showToastOnRetry?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {thunk, ...restProps}: Props = $props()
|
||||
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||
|
||||
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
|
||||
const showPending = $derived(!thunkIsComplete($thunk))
|
||||
</script>
|
||||
|
||||
{#if showFailure}
|
||||
<ThunkFailure class={restProps.class} {thunk} />
|
||||
<ThunkFailure class={restProps.class} {thunk} {showToastOnRetry} />
|
||||
{:else if showPending}
|
||||
<ThunkPending class={restProps.class} {thunk} />
|
||||
{/if}
|
||||
|
||||
@@ -1,32 +1,69 @@
|
||||
<script lang="ts">
|
||||
import {stopPropagation} from "svelte/legacy"
|
||||
import type {AbstractThunk} from "@welshman/app"
|
||||
import {getFailedThunkUrls, getThunkUrlsWithStatus} from "@welshman/app"
|
||||
import {PublishStatus} from "@welshman/net"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
|
||||
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import {addPeriod} from "@lib/util"
|
||||
|
||||
interface Props {
|
||||
url: string
|
||||
status: string
|
||||
message: string
|
||||
retry: () => void
|
||||
thunk: AbstractThunk
|
||||
retry: (url: string) => void
|
||||
}
|
||||
|
||||
let {url, status, message = $bindable(), retry}: Props = $props()
|
||||
const {thunk, retry}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
if (!message && status === PublishStatus.Timeout) {
|
||||
message = "request timed out"
|
||||
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk))
|
||||
const failedUrls = $derived(getFailedThunkUrls($thunk))
|
||||
const total = $derived(successUrls.length + failedUrls.length)
|
||||
const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0)
|
||||
|
||||
const title = $derived(
|
||||
isPartial ? `Partial delivery ${successUrls.length}/${total} relays` : "Failed to send!",
|
||||
)
|
||||
|
||||
const relayMessage = (status: PublishStatus | undefined, detail: string | undefined) => {
|
||||
if (detail) {
|
||||
return detail
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = "no details recieved"
|
||||
if (status === PublishStatus.Timeout) {
|
||||
return "request timed out"
|
||||
}
|
||||
})
|
||||
|
||||
return "no details received"
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card2 bg-alt col-2 shadow-lg">
|
||||
<p>
|
||||
Failed to publish to {displayRelayUrl(url)}: {addPeriod(message)}
|
||||
</p>
|
||||
<Button class="link" onclick={retry}>Retry</Button>
|
||||
<div class="card2 bg-alt flex min-w-72 max-w-sm flex-col gap-3 px-4 py-3 shadow-lg">
|
||||
<span class="flex items-center gap-2 text-sm font-medium">
|
||||
<Icon icon={Danger} class="text-error" size={4} />
|
||||
{title}
|
||||
</span>
|
||||
<div class="divider my-0"></div>
|
||||
<div class="flex flex-col gap-3">
|
||||
{#each successUrls as url (url)}
|
||||
<div class="flex items-start gap-2 text-sm">
|
||||
<Icon icon={CheckCircle} class="mt-0.5 shrink-0 text-success" size={4} />
|
||||
<span>{displayRelayUrl(url)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
{#each failedUrls as url (url)}
|
||||
{@const {detail, status} = $thunk.results[url] || {}}
|
||||
<div class="grid grid-cols-[1rem_1fr_auto] items-start gap-x-3 gap-y-1 text-sm">
|
||||
<Icon icon={Danger} class="mt-0.5 text-error" size={4} />
|
||||
<div class="min-w-0">
|
||||
<p class="break-all">{displayRelayUrl(url)}</p>
|
||||
<p class="text-xs opacity-60">{addPeriod(relayMessage(status, detail))}</p>
|
||||
</div>
|
||||
<Button class="link shrink-0 px-1" onclick={stopPropagation(() => retry(url))}>
|
||||
Retry
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user