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:
@@ -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');
|
||||
|
||||
@@ -36,6 +36,18 @@ export function getActivePeerOrigins(): Set<string> {
|
||||
return activePeerOrigins;
|
||||
}
|
||||
|
||||
// ─── Federation change listeners (for real-time panel updates) ───────────────
|
||||
const federationChangeListeners = new Set<() => void>();
|
||||
|
||||
export function onFederationPeersChanged(cb: () => void): () => void {
|
||||
federationChangeListeners.add(cb);
|
||||
return () => { federationChangeListeners.delete(cb); };
|
||||
}
|
||||
|
||||
function notifyFederationChangeListeners(): void {
|
||||
for (const cb of federationChangeListeners) cb();
|
||||
}
|
||||
|
||||
// ─── Connection state ─────────────────────────────────────────────────────────
|
||||
|
||||
interface ConnectionState {
|
||||
@@ -717,6 +729,7 @@ function handleEvent(origin: string, event: ServerEvent): void {
|
||||
'warning',
|
||||
10000,
|
||||
);
|
||||
notifyFederationChangeListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -724,6 +737,17 @@ function handleEvent(origin: string, event: ServerEvent): void {
|
||||
rejectedPeerOrigins.delete(event.peerOrigin);
|
||||
awaitingApprovalPeerOrigins.delete(event.peerOrigin);
|
||||
activePeerOrigins.add(event.peerOrigin);
|
||||
notifyFederationChangeListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'federation_peers_changed': {
|
||||
notifyFederationChangeListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'federation_approval_request_received': {
|
||||
notifyFederationChangeListeners();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user