refactor: Remove routing summary — controls already communicate it

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-10 00:17:52 +00:00
parent e1582edfec
commit fffc84e14c
2 changed files with 69 additions and 42 deletions

View File

@@ -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<Record<string, { ok: boolean; message: string }>>({});
const [provisioningInstance, setProvisioningInstance] = useState<string | null>(null);
const [blockSuccess, setBlockSuccess] = useState<string | null>(null);
@@ -263,7 +272,10 @@ export function CrowdSecComponent() {
)}
<p className="text-xs text-muted-foreground">
{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.`;
})()}
</p>
</>
)}
@@ -633,10 +645,17 @@ export function CrowdSecComponent() {
<p className="text-sm text-muted-foreground text-center py-3">No bouncers registered. Provision an instance to add them.</p>
) : (
<div className="space-y-2">
{status.bouncers.map((b) => {
{(() => {
// 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 (
<div key={b.name} className="flex items-center justify-between py-2 px-3 bg-muted rounded">
<div key={b.name} className="py-2 px-3 bg-muted rounded">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<span className="font-mono text-sm truncate max-w-64">{b.name}</span>
@@ -664,10 +683,27 @@ export function CrowdSecComponent() {
</Button>
)}
</div>
{childBouncers.length > 0 && (
<div className="mt-1.5 ml-4 space-y-1 border-l-2 border-muted-foreground/20 pl-3">
{childBouncers.map(c => {
const ip = c.name.split('@')[1];
return (
<div key={c.name} className="flex items-center gap-2 text-xs text-muted-foreground">
<span className="font-mono">{ip}</span>
<Badge variant={c.revoked ? 'secondary' : 'default'} className="text-[10px] h-3.5">
{c.revoked ? 'revoked' : 'active'}
</Badge>
</div>
);
})}
</div>
)}
</div>
);
});
})()}
</div>
)}
</CardContent>
</Card>

View File

@@ -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<Set<string>>(new Set());
const [showAddForm, setShowAddForm] = useState(false);
@@ -63,7 +62,6 @@ export function ServicesComponent() {
const certDomains = new Set<string>(
(certStatus?.certs ?? []).filter(c => c.cert.exists).map(c => c.domain)
);
const publicIp = ddnsStatus?.currentIP;
const registerMutation = useMutation({
mutationFn: (svc: Record<string, unknown>) => 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 (
<Card key={service.domain}>
@@ -277,12 +274,6 @@ export function ServicesComponent() {
</div>
</div>
{/* Routing summary */}
<p className="text-xs text-muted-foreground">
Routes to <span className="font-mono text-foreground">{backendHost}</span> on LAN
{service.reach === 'public' && <>, <span className="font-mono text-foreground">{publicIp ?? '—'}</span> externally</>}
</p>
{/* Source + deregister */}
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Source: {service.source}</span>