+
Skip to content

Enhance note status #487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 10, 2024
52 changes: 17 additions & 35 deletions src/app/shared/RelayStatus.svelte
Original file line number Diff line number Diff line change
@@ -1,48 +1,18 @@
<script lang="ts">
import {onMount} from "svelte"
import {ctx} from "@welshman/lib"
import {SocketStatus, AuthStatus} from "@welshman/net"
import {getRelayQuality} from "@welshman/app"
import Popover from "src/partials/Popover.svelte"
import {ConnectionType, displayConnectionType, getConnectionStatus} from "src/domain/connection"

export let url

const pendingStatuses = [
AuthStatus.Requested,
AuthStatus.PendingSignature,
AuthStatus.PendingResponse,
]
const failureStatuses = [AuthStatus.DeniedSignature, AuthStatus.Forbidden]

let description = "Not connected"
let className = "bg-neutral-600"
let status = ConnectionType.NotConnected

onMount(() => {
const interval = setInterval(() => {
const cxn = ctx.net.pool.get(url)

if (pendingStatuses.includes(cxn.auth.status)) {
className = "bg-warning"
description = "Logging in"
} else if (failureStatuses.includes(cxn.auth.status)) {
className = "bg-danger"
description = "Failed to log in"
} else if (cxn.socket.status === SocketStatus.Error) {
className = "bg-danger"
description = "Failed to connect"
} else if (cxn.socket.status === SocketStatus.Closed) {
className = "bg-warning"
description = "Waiting to reconnect"
} else if (cxn.socket.status === SocketStatus.New) {
className = "bg-neutral-600"
description = "Not connected"
} else if (getRelayQuality(cxn.url) < 0.5) {
className = "bg-warning"
description = "Unstable connection"
} else {
className = "bg-success"
description = "Connected"
}
status = getConnectionStatus(cxn)
}, 800)

return () => {
Expand All @@ -52,8 +22,20 @@
</script>

<Popover triggerType="mouseenter">
<div slot="trigger" class="h-2 w-2 cursor-pointer rounded-full {className}" />
<div
slot="trigger"
class="h-2 w-2 cursor-pointer rounded-full bg-neutral-600"
class:bg-neutral-600={ConnectionType.NotConnected == status}
class:bg-danger={[ConnectionType.LoginFailed, ConnectionType.ConnectFailed].some(
s => s == status,
)}
class:bg-success={ConnectionType.Connected == status}
class:bg-warning={[
ConnectionType.Logging,
ConnectionType.WaitReconnect,
ConnectionType.UnstableConnection,
].some(s => s == status)} />
<div slot="tooltip" class="transition-all sm:block">
{description}
{displayConnectionType(status)}
</div>
</Popover>
78 changes: 15 additions & 63 deletions src/app/views/Publishes.svelte
Original file line number Diff line number Diff line change
@@ -1,70 +1,22 @@
<script lang="ts">
import {pluralize, seconds} from "hurdak"
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 PublishesConnections from "src/app/views/PublishesConnections.svelte"
import PublishesNotices from "src/app/views/PublishesNotices.svelte"
import PublishesEvents from "src/app/views/PublishesEvents.svelte"
import Subheading from "src/partials/Subheading.svelte"
import PublishCard from "src/app/shared/PublishCard.svelte"
import {thunks, type Thunk} from "@welshman/app"
import {get} from "svelte/store"
import Tabs from "src/partials/Tabs.svelte"

const hasStatus = (thunk: Thunk, statuses: PublishStatus[]) =>
Object.values(get(thunk.status)).some(s => statuses.includes(s.status))
const tabs = ["events", "connections", "notices"]
let activeTab = "events"

$: recent = (Object.values($thunks) as Thunk[]).filter(
t =>
remove(LOCAL_RELAY_URL, t.request.relays).length > 0 &&
t.event.created_at > now() - seconds(24, "hour"),
)
$: relays = new Set(
remove(
LOCAL_RELAY_URL,
recent.flatMap(({request}) => request.relays),
),
)
$: success = recent.filter(t => hasStatus(t, [PublishStatus.Success]))
$: pending = recent.filter(
t => hasStatus(t, [PublishStatus.Pending]) && !hasStatus(t, [PublishStatus.Success]),
)

// If the page gets refreshed before pending finishes, it hangs. Set stuff to failed
$: {
for (const t of recent) {
if (t.event.created_at < now() - seconds(1, "minute")) {
for (const [url, s] of Object.entries(t.status)) {
if (s.status === PublishStatus.Pending) {
t.status.update(assoc(url, {status: PublishStatus.Failure, message: ""}))
}
}
}
}
}
let selected: string
</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"}
<PublishesEvents />
{:else if activeTab === "connections"}
<PublishesConnections bind:selected bind:activeTab />
{:else if activeTab === "notices"}
<PublishesNotices search={selected} />
{/if}
105 changes: 105 additions & 0 deletions src/app/views/PublishesConnections.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script lang="ts">
import {relaysByUrl} from "@welshman/app"
import {addToMapKey, ctx} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
import {quantify} from "hurdak"
import {onMount} from "svelte"
import AltColor from "src/partials/AltColor.svelte"
import SelectButton from "src/partials/SelectButton.svelte"
import {ConnectionType, displayConnectionType, getConnectionStatus} from "src/domain/connection"

export let selected: string
export let activeTab: string

let selectedOptions: ConnectionType[] = []
let connectionsStatus: Map<ConnectionType, Set<string>> = new Map()

const options = [
ConnectionType.Connected,
ConnectionType.Logging,
ConnectionType.LoginFailed,
ConnectionType.ConnectFailed,
ConnectionType.WaitReconnect,
ConnectionType.NotConnected,
ConnectionType.UnstableConnection,
]

$: connections = Array.from(ctx.net.pool.data.keys()).filter(url =>
selectedOptions.length ? selectedOptions.some(s => connectionsStatus.get(s)?.has(url)) : true,
)

function fetchConnectionStatus() {
const newConnectionStatus: Map<ConnectionType, Set<string>> = new Map()
for (const [url, cxn] of ctx.net.pool.data.entries()) {
addToMapKey(newConnectionStatus, getConnectionStatus(cxn), url)
}
connectionsStatus = newConnectionStatus
}

onMount(() => {
fetchConnectionStatus()
const interval = setInterval(fetchConnectionStatus, 800)

return () => {
clearInterval(interval)
}
})
</script>

<SelectButton {options} bind:value={selectedOptions} multiple class="text-left">
<div class="flex items-center gap-2" slot="item" let:option>
{connectionsStatus.get(option)?.size || 0}
{displayConnectionType(option)}
</div>
</SelectButton>
{#each connections as url (url)}
{@const relay = $relaysByUrl.get(url)}
<AltColor
background
class="cursor-pointer justify-between rounded-md p-6 shadow"
on:click={() => {
selected = url
activeTab = "notices"
}}>
<div class="flex min-w-0 shrink-0 items-start 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(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 items-center justify-end gap-2 text-sm">
{#each options.filter(o => connectionsStatus.get(o)?.has(url)) as o}
{@const opt = displayConnectionType(o)}
<div class="flex items-center gap-2">
<span>{opt}</span>
<div
class:!bg-danger={opt.includes("Failed") || opt.includes("Not")}
class:!bg-warning={opt == "Logging in" ||
o == ConnectionType.WaitReconnect ||
o == ConnectionType.UnstableConnection}
class="h-3 w-3 rounded-full bg-success" />
</div>
{/each}
</div>
</div>
</AltColor>
{/each}
68 changes: 68 additions & 0 deletions src/app/views/PublishesEvents.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
import {thunks, type Thunk} from "@welshman/app"
import {assoc, now, remove, sortBy} from "@welshman/lib"
import {pluralize, seconds} from "hurdak"
import Tile from "src/partials/Tile.svelte"
import PublishCard from "src/app/shared/PublishCard.svelte"
import {LOCAL_RELAY_URL} from "@welshman/util"
import {PublishStatus} from "@welshman/net"
import {get} from "svelte/store"

const hasStatus = (thunk: Thunk, statuses: PublishStatus[]) =>
Object.values(get(thunk.status)).some(s => statuses.includes(s.status))

$: recent = (Object.values($thunks) as Thunk[]).filter(
t =>
remove(LOCAL_RELAY_URL, t.request.relays).length > 0 &&
t.event.created_at > now() - seconds(24, "hour"),
)
$: relays = new Set(
remove(
LOCAL_RELAY_URL,
recent.flatMap(({request}) => request.relays),
),
)
$: success = recent.filter(t => hasStatus(t, [PublishStatus.Success]))
$: pending = recent.filter(
t => hasStatus(t, [PublishStatus.Pending]) && !hasStatus(t, [PublishStatus.Success]),
)

// If the page gets refreshed before pending finishes, it hangs. Set stuff to failed
$: {
for (const t of recent) {
if (t.event.created_at < now() - seconds(1, "minute")) {
for (const [url, s] of Object.entries(t.status)) {
if (s.status === PublishStatus.Pending) {
t.status.update(assoc(url, {status: PublishStatus.Failure, message: ""}))
}
}
}
}
}
</script>

<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}
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载