Add space status indicator #245

This commit is contained in:
Jon Staab
2025-10-20 17:05:22 -07:00
parent e5b8987a9d
commit 7476767aa7
6 changed files with 93 additions and 47 deletions

View File

@@ -35,6 +35,7 @@
import Alerts from "@app/components/Alerts.svelte" import Alerts from "@app/components/Alerts.svelte"
import RoomCreate from "@app/components/RoomCreate.svelte" import RoomCreate from "@app/components/RoomCreate.svelte"
import MenuSpaceRoomItem from "@app/components/MenuSpaceRoomItem.svelte" import MenuSpaceRoomItem from "@app/components/MenuSpaceRoomItem.svelte"
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
import { import {
ENABLE_ZAPS, ENABLE_ZAPS,
MESSAGE_FILTER, MESSAGE_FILTER,
@@ -123,7 +124,7 @@
class="flex w-full flex-col rounded-xl p-3 transition-all hover:bg-base-100" class="flex w-full flex-col rounded-xl p-3 transition-all hover:bg-base-100"
onclick={openMenu}> onclick={openMenu}>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<strong class="ellipsize flex items-center gap-3"> <strong class="ellipsize flex items-center gap-1">
<RelayName {url} /> <RelayName {url} />
</strong> </strong>
<Icon icon={AltArrowDown} /> <Icon icon={AltArrowDown} />
@@ -245,10 +246,13 @@
{/if} {/if}
</div> </div>
</SecondaryNavSection> </SecondaryNavSection>
<div class="p-4"> <div class="flex flex-col gap-2 p-4">
<button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}> <Button class="btn btn-neutral btn-sm" onclick={showDetail}>
<SocketStatusIndicator {url} />
</Button>
<Button class="btn btn-neutral btn-sm" onclick={manageAlerts}>
<Icon icon={Bell} /> <Icon icon={Bell} />
Manage Alerts Manage Alerts
</button> </Button>
</div> </div>
</div> </div>

View File

@@ -6,17 +6,22 @@
import {notifications} from "@app/util/notifications" import {notifications} from "@app/util/notifications"
import {makeSpacePath} from "@app/util/routes" import {makeSpacePath} from "@app/util/routes"
import {pushDrawer} from "@app/util/modal" import {pushDrawer} from "@app/util/modal"
import {deriveSocketStatus} from "@app/core/state"
const {url} = $props() const {url} = $props()
const path = makeSpacePath(url) + ":mobile" const path = makeSpacePath(url) + ":mobile"
const status = deriveSocketStatus(url)
const openMenu = () => pushDrawer(MenuSpace, {url}) const openMenu = () => pushDrawer(MenuSpace, {url})
</script> </script>
<Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden"> <Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden">
<Icon icon={MenuDots} /> <Icon icon={MenuDots} />
{#if $notifications.has(path)} {#if $status.theme !== "success"}
<div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-{$status.theme}"></div>
{:else if $notifications.has(path)}
<div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div> <div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div>
{/if} {/if}
</Button> </Button>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import StatusIndicator from "@lib/components/StatusIndicator.svelte"
import {deriveSocketStatus} from "@app/core/state"
type Props = {
url: string
}
const {url}: Props = $props()
const status = deriveSocketStatus(url)
</script>
<StatusIndicator class="bg-{$status.theme}">{$status.title}</StatusIndicator>

View File

@@ -2,8 +2,8 @@
import {deriveRelay} from "@welshman/app" import {deriveRelay} from "@welshman/app"
import Server from "@assets/icons/server.svg?dataurl" import Server from "@assets/icons/server.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import SocketStatusIndicator from "@lib/components/SocketStatusIndicator.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte" import ProfileLink from "@app/components/ProfileLink.svelte"
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
interface Props { interface Props {
url: string url: string

View File

@@ -26,8 +26,23 @@ import {
last, last,
} from "@welshman/lib" } from "@welshman/lib"
import type {Socket} from "@welshman/net" import type {Socket} from "@welshman/net"
import {Pool, load, AuthStateEvent, AuthStatus, SocketEvent, netContext} from "@welshman/net" import {
import {collection, custom, deriveEvents, deriveEventsMapped, withGetter} from "@welshman/store" Pool,
load,
SocketStatus,
AuthStateEvent,
AuthStatus,
SocketEvent,
netContext,
} from "@welshman/net"
import {
collection,
custom,
throttled,
deriveEvents,
deriveEventsMapped,
withGetter,
} from "@welshman/store"
import {isKindFeed, findFeed} from "@welshman/feeds" import {isKindFeed, findFeed} from "@welshman/feeds"
import { import {
getIdFilters, getIdFilters,
@@ -771,6 +786,54 @@ export const deriveSocket = (url: string) =>
return () => subs.forEach(call) return () => subs.forEach(call)
}) })
export const deriveSocketStatus = (url: string) =>
throttled(
800,
derived([deriveSocket(url), relaysMostlyRestricted], ([$socket, $relaysMostlyRestricted]) => {
if ($socket.status === SocketStatus.Opening) {
return {theme: "warning", title: "Connecting"}
}
if ($socket.status === SocketStatus.Closing) {
return {theme: "gray-500", title: "Not Connected"}
}
if ($socket.status === SocketStatus.Closed) {
return {theme: "gray-500", title: "Not Connected"}
}
if ($socket.status === SocketStatus.Error) {
return {theme: "error", title: "Failed to Connect"}
}
if ($socket.auth.status === AuthStatus.Requested) {
return {theme: "warning", title: "Authenticating"}
}
if ($socket.auth.status === AuthStatus.PendingSignature) {
return {theme: "warning", title: "Authenticating"}
}
if ($socket.auth.status === AuthStatus.DeniedSignature) {
return {theme: "error", title: "Failed to Authenticate"}
}
if ($socket.auth.status === AuthStatus.PendingResponse) {
return {theme: "warning", title: "Authenticating"}
}
if ($socket.auth.status === AuthStatus.Forbidden) {
return {theme: "error", title: "Access Denied"}
}
if ($relaysMostlyRestricted[url]) {
return {theme: "error", title: "Access Denied"}
}
return {theme: "success", title: "Connected"}
}),
)
export const deriveTimeout = (timeout: number) => { export const deriveTimeout = (timeout: number) => {
const store = writable<boolean>(false) const store = writable<boolean>(false)

View File

@@ -1,39 +0,0 @@
<script lang="ts">
import {throttled} from "@welshman/store"
import {AuthStatus, SocketStatus} from "@welshman/net"
import StatusIndicator from "@lib/components/StatusIndicator.svelte"
import {deriveSocket, relaysMostlyRestricted} from "@app/core/state"
type Props = {
url: string
}
const {url}: Props = $props()
const socket = throttled(800, deriveSocket(url))
</script>
{#if $socket.status === SocketStatus.Open}
{#if $socket.auth.status === AuthStatus.None}
<StatusIndicator class="bg-green-500">Connected</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.Requested}
<StatusIndicator class="bg-yellow-500">Authenticating</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.PendingSignature}
<StatusIndicator class="bg-yellow-500">Authenticating</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.DeniedSignature}
<StatusIndicator class="bg-red-500">Failed to Authenticate</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.PendingResponse}
<StatusIndicator class="bg-yellow-500">Authenticating</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.Forbidden || $relaysMostlyRestricted[url]}
<StatusIndicator class="bg-red-500">Access Denied</StatusIndicator>
{:else if $socket.auth.status === AuthStatus.Ok}
<StatusIndicator class="bg-green-500">Connected</StatusIndicator>
{/if}
{:else if $socket.status === SocketStatus.Opening}
<StatusIndicator class="bg-yellow-500">Connecting</StatusIndicator>
{:else if $socket.status === SocketStatus.Closing}
<StatusIndicator class="bg-gray-500">Not Connected</StatusIndicator>
{:else if $socket.status === SocketStatus.Closed}
<StatusIndicator class="bg-gray-500">Not Connected</StatusIndicator>
{:else if $socket.status === SocketStatus.Error}
<StatusIndicator class="bg-red-500">Failed to Connect</StatusIndicator>
{/if}