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:
@@ -39,12 +39,15 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
const [editHost, setEditHost] = useState<L4ProxyHost | null>(null);
|
||||
const [deleteHost, setDeleteHost] = useState<L4ProxyHost | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(initialSearch);
|
||||
const [bannerRefresh, setBannerRefresh] = useState(0);
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const signalBannerRefresh = () => setBannerRefresh(n => n + 1);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm(initialSearch);
|
||||
}, [initialSearch]);
|
||||
@@ -66,6 +69,7 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
|
||||
const handleToggleEnabled = async (id: number, enabled: boolean) => {
|
||||
await toggleL4ProxyHostAction(id, enabled);
|
||||
signalBannerRefresh();
|
||||
};
|
||||
|
||||
const columns = [
|
||||
@@ -215,7 +219,7 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
|
||||
return (
|
||||
<Stack spacing={4}>
|
||||
<L4PortsApplyBanner />
|
||||
<L4PortsApplyBanner refreshSignal={bannerRefresh} />
|
||||
<PageHeader
|
||||
title="L4 Proxy Hosts"
|
||||
description="Define TCP/UDP stream proxies powered by caddy-l4. Port mappings are applied automatically by the L4 port manager."
|
||||
@@ -245,6 +249,7 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
onClose={() => {
|
||||
setCreateOpen(false);
|
||||
setTimeout(() => setDuplicateHost(null), 200);
|
||||
signalBannerRefresh();
|
||||
}}
|
||||
initialData={duplicateHost}
|
||||
/>
|
||||
@@ -253,7 +258,10 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
<EditL4HostDialog
|
||||
open={!!editHost}
|
||||
host={editHost}
|
||||
onClose={() => setEditHost(null)}
|
||||
onClose={() => {
|
||||
setEditHost(null);
|
||||
signalBannerRefresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -261,7 +269,10 @@ export default function L4ProxyHostsClient({ hosts, pagination, initialSearch }:
|
||||
<DeleteL4HostDialog
|
||||
open={!!deleteHost}
|
||||
host={deleteHost}
|
||||
onClose={() => setDeleteHost(null)}
|
||||
onClose={() => {
|
||||
setDeleteHost(null);
|
||||
signalBannerRefresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user