feat: real-time Federation panel updates via WS events

Added federation_peers_changed (no-payload signal) broadcast from every
peer state mutation, and federation_approval_request_received when a new
approval request is queued. Client subscribes via onFederationPeersChanged
callback registry. FederationPanel and PendingApprovals debounce-refetch
on any event. sendToAdmins helper broadcasts only to admin users.
This commit is contained in:
Jannis Braun
2026-04-20 18:28:10 +02:00
parent 6afad97bd1
commit 3d8709d20a
7 changed files with 95 additions and 0 deletions
@@ -4,6 +4,7 @@ import { useUIStore } from '../../../stores/uiStore';
import { Toggle } from '../../ui/Toggle';
import { ConfirmDialog } from '../../ui/ConfirmDialog';
import { api } from '../../../api/client';
import { onFederationPeersChanged } from '../../../hooks/useWebSocket';
import type { InstanceAdminSettings } from '@backspace/shared';
import type { FederationPeer, ApprovalRequest } from '../../../api/client';
@@ -611,6 +612,18 @@ function PendingApprovals({ onCountChange }: { onCountChange?: (count: number) =
fetchRequests();
}, [fetchRequests]);
// Real-time updates: re-fetch approval requests on federation changes
useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;
const unsub = onFederationPeersChanged(() => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fetchRequests();
}, 500);
});
return () => { unsub(); clearTimeout(timeout); };
}, [fetchRequests]);
const handleConfirm = async () => {
if (!confirmAction) return;
const { type, request: req } = confirmAction;
@@ -765,6 +778,18 @@ export function FederationPanel({ onApprovalCountChange }: { onApprovalCountChan
fetchPeers();
}, [fetchPeers]);
// Real-time updates: re-fetch peers and approval requests on any federation change
useEffect(() => {
let timeout: ReturnType<typeof setTimeout>;
const unsub = onFederationPeersChanged(() => {
clearTimeout(timeout);
timeout = setTimeout(() => {
fetchPeers();
}, 500);
});
return () => { unsub(); clearTimeout(timeout); };
}, [fetchPeers]);
// Derived peer lists
const activePeers = peers.filter((p) => p.status !== 'revoked');
const revokedPeers = peers.filter((p) => p.status === 'revoked');