refactor: Replace 'reach' field with 'public' boolean in service registration and related components
This commit is contained in:
@@ -143,22 +143,8 @@ export function CrowdSecComponent() {
|
||||
deleteDecision,
|
||||
deleteBouncer,
|
||||
deleteMachine,
|
||||
provision,
|
||||
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);
|
||||
const [timescale, setTimescale] = useState<Timescale>('24h');
|
||||
|
||||
@@ -167,20 +153,6 @@ export function CrowdSecComponent() {
|
||||
});
|
||||
const selectedType = watch('type');
|
||||
|
||||
function handleProvision(instanceName: string) {
|
||||
setProvisioningInstance(instanceName);
|
||||
provision(instanceName, {
|
||||
onSuccess: (data) => {
|
||||
setProvisionResults((prev) => ({ ...prev, [instanceName]: { ok: true, message: data.message } }));
|
||||
setProvisioningInstance(null);
|
||||
},
|
||||
onError: (err) => {
|
||||
setProvisionResults((prev) => ({ ...prev, [instanceName]: { ok: false, message: (err as Error).message } }));
|
||||
setProvisioningInstance(null);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function onBlockSubmit(values: BlockFormValues) {
|
||||
setBlockSuccess(null);
|
||||
const req: AddDecisionRequest = { ip: values.ip, type: values.type, reason: values.reason, duration: values.duration };
|
||||
@@ -707,88 +679,6 @@ export function CrowdSecComponent() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Provision Instances */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Zap className="h-4 w-4 text-muted-foreground" />
|
||||
<CardTitle>Provision Instances</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<strong>Provisioning</strong> connects a Wild Cloud instance's CrowdSec agent to this LAPI —
|
||||
generating credentials, registering agent and bouncer, and writing the LAPI URL into the
|
||||
instance's app config. After provisioning, redeploy the CrowdSec app on the instance.
|
||||
Safe to re-run.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{instances.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No instances found.</p>
|
||||
) : (
|
||||
instances.map((instanceName) => {
|
||||
const agentName = `wc-${instanceName}`;
|
||||
const bouncerName = `wc-bouncer-${instanceName}`;
|
||||
const agent = status.machines.find((m) => m.machineId === agentName);
|
||||
const bouncer = status.bouncers.find((b) => b.name === bouncerName);
|
||||
const isActive = provisioningInstance === instanceName;
|
||||
const result = provisionResults[instanceName];
|
||||
|
||||
return (
|
||||
<div key={instanceName} className="rounded border p-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono text-sm font-medium">{instanceName}</span>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={agent ? 'outline' : 'default'}
|
||||
onClick={() => handleProvision(instanceName)}
|
||||
disabled={isProvisioning || isActive}
|
||||
className="gap-2 h-7"
|
||||
>
|
||||
{isActive
|
||||
? <><Loader2 className="h-3 w-3 animate-spin" />Provisioning…</>
|
||||
: agent
|
||||
? <><Zap className="h-3 w-3" />Re-provision</>
|
||||
: <><Zap className="h-3 w-3" />Provision</>
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground">Agent:</span>
|
||||
{agent ? (
|
||||
<Badge variant={agent.isValidated ? 'default' : 'secondary'} className="text-xs h-4">
|
||||
{agent.isValidated ? 'validated' : 'pending'}
|
||||
{agent.last_heartbeat && ` · ${formatRelativeTime(agent.last_heartbeat)}`}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">not provisioned</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-muted-foreground">Bouncer:</span>
|
||||
{bouncer ? (
|
||||
<Badge variant={bouncer.revoked ? 'secondary' : 'default'} className="text-xs h-4">
|
||||
{bouncer.revoked ? 'revoked' : 'active'}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">not provisioned</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{result && (
|
||||
<Alert variant={result.ok ? 'default' : 'error'} className="py-2">
|
||||
{result.ok ? <CheckCircle className="h-3 w-3" /> : <AlertCircle className="h-3 w-3" />}
|
||||
<AlertDescription className="text-xs">{result.message}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -55,7 +55,7 @@ export function ServicesComponent() {
|
||||
// Add form state
|
||||
const [newDomain, setNewDomain] = useState('');
|
||||
const [newBackendAddr, setNewBackendAddr] = useState('');
|
||||
const [newReach, setNewReach] = useState<string>('public');
|
||||
const [newPublic, setNewPublic] = useState(true);
|
||||
const [newSubdomains, setNewSubdomains] = useState(false);
|
||||
const [newTls, setNewTls] = useState<string>('passthrough');
|
||||
|
||||
@@ -68,7 +68,7 @@ export function ServicesComponent() {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['services'] });
|
||||
setShowAddForm(false);
|
||||
setNewDomain(''); setNewBackendAddr(''); setNewReach('public'); setNewSubdomains(false); setNewTls('passthrough');
|
||||
setNewDomain(''); setNewBackendAddr(''); setNewPublic(true); setNewSubdomains(false); setNewTls('passthrough');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ export function ServicesComponent() {
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<Switch checked={newReach === 'public'} onCheckedChange={v => setNewReach(v ? 'public' : 'internal')} />
|
||||
<Switch checked={newPublic} onCheckedChange={setNewPublic} />
|
||||
Public
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
@@ -165,7 +165,7 @@ export function ServicesComponent() {
|
||||
onClick={() => registerMutation.mutate({
|
||||
domain: newDomain,
|
||||
backend: { address: newBackendAddr, type: newTls === 'passthrough' ? 'tcp-passthrough' : 'http' },
|
||||
reach: newReach, subdomains: newSubdomains, tls: newTls, source: 'manual',
|
||||
public: newPublic, subdomains: newSubdomains, tls: newTls, source: 'manual',
|
||||
})}>
|
||||
{registerMutation.isPending ? <Loader2 className="h-3 w-3 animate-spin mr-1" /> : <Plus className="h-3 w-3 mr-1" />}
|
||||
Register
|
||||
@@ -196,7 +196,7 @@ export function ServicesComponent() {
|
||||
{services.map(service => {
|
||||
const isExpanded = expandedDomains.has(service.domain);
|
||||
const tlsStatus = getTlsStatus(service, certDomains);
|
||||
const ddnsStatus2 = service.reach === 'public' ? 'ok' as const : 'na' as const;
|
||||
const ddnsStatus2 = service.public ? 'ok' as const : 'na' as const;
|
||||
const isPassthrough = service.tls === 'passthrough' || service.backend.type === 'tcp-passthrough';
|
||||
|
||||
return (
|
||||
@@ -242,11 +242,11 @@ export function ServicesComponent() {
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={service.reach === 'public'}
|
||||
onCheckedChange={v => updateMutation.mutate({ domain: service.domain, updates: { reach: v ? 'public' : 'internal' } })}
|
||||
checked={service.public}
|
||||
onCheckedChange={v => updateMutation.mutate({ domain: service.domain, updates: { public: v } })}
|
||||
disabled={updateMutation.isPending}
|
||||
/>
|
||||
<Label className="text-sm">{service.reach === 'public' ? 'Public' : 'Private'}</Label>
|
||||
<Label className="text-sm">{service.public ? 'Public' : 'Private'}</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user