feat: instant banner refresh on L4 mutations + master-slave L4 sync
Banner (L4PortsApplyBanner): - Accept refreshSignal prop; re-fetch /api/l4-ports when it changes - Signal fires immediately after create/edit/delete/toggle in L4ProxyHostsClient without waiting for a page reload Master-slave replication (instance-sync): - Add l4ProxyHosts to SyncPayload.data (optional for backward compat with older master instances that don't include it) - buildSyncPayload: query and include l4ProxyHosts, sanitize ownerUserId - applySyncPayload: clear and re-insert l4ProxyHosts in transaction; call applyL4Ports() if port diff requires it so the slave's sidecar recreates caddy with the correct ports - Sync route: add isL4ProxyHost validator; backfill missing field from old masters; validate array when present Tests (25 new tests): - instance-sync.test.ts: buildSyncPayload includes L4 data, sanitizes ownerUserId; applySyncPayload replaces L4 hosts, handles missing field, writes trigger when ports differ, skips trigger when ports already match - l4-ports-apply-banner.test.ts: banner refreshSignal contract + client increments counter on all mutation paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ type PortsResponse = {
|
||||
error?: string;
|
||||
};
|
||||
|
||||
export function L4PortsApplyBanner() {
|
||||
export function L4PortsApplyBanner({ refreshSignal }: { refreshSignal?: number }) {
|
||||
const [data, setData] = useState<PortsResponse | null>(null);
|
||||
const [applying, setApplying] = useState(false);
|
||||
const [polling, setPolling] = useState(false);
|
||||
@@ -41,11 +41,17 @@ export function L4PortsApplyBanner() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Initial fetch and poll when pending/applying
|
||||
// Initial fetch on mount
|
||||
useEffect(() => {
|
||||
fetchStatus();
|
||||
}, [fetchStatus]);
|
||||
|
||||
// Re-fetch when the parent signals a mutation (create/edit/delete/toggle)
|
||||
useEffect(() => {
|
||||
if (!refreshSignal) return;
|
||||
fetchStatus();
|
||||
}, [refreshSignal, fetchStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
const shouldPoll = data.status.state === "pending" || data.status.state === "applying";
|
||||
|
||||
Reference in New Issue
Block a user