import { useCallback, useEffect, useRef, useState } from 'react' import { Loading, ErrorState } from '../../../components/PageState.jsx' import { useShardFeed } from '../../../lib/useShardFeed.js' import { describe, kindLabel } from '../../../lib/shardEvents.js' import { ago } from '../../../lib/format.js' import { api } from '../../../api/client.js' // Full live feed from the admin SSE channel — every kind, incl. staff audit, // cheat detection and login attempts that the public channel never carries. function AdminLiveFeed() { const { events, connected } = useShardFeed({ url: api.adminShardStreamUrl, max: 60 }) return (

Live feed (all events)

{connected ? 'Live' : 'Offline'}
{events.length === 0 ? (

Waiting for shard events…

) : ( )}
) } // uo-link sidecar control panel. The auth token is write-only over this API — // stored encrypted, never returned — same convention as the Discord bot token. // Saving (re)starts the WS ingest client, so Enabled/URL/token changes take // effect immediately with no redeploy. function Toggle({ checked, onChange, label }) { return ( ) } const STATUS_COLOR = { connected: '#7fd0a4', reconnecting: '#e0b070', error: '#d98b84', disconnected: 'var(--muted)', } function StatusPanel({ config }) { const color = STATUS_COLOR[config.status] || 'var(--muted)' const ingest = config.ingest || {} const health = config.health || {} return (
{config.status || 'disconnected'}
{config.statusDetail && (

{config.statusDetail}

)}
Shard link: {config.pluginConnected ? 'up' : 'down'} WS ingest: {ingest.connected ? 'connected' : 'offline'} Reconnects: {ingest.reconnects ?? 0} SSE clients: {(config.sse?.publicClients ?? 0) + (config.sse?.adminClients ?? 0)} {config.lastEventAt && Last event: {new Date(config.lastEventAt).toLocaleString()}} {health.uptime && Sidecar uptime: {health.uptime}}
) } // ── Town crier ────────────────────────────────────────────────────────────── function TownCrier() { const [id, setId] = useState('') const [text, setText] = useState('') const [durationSec, setDurationSec] = useState(3600) const [busy, setBusy] = useState(false) const [msg, setMsg] = useState('') const [error, setError] = useState('') async function post() { setBusy(true); setMsg(''); setError('') const lines = text.split('\n').map((l) => l.trim()).filter(Boolean) if (!id.trim() || lines.length === 0) { setBusy(false) return setError('An id and at least one line are required.') } try { await api.admin.postTownCrier({ id: id.trim(), lines, durationSec: Number(durationSec) || undefined }) setMsg(`Posted “${id.trim()}”.`) } catch (err) { setError(err.message || 'Could not post.') } finally { setBusy(false) } } async function remove() { if (!id.trim()) return setError('Enter the id to remove.') setBusy(true); setMsg(''); setError('') try { await api.admin.deleteTownCrier(id.trim()) setMsg(`Removed “${id.trim()}”.`) } catch (err) { setError(err.message || 'Could not remove.') } finally { setBusy(false) } } return (

Town crier

Broadcast a message that every in-game town crier announces until it expires. Re-posting the same id replaces it.