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() { export function CrowdSecComponent() {
const instances: string[] = [];
const { const {
status, status,
isLoadingStatus, isLoadingStatus,
@@ -148,6 +147,16 @@ export function CrowdSecComponent() {
isProvisioning, isProvisioning,
} = useCrowdSec(); } = 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 [provisionResults, setProvisionResults] = useState<Record<string, { ok: boolean; message: string }>>({});
const [provisioningInstance, setProvisioningInstance] = useState<string | null>(null); const [provisioningInstance, setProvisioningInstance] = useState<string | null>(null);
const [blockSuccess, setBlockSuccess] = 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"> <p className="text-xs text-muted-foreground">
{status.machines.length > 0 && `${status.machines.length} agent${status.machines.length !== 1 ? 's' : ''} reporting. `} {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> </p>
</> </>
)} )}
@@ -633,39 +645,63 @@ export function CrowdSecComponent() {
<p className="text-sm text-muted-foreground text-center py-3">No bouncers registered. Provision an instance to add them.</p> <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"> <div className="space-y-2">
{status.bouncers.map((b) => { {(() => {
const isFirewallBouncer = b.type === 'crowdsec-firewall-bouncer'; // Group auto-created child bouncers (name contains @) under their parent
return ( const parents = status.bouncers.filter(b => !b.name.includes('@'));
<div key={b.name} className="flex items-center justify-between py-2 px-3 bg-muted rounded"> const children = status.bouncers.filter(b => b.name.includes('@'));
<div className="space-y-0.5">
<div className="flex items-center gap-2"> return parents.map((b) => {
<span className="font-mono text-sm truncate max-w-64">{b.name}</span> const isFirewallBouncer = b.type === 'crowdsec-firewall-bouncer';
<Badge variant={b.revoked ? 'secondary' : 'default'} className="text-xs h-4"> const childBouncers = children.filter(c => c.name.startsWith(b.name + '@'));
{b.revoked ? 'revoked' : 'active'} return (
</Badge> <div key={b.name} className="py-2 px-3 bg-muted rounded">
{isFirewallBouncer && ( <div className="flex items-center justify-between">
<Badge variant="outline" className="text-xs h-4 border-orange-400 text-orange-600 dark:text-orange-400"> <div className="space-y-0.5">
perimeter <div className="flex items-center gap-2">
</Badge> <span className="font-mono text-sm truncate max-w-64">{b.name}</span>
<Badge variant={b.revoked ? 'secondary' : 'default'} className="text-xs h-4">
{b.revoked ? 'revoked' : 'active'}
</Badge>
{isFirewallBouncer && (
<Badge variant="outline" className="text-xs h-4 border-orange-400 text-orange-600 dark:text-orange-400">
perimeter
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">
{isFirewallBouncer ? 'Crowdsec Firewall Bouncer' : b.type?.replace(/-/g, ' ') || ''}
</p>
</div>
{!isFirewallBouncer && (
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0 text-muted-foreground hover:text-red-500"
onClick={() => deleteBouncer(b.name)}
>
<Trash2 className="h-3 w-3" />
</Button>
)} )}
</div> </div>
<p className="text-xs text-muted-foreground"> {childBouncers.length > 0 && (
{isFirewallBouncer ? 'Crowdsec Firewall Bouncer' : b.type?.replace(/-/g, ' ') || ''} <div className="mt-1.5 ml-4 space-y-1 border-l-2 border-muted-foreground/20 pl-3">
</p> {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>
{!isFirewallBouncer && ( );
<Button });
variant="ghost" })()}
size="sm"
className="h-7 w-7 p-0 text-muted-foreground hover:text-red-500"
onClick={() => deleteBouncer(b.name)}
>
<Trash2 className="h-3 w-3" />
</Button>
)}
</div>
);
})}
</div> </div>
)} )}
</CardContent> </CardContent>

View File

@@ -14,7 +14,6 @@ import {
import { useHaproxy } from '../hooks/useHaproxy'; import { useHaproxy } from '../hooks/useHaproxy';
import { useServices } from '../hooks/useServices'; import { useServices } from '../hooks/useServices';
import { useCert } from '../hooks/useCert'; import { useCert } from '../hooks/useCert';
import { useDdns } from '../hooks/useDdns';
import { usePageHelp } from '../hooks/usePageHelp'; import { usePageHelp } from '../hooks/usePageHelp';
import type { RegisteredService } from '../services/api/networking'; import type { RegisteredService } from '../services/api/networking';
import { servicesApi, haproxyApi } from '../services/api/networking'; import { servicesApi, haproxyApi } from '../services/api/networking';
@@ -45,7 +44,7 @@ export function ServicesComponent() {
} = useHaproxy(); } = useHaproxy();
const { services, isLoading: isServicesLoading } = useServices(); const { services, isLoading: isServicesLoading } = useServices();
const { status: certStatus } = useCert(); const { status: certStatus } = useCert();
const { status: ddnsStatus } = useDdns();
const [expandedDomains, setExpandedDomains] = useState<Set<string>>(new Set()); const [expandedDomains, setExpandedDomains] = useState<Set<string>>(new Set());
const [showAddForm, setShowAddForm] = useState(false); const [showAddForm, setShowAddForm] = useState(false);
@@ -63,7 +62,6 @@ export function ServicesComponent() {
const certDomains = new Set<string>( const certDomains = new Set<string>(
(certStatus?.certs ?? []).filter(c => c.cert.exists).map(c => c.domain) (certStatus?.certs ?? []).filter(c => c.cert.exists).map(c => c.domain)
); );
const publicIp = ddnsStatus?.currentIP;
const registerMutation = useMutation({ const registerMutation = useMutation({
mutationFn: (svc: Record<string, unknown>) => servicesApi.register(svc as any), mutationFn: (svc: Record<string, unknown>) => servicesApi.register(svc as any),
@@ -200,7 +198,6 @@ export function ServicesComponent() {
const tlsStatus = getTlsStatus(service, certDomains); const tlsStatus = getTlsStatus(service, certDomains);
const ddnsStatus2 = service.reach === 'public' ? 'ok' as const : 'na' as const; const ddnsStatus2 = service.reach === 'public' ? 'ok' as const : 'na' as const;
const isPassthrough = service.tls === 'passthrough' || service.backend.type === 'tcp-passthrough'; const isPassthrough = service.tls === 'passthrough' || service.backend.type === 'tcp-passthrough';
const backendHost = service.backend.address.split(':')[0];
return ( return (
<Card key={service.domain}> <Card key={service.domain}>
@@ -277,12 +274,6 @@ export function ServicesComponent() {
</div> </div>
</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 */} {/* Source + deregister */}
<div className="flex items-center justify-between text-xs text-muted-foreground"> <div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Source: {service.source}</span> <span>Source: {service.source}</span>