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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user