From fffc84e14cb5d911992cdee43d55affa0812f084 Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Fri, 10 Jul 2026 00:17:52 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20Remove=20routing=20summary=20?= =?UTF-8?q?=E2=80=94=20controls=20already=20communicate=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend field + Public toggle already tell you where traffic routes. Removed redundant "Routes to X on LAN, Y externally" line and unused ddns/publicIp/backendHost vars. Co-Authored-By: Claude Opus 4.6 (1M context) --- web/src/components/CrowdSecComponent.tsx | 100 +++++++++++++++-------- web/src/components/ServicesComponent.tsx | 11 +-- 2 files changed, 69 insertions(+), 42 deletions(-) diff --git a/web/src/components/CrowdSecComponent.tsx b/web/src/components/CrowdSecComponent.tsx index bf0c0a3..36a9575 100644 --- a/web/src/components/CrowdSecComponent.tsx +++ b/web/src/components/CrowdSecComponent.tsx @@ -126,7 +126,6 @@ interface BlockFormValues { } export function CrowdSecComponent() { - const instances: string[] = []; const { status, isLoadingStatus, @@ -148,6 +147,16 @@ export function CrowdSecComponent() { isProvisioning, } = useCrowdSec(); + // Derive instance names from CrowdSec machines/bouncers with "wc-" prefix convention + const instances = [...new Set([ + ...(status?.machines ?? []) + .filter(m => m.machineId.startsWith('wc-')) + .map(m => m.machineId.replace(/^wc-/, '')), + ...(status?.bouncers ?? []) + .filter(b => b.name.startsWith('wc-bouncer-')) + .map(b => b.name.replace(/^wc-bouncer-/, '').split('@')[0]), + ])].sort(); + const [provisionResults, setProvisionResults] = useState>({}); const [provisioningInstance, setProvisioningInstance] = useState(null); const [blockSuccess, setBlockSuccess] = useState(null); @@ -263,7 +272,10 @@ export function CrowdSecComponent() { )}

{status.machines.length > 0 && `${status.machines.length} agent${status.machines.length !== 1 ? 's' : ''} reporting. `} - {status.bouncers.length > 0 && `${status.bouncers.length} enforcement point${status.bouncers.length !== 1 ? 's' : ''} active.`} + {(() => { + const count = status.bouncers.filter(b => !b.name.includes('@')).length; + return count > 0 && `${count} bouncer${count !== 1 ? 's' : ''} active.`; + })()}

)} @@ -633,39 +645,63 @@ export function CrowdSecComponent() {

No bouncers registered. Provision an instance to add them.

) : (
- {status.bouncers.map((b) => { - const isFirewallBouncer = b.type === 'crowdsec-firewall-bouncer'; - return ( -
-
-
- {b.name} - - {b.revoked ? 'revoked' : 'active'} - - {isFirewallBouncer && ( - - perimeter - + {(() => { + // Group auto-created child bouncers (name contains @) under their parent + const parents = status.bouncers.filter(b => !b.name.includes('@')); + const children = status.bouncers.filter(b => b.name.includes('@')); + + return parents.map((b) => { + const isFirewallBouncer = b.type === 'crowdsec-firewall-bouncer'; + const childBouncers = children.filter(c => c.name.startsWith(b.name + '@')); + return ( +
+
+
+
+ {b.name} + + {b.revoked ? 'revoked' : 'active'} + + {isFirewallBouncer && ( + + perimeter + + )} +
+

+ {isFirewallBouncer ? 'Crowdsec Firewall Bouncer' : b.type?.replace(/-/g, ' ') || ''} +

+
+ {!isFirewallBouncer && ( + )}
-

- {isFirewallBouncer ? 'Crowdsec Firewall Bouncer' : b.type?.replace(/-/g, ' ') || ''} -

+ {childBouncers.length > 0 && ( +
+ {childBouncers.map(c => { + const ip = c.name.split('@')[1]; + return ( +
+ {ip} + + {c.revoked ? 'revoked' : 'active'} + +
+ ); + })} +
+ )}
- {!isFirewallBouncer && ( - - )} -
- ); - })} + ); + }); + })()}
)} diff --git a/web/src/components/ServicesComponent.tsx b/web/src/components/ServicesComponent.tsx index 5e8684b..8aece9b 100644 --- a/web/src/components/ServicesComponent.tsx +++ b/web/src/components/ServicesComponent.tsx @@ -14,7 +14,6 @@ import { import { useHaproxy } from '../hooks/useHaproxy'; import { useServices } from '../hooks/useServices'; import { useCert } from '../hooks/useCert'; -import { useDdns } from '../hooks/useDdns'; import { usePageHelp } from '../hooks/usePageHelp'; import type { RegisteredService } from '../services/api/networking'; import { servicesApi, haproxyApi } from '../services/api/networking'; @@ -45,7 +44,7 @@ export function ServicesComponent() { } = useHaproxy(); const { services, isLoading: isServicesLoading } = useServices(); const { status: certStatus } = useCert(); - const { status: ddnsStatus } = useDdns(); + const [expandedDomains, setExpandedDomains] = useState>(new Set()); const [showAddForm, setShowAddForm] = useState(false); @@ -63,7 +62,6 @@ export function ServicesComponent() { const certDomains = new Set( (certStatus?.certs ?? []).filter(c => c.cert.exists).map(c => c.domain) ); - const publicIp = ddnsStatus?.currentIP; const registerMutation = useMutation({ mutationFn: (svc: Record) => servicesApi.register(svc as any), @@ -200,7 +198,6 @@ export function ServicesComponent() { const tlsStatus = getTlsStatus(service, certDomains); const ddnsStatus2 = service.reach === 'public' ? 'ok' as const : 'na' as const; const isPassthrough = service.tls === 'passthrough' || service.backend.type === 'tcp-passthrough'; - const backendHost = service.backend.address.split(':')[0]; return ( @@ -277,12 +274,6 @@ export function ServicesComponent() {
- {/* Routing summary */} -

- Routes to {backendHost} on LAN - {service.reach === 'public' && <>, {publicIp ?? '—'} externally} -

- {/* Source + deregister */}
Source: {service.source}