Skip to content

Commit

Permalink
enhance notices view
Browse files Browse the repository at this point in the history
  • Loading branch information
ticruz38 committed Nov 25, 2024
1 parent a8ece59 commit d06bef4
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 31 deletions.
77 changes: 46 additions & 31 deletions src/app/views/Publishes.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
<script lang="ts">
import {pluralize, seconds} from "hurdak"
import {thunks, type Thunk} from "@welshman/app"
import {assoc, now, remove, sortBy} from "@welshman/lib"
import {LOCAL_RELAY_URL} from "@welshman/util"
import {PublishStatus} from "@welshman/net"
import Tile from "src/partials/Tile.svelte"
import Subheading from "src/partials/Subheading.svelte"
import PublishCard from "src/app/shared/PublishCard.svelte"
import {thunks, type Thunk} from "@welshman/app"
import {LOCAL_RELAY_URL} from "@welshman/util"
import {get} from "svelte/store"
import {pluralize, seconds} from "hurdak"
import PublishCard from "src/app/shared/PublishCard.svelte"
import Subheading from "src/partials/Subheading.svelte"
import Tabs from "src/partials/Tabs.svelte"
import Tile from "src/partials/Tile.svelte"
import PublishesConnections from "src/app/views/PublishesConnections.svelte"
import PublishesNotices from "src/app/views/PublishesNotices.svelte"
const tabs = ["events", "connections", "notices"]
let activeTab = "events"
let selectedUrl: string
const hasStatus = (thunk: Thunk, statuses: PublishStatus[]) =>
Object.values(get(thunk.status)).some(s => statuses.includes(s.status))
Expand Down Expand Up @@ -43,28 +51,35 @@
</script>

<Subheading>Published Events</Subheading>
<div class="grid grid-cols-4 justify-between gap-2 sm:grid-cols-5">
<Tile background>
<p class="text-lg sm:text-2xl">{recent.length}</p>
<span class="text-sm">{pluralize(recent.length, "Event")}</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{relays.size}</p>
<span class="text-sm">{pluralize(relays.size, "Relay")}</span>
</Tile>
<Tile background lass="hidden sm:block">
<p class="text-lg sm:text-2xl">{pending.length}</p>
<span class="text-sm">Pending</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{success.length}</p>
<span class="text-sm">Succeeded</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{recent.length - pending.length - success.length}</p>
<span class="text-sm">Failed</span>
</Tile>
</div>
{#each sortBy(t => -t.event.created_at, recent) as thunk (thunk.event.id)}
<PublishCard {thunk} />
{/each}
<Tabs {tabs} {activeTab} setActiveTab={tab => (activeTab = tab)} />
{#if activeTab === "events"}
<div class="grid grid-cols-4 justify-between gap-2 sm:grid-cols-5">
<Tile background>
<p class="text-lg sm:text-2xl">{recent.length}</p>
<span class="text-sm">{pluralize(recent.length, "Event")}</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{relays.size}</p>
<span class="text-sm">{pluralize(relays.size, "Relay")}</span>
</Tile>
<Tile background lass="hidden sm:block">
<p class="text-lg sm:text-2xl">{pending.length}</p>
<span class="text-sm">Pending</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{success.length}</p>
<span class="text-sm">Succeeded</span>
</Tile>
<Tile background>
<p class="text-lg sm:text-2xl">{recent.length - pending.length - success.length}</p>
<span class="text-sm">Failed</span>
</Tile>
</div>
{#each sortBy(t => -t.event.created_at, recent) as thunk (thunk.event.id)}
<PublishCard {thunk} />
{/each}
{:else if activeTab === "connections"}
<PublishesConnections bind:selected={selectedUrl} bind:activeTab />
{:else if activeTab === "notices"}
<PublishesNotices selected={[selectedUrl].filter(Boolean)} />
{/if}
201 changes: 201 additions & 0 deletions src/app/views/PublishesConnections.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
<script lang="ts">
import {getRelayQuality, relaysByUrl, thunks} from "@welshman/app"
import {AuthStatus, PublishStatus, SocketStatus, type Connection} from "@welshman/net"
import {ctx} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import {onMount} from "svelte"
import cx from "classnames"
import AltColor from "src/partials/AltColor.svelte"
import {quantify} from "hurdak"
import {get} from "svelte/store"
import Tile from "src/partials/Tile.svelte"
export let selected: string
export let activeTab: string
const connectionsStatus: {[key: string]: Map<string, Connection>} = {}
const filters: {[key: string]: boolean} = {}
const pendingStatuses = [
AuthStatus.Requested,
AuthStatus.PendingSignature,
AuthStatus.PendingResponse,
]
const failureStatuses = [AuthStatus.DeniedSignature, AuthStatus.Forbidden]
function getStatus(status: PublishStatus, cxn: Connection) {
return Object.values($thunks).filter(t => get(t.status)[cxn.url]?.status == status)
}
$: connections = Array.from(ctx.net.pool.data.entries())
.filter(([url, cxn]) =>
Object.keys(filters).filter(f => filters[f]).length
? Object.keys(filters).some(f => filters[f] && connectionsStatus[f]?.has(url))
: true,
)
.map(([url, cxn]) => cxn)
onMount(() => {
const interval = setInterval(() => {
for (const [url, cxn] of ctx.net.pool.data.entries()) {
if (pendingStatuses.includes(cxn.auth.status)) {
connectionsStatus["Logging in"] = (connectionsStatus["Logging in"] || new Map()).set(
url,
cxn,
)
} else if (failureStatuses.includes(cxn.auth.status)) {
connectionsStatus["Failed to log in"] = (
connectionsStatus["Failed to log in"] || new Map()
).set(url, cxn)
} else if (cxn.socket.status === SocketStatus.Error) {
connectionsStatus["Failed to connect"] = (
connectionsStatus["Failed to connect"] || new Map()
).set(url, cxn)
} else if (cxn.socket.status === SocketStatus.Closed) {
connectionsStatus["Waiting to reconnect"] = (
connectionsStatus["Waiting to reconnect"] || new Map()
).set(url, cxn)
} else if (cxn.socket.status === SocketStatus.New) {
connectionsStatus["Not connected"] = (
connectionsStatus["Not connected"] || new Map()
).set(url, cxn)
} else if (getRelayQuality(cxn.url) < 0.5) {
connectionsStatus["Unstable connection"] = (
connectionsStatus["Unstable connection"] || new Map()
).set(url, cxn)
} else {
connectionsStatus["Connected"] = (connectionsStatus["Connected"] || new Map()).set(
url,
cxn,
)
}
}
}, 800)
return () => {
clearInterval(interval)
}
})
</script>

<div class="grid grid-cols-6 justify-between gap-2 sm:grid-cols-4">
<Tile
class={cx({border: filters["Connected"]}, "cursor-pointer")}
background
on:click={() => (filters["Connected"] = !filters["Connected"])}>
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Connected"]?.values() || []).length || 0}
</p>
<span class="text-sm">Connected</span>
</Tile>
<Tile
class={cx({border: filters["Logging in"]}, "cursor-pointer")}
background
on:click={() => (filters["Logging in"] = !filters["Logging in"])}>
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Logging in"]?.values() || []).length || 0}
</p>
<span class="text-sm">Logging in</span>
</Tile>
<Tile
class={cx({border: filters["Failed to log in"]}, "cursor-pointer")}
background
on:click={() => (filters["Failed to log in"] = !filters["Failed to log in"])}>
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Failed to log in"]?.values() || []).length || 0}
</p>
<span class="text-sm">Login failed</span>
</Tile>
<Tile
class={cx({border: filters["Failed to connect"]}, "cursor-pointer")}
background
on:click={() => (filters["Failed to connect"] = !filters["Failed to connect"])}
lass="hidden sm:block">
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Failed to connect"]?.values() || []).length || 0}
</p>
<span class="text-sm">Connection failed</span>
</Tile>
<Tile
class={cx({border: filters["Waiting to reconnect"]}, "cursor-pointer")}
background
on:click={() => (filters["Waiting to reconnect"] = !filters["Waiting to reconnect"])}>
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Waiting to reconnect"]?.values() || []).length || 0}
</p>
<span class="text-sm">Reconnecting</span>
</Tile>
<Tile
class={cx({border: filters["Not connected"]}, "cursor-pointer")}
background
on:click={() => (filters["Not connected"] = !filters["Not connected"])}>
<p class="text-lg sm:text-2xl">
{Array.from(connectionsStatus["Not connected"]?.values() || []).length || 0}
</p>
<span class="text-sm">Not Connected</span>
</Tile>
</div>
{#each connections as cxn (cxn.url)}
{@const relay = $relaysByUrl.get(cxn.url)}
<AltColor
background
class="cursor-pointer justify-between rounded-md p-6 shadow"
on:click={() => {
selected = cxn.url
activeTab = "notices"
}}>
<div class="flex min-w-0 shrink-0 items-center gap-3">
{#if relay?.profile?.icon}
<img class="h-9 w-9 shrink-0 rounded-full border" src={relay.profile.icon} />
{:else}
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full border">
<i class="fa fa-server text-xl text-neutral-100"></i>
</div>
{/if}
<div class="shrink-0">
<div class="flex items-center gap-2">
<div class="text-md overflow-hidden text-ellipsis whitespace-nowrap">
{displayRelayUrl(cxn.url)}
</div>
</div>
<div class="flex gap-4 text-xs text-neutral-400">
{#if relay?.profile?.supported_nips}
<span>
{relay.profile.supported_nips.length} NIPs
</span>
{/if}
<span>
Connected {quantify(relay?.stats?.open_count || 0, "time")}
</span>
</div>
</div>
<div class="flex w-full justify-end gap-2">
{#if getStatus(PublishStatus.Success, cxn).length > 0}
{@const success = getStatus(PublishStatus.Success, cxn).length}
<div class="flex items-center gap-2">
<i class="fa fa-check-circle text-success"></i>{success || ""}
</div>
{/if}
{#if getStatus(PublishStatus.Failure, cxn).length > 0}
{@const failure = getStatus(PublishStatus.Failure, cxn).length}
<div class="flex items-center gap-2">
<i class="fa fa-times-circle text-danger"></i>{failure || ""}
</div>
{/if}
{#if getStatus(PublishStatus.Pending, cxn).length > 0}
{@const pending = getStatus(PublishStatus.Pending, cxn).length}
<div class="flex items-center gap-2">
<i class="fa fa-hourglass-half text-accent"></i>{pending || ""}
</div>
{/if}
{#if getStatus(PublishStatus.Timeout, cxn).length > 0}
{@const timeout = getStatus(PublishStatus.Pending, cxn).length}
<div class="flex items-center gap-2">
<i class="fa fa-clock"></i>{timeout || ""}
</div>
{/if}
</div>
</div>
</AltColor>
{/each}
82 changes: 82 additions & 0 deletions src/app/views/PublishesNotices.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts">
import {formatTimestamp, thunks, type Thunk} from "@welshman/app"
import {ctx, identity, uniq} from "@welshman/lib"
import {PublishStatus, type Connection} from "@welshman/net"
import {get} from "svelte/store"
import {fly} from "svelte/transition"
import SearchSelect from "src/partials/SearchSelect.svelte"
import {fuzzy} from "src/util/misc"
import Anchor from "src/partials/Anchor.svelte"
import {router} from "src/app/util"
import AltColor from "src/partials/AltColor.svelte"
export let selected: string[] = []
const searchConnections = fuzzy(Array.from(ctx.net.pool.data.keys()))
function getNotices(connections: Connection[]) {
return Object.values($thunks).filter(t =>
connections.some(cxn => get(t.status)[cxn.url]?.status),
)
}
function iconStatus(status: PublishStatus) {
switch (status) {
case PublishStatus.Success:
return "fa fa-check-circle text-success"
case PublishStatus.Pending:
return "fa fa-clock"
case PublishStatus.Failure:
return "fa fa-times-circle text-danger"
case PublishStatus.Timeout:
return "fa fa-hourglass-half text-accent"
case PublishStatus.Aborted:
return "fa fa-ban"
}
}
$: notices = getNotices(
selected.map(url => ctx.net.pool.data.get(url)).filter(Boolean),
) as Thunk[]
</script>

<SearchSelect
placeholder="Search notices by relay"
multiple
search={searchConnections}
bind:value={selected}
termToItem={identity}>
<div slot="item" let:item>
<strong>{item}</strong>
</div>
</SearchSelect>

{#if !notices.length && selected.length}
<div transition:fly|local={{y: 20}}>
<AltColor background class="rounded-md p-6 shadow">
<div class="place text-center text-neutral-100">No notices found for selected relays.</div>
</AltColor>
</div>
{:else}
{#each notices as thunk}
<AltColor background class="rounded-md p-6 shadow">
<div class="flex justify-between">
<span>Kind {thunk.event.kind}, published {formatTimestamp(thunk.event.created_at)}</span>
<Anchor
underline
modal
class="text-sm"
on:click={() => router.at("notes").of(thunk.event.id).open()}>View Note</Anchor>
</div>
{#each uniq(Object.entries(get(thunk.status)).filter( ([k, v]) => selected.includes(k), )) as [url, status]}
<div class="mt-4 flex items-center justify-between">
<strong>{url}</strong>
<div class="flex items-center gap-1">
<i class={iconStatus(status.status)} />
<span class="ml-2">{status.status}</span>
</div>
</div>
{/each}
</AltColor>
{/each}
{/if}

0 comments on commit d06bef4

Please sign in to comment.