forked from coracle/caravel
Add relay activity
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { For, Show } from "solid-js"
|
||||
import type { Activity } from "@/lib/hooks"
|
||||
|
||||
const ACTIVITY_LABELS: Record<string, string> = {
|
||||
create_relay: "Relay created",
|
||||
update_relay: "Relay updated",
|
||||
deactivate_relay: "Relay deactivated",
|
||||
fail_relay_sync: "Relay sync failed",
|
||||
create_tenant: "Account created",
|
||||
update_tenant_billing_anchor: "Billing anchor updated",
|
||||
update_tenant_nwc_url: "Wallet connection updated",
|
||||
create_invoice: "Invoice created",
|
||||
mark_invoice_paid: "Invoice paid",
|
||||
mark_invoice_attempted: "Invoice payment attempted",
|
||||
mark_invoice_sent: "Invoice sent",
|
||||
mark_invoice_closed: "Invoice closed",
|
||||
}
|
||||
|
||||
function formatDate(ts: number) {
|
||||
return new Date(ts * 1000).toLocaleString(undefined, {
|
||||
month: "short", day: "numeric", year: "numeric",
|
||||
hour: "2-digit", minute: "2-digit",
|
||||
})
|
||||
}
|
||||
|
||||
export default function ActivityFeed(props: { activity: Activity[]; loading: boolean }) {
|
||||
return (
|
||||
<section class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-wider mb-6">Activity</h3>
|
||||
<Show when={props.loading}>
|
||||
<p class="text-sm text-gray-400">Loading activity...</p>
|
||||
</Show>
|
||||
<Show when={!props.loading && props.activity.length === 0}>
|
||||
<p class="text-sm text-gray-400">No activity yet.</p>
|
||||
</Show>
|
||||
<Show when={!props.loading && props.activity.length > 0}>
|
||||
<ol class="relative border-l border-gray-200 space-y-6 ml-3">
|
||||
<For each={props.activity}>
|
||||
{(item) => (
|
||||
<li class="pl-6">
|
||||
<span class="absolute -left-1.5 mt-1.5 h-3 w-3 rounded-full border-2 border-white bg-gray-300" />
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
{ACTIVITY_LABELS[item.activity_type] ?? item.activity_type.replace(/_/g, " ")}
|
||||
</p>
|
||||
<time class="text-xs text-gray-400">{formatDate(item.created_at)}</time>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ol>
|
||||
</Show>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -9,6 +9,25 @@ import ToggleField from "@/components/ToggleField"
|
||||
import { setToastMessage } from "@/components/Toast"
|
||||
import { plans } from "@/lib/state"
|
||||
|
||||
const STATUS_STYLES: Record<string, string> = {
|
||||
active: "bg-green-50 text-green-700 border-green-200",
|
||||
new: "bg-blue-50 text-blue-700 border-blue-200",
|
||||
pending: "bg-yellow-50 text-yellow-700 border-yellow-200",
|
||||
provisioning_failed: "bg-red-50 text-red-700 border-red-200",
|
||||
deactivated: "bg-gray-100 text-gray-500 border-gray-200",
|
||||
suspended: "bg-orange-50 text-orange-700 border-orange-200",
|
||||
}
|
||||
|
||||
function StatusBadge(props: { status: string }) {
|
||||
const styles = () => STATUS_STYLES[props.status] ?? "bg-gray-100 text-gray-500 border-gray-200"
|
||||
const label = () => props.status.replace(/_/g, " ")
|
||||
return (
|
||||
<span class={`inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-medium capitalize ${styles()}`}>
|
||||
{label()}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function DetailSection(props: { title: string; children: any }) {
|
||||
return (
|
||||
<div>
|
||||
@@ -114,7 +133,10 @@ export default function RelayDetailCard(props: RelayDetailCardProps) {
|
||||
<img src={r().info_icon} alt="" class="w-14 h-14 rounded-xl object-cover flex-shrink-0 border border-gray-200" />
|
||||
</Show>
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{r().info_name || r().subdomain}</h1>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{r().info_name || r().subdomain}</h1>
|
||||
<StatusBadge status={r().status} />
|
||||
</div>
|
||||
<a
|
||||
href={`wss://${r().subdomain}.spaces.coracle.social`}
|
||||
class="text-sm text-blue-600 hover:underline break-all"
|
||||
|
||||
@@ -115,6 +115,15 @@ export type Invoice = {
|
||||
period_end: number
|
||||
}
|
||||
|
||||
export type Activity = {
|
||||
id: string
|
||||
tenant: string
|
||||
created_at: number
|
||||
activity_type: string
|
||||
resource_type: string
|
||||
resource_id: string
|
||||
}
|
||||
|
||||
export type Identity = {
|
||||
pubkey: string
|
||||
is_admin: boolean
|
||||
@@ -229,6 +238,10 @@ export function getRelay(id: string) {
|
||||
return callApi<undefined, Relay>("GET", `/relays/${id}`)
|
||||
}
|
||||
|
||||
export function listRelayActivity(id: string) {
|
||||
return callApi<undefined, Activity[]>("GET", `/relays/${id}/activity`)
|
||||
}
|
||||
|
||||
export function createRelay(input: CreateRelayInput) {
|
||||
return callApi<CreateRelayInput, Relay>("POST", "/relays", input)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ import {
|
||||
deactivateRelay,
|
||||
getRelay,
|
||||
getTenant,
|
||||
listRelayActivity,
|
||||
listRelays,
|
||||
listTenantInvoices,
|
||||
listTenantRelays,
|
||||
listTenants,
|
||||
updateRelay,
|
||||
updateTenantBilling,
|
||||
type Activity,
|
||||
type CreateRelayInput,
|
||||
type Relay,
|
||||
type Tenant,
|
||||
@@ -88,6 +90,8 @@ export const useTenantInvoices = () => createResource(() => listTenantInvoices(a
|
||||
|
||||
export const useRelay = (relayId: () => string) => createResource(relayId, getRelay)
|
||||
|
||||
export const useRelayActivity = (relayId: () => string) => createResource(relayId, listRelayActivity)
|
||||
|
||||
export const useAdminTenants = () => createResource(listTenants)
|
||||
|
||||
export const useAdminRelays = () => createResource(listRelays)
|
||||
@@ -135,4 +139,4 @@ export async function getRelayMembers(url: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export type { Relay, Tenant }
|
||||
export type { Activity, Relay, Tenant }
|
||||
|
||||
@@ -5,7 +5,8 @@ import PageContainer from "@/components/PageContainer"
|
||||
import RelayDetailCard from "@/components/RelayDetailCard"
|
||||
import ResourceState from "@/components/ResourceState"
|
||||
import useMinLoading from "@/components/useMinLoading"
|
||||
import { useRelay } from "@/lib/hooks"
|
||||
import ActivityFeed from "@/components/ActivityFeed"
|
||||
import { useRelay, useRelayActivity } from "@/lib/hooks"
|
||||
import useRelayToggles from "@/lib/useRelayToggles"
|
||||
|
||||
export default function AdminRelayDetail() {
|
||||
@@ -13,6 +14,7 @@ export default function AdminRelayDetail() {
|
||||
const relayId = () => params.id ?? ""
|
||||
const [relay, { refetch, mutate }] = useRelay(relayId)
|
||||
const loading = useMinLoading(() => relay.loading && !relay())
|
||||
const [activity] = useRelayActivity(relayId)
|
||||
const { busy, handleDeactivate, toggles } = useRelayToggles(relayId, relay, { refetch, mutate })
|
||||
|
||||
return (
|
||||
@@ -21,7 +23,7 @@ export default function AdminRelayDetail() {
|
||||
<ResourceState loading={loading()} error={relay.error} loadingText="Loading relay..." errorText="Failed to load relay." class="mb-4" />
|
||||
<Show when={!loading() && relay()}>
|
||||
{(r) => (
|
||||
<div class="mb-6">
|
||||
<div class="space-y-6 mb-6">
|
||||
<RelayDetailCard
|
||||
relay={r()}
|
||||
showTenant
|
||||
@@ -32,6 +34,7 @@ export default function AdminRelayDetail() {
|
||||
showPlanActions={false}
|
||||
{...toggles}
|
||||
/>
|
||||
<ActivityFeed activity={activity() ?? []} loading={activity.loading} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -5,7 +5,8 @@ import PageContainer from "@/components/PageContainer"
|
||||
import RelayDetailCard from "@/components/RelayDetailCard"
|
||||
import ResourceState from "@/components/ResourceState"
|
||||
import useMinLoading from "@/components/useMinLoading"
|
||||
import { getRelayMembers, useRelay } from "@/lib/hooks"
|
||||
import ActivityFeed from "@/components/ActivityFeed"
|
||||
import { getRelayMembers, useRelay, useRelayActivity } from "@/lib/hooks"
|
||||
import useRelayToggles from "@/lib/useRelayToggles"
|
||||
|
||||
export default function RelayDetail() {
|
||||
@@ -18,6 +19,7 @@ export default function RelayDetail() {
|
||||
})
|
||||
const [members] = createResource(relayUrl, getRelayMembers)
|
||||
const loading = useMinLoading(() => relay.loading && !relay())
|
||||
const [activity] = useRelayActivity(relayId)
|
||||
const { busy, handleDeactivate, handleUpdatePlan, toggles } = useRelayToggles(relayId, relay, { refetch, mutate })
|
||||
|
||||
return (
|
||||
@@ -26,7 +28,7 @@ export default function RelayDetail() {
|
||||
<ResourceState loading={loading()} error={relay.error} loadingText="Loading relay..." errorText="Failed to load relay." class="mb-4" />
|
||||
<Show when={!loading() && relay()}>
|
||||
{(r) => (
|
||||
<div class="mb-6">
|
||||
<div class="space-y-6 mb-6">
|
||||
<RelayDetailCard
|
||||
relay={r()}
|
||||
currentMembers={members.length}
|
||||
@@ -36,6 +38,7 @@ export default function RelayDetail() {
|
||||
onUpdatePlan={handleUpdatePlan}
|
||||
{...toggles}
|
||||
/>
|
||||
<ActivityFeed activity={activity() ?? []} loading={activity.loading} />
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
Reference in New Issue
Block a user