feat: add enabled state to BGP peer types and UI. Implement peer enabling toggle in the network page, allowing users to enable or disable peers during creation and editing. Update relevant API calls and UI components to reflect the new functionality.
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m0s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m1s
CI / docker-bird (push) Has been skipped
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been skipped
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been skipped
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been skipped
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been skipped
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been skipped
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been skipped
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been skipped
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been skipped

This commit is contained in:
Denozordec
2026-04-06 01:35:13 +07:00
parent 3e8543177e
commit 80965adc0c
2 changed files with 57 additions and 6 deletions
+2
View File
@@ -123,6 +123,7 @@ export type PeerRow = {
name?: string; name?: string;
neighbor: string; neighbor: string;
remote_asn?: number; remote_asn?: number;
enabled?: boolean;
session_state: string; session_state: string;
bgp_speaker_id: string | null; bgp_speaker_id: string | null;
}; };
@@ -131,6 +132,7 @@ export type BgpPeerCreate = {
neighbor: string; neighbor: string;
remote_asn: number; remote_asn: number;
bgp_speaker_id?: string | null; bgp_speaker_id?: string | null;
enabled?: boolean;
}; };
export type BgpPeerPatch = Partial<BgpPeerCreate>; export type BgpPeerPatch = Partial<BgpPeerCreate>;
+55 -6
View File
@@ -41,6 +41,7 @@
TableHeader, TableHeader,
TableRow TableRow
} from '$lib/components/ui/table/index.js'; } from '$lib/components/ui/table/index.js';
import { Switch } from '$lib/components/ui/switch/index.js';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Plus from '@lucide/svelte/icons/plus'; import Plus from '@lucide/svelte/icons/plus';
import Pencil from '@lucide/svelte/icons/pencil'; import Pencil from '@lucide/svelte/icons/pencil';
@@ -56,9 +57,11 @@
let peerForm = $state<BgpPeerCreate & { bgp_speaker_id?: string | null }>({ let peerForm = $state<BgpPeerCreate & { bgp_speaker_id?: string | null }>({
neighbor: '', neighbor: '',
remote_asn: 0, remote_asn: 0,
bgp_speaker_id: null bgp_speaker_id: null,
enabled: true
}); });
let peerSaving = $state(false); let peerSaving = $state(false);
let peerToggleId = $state<string | null>(null);
let peerDeleteTarget = $state<PeerRow | null>(null); let peerDeleteTarget = $state<PeerRow | null>(null);
// --- Speakers --- // --- Speakers ---
@@ -93,14 +96,31 @@
// --- Peer actions --- // --- Peer actions ---
function openPeerCreate() { function openPeerCreate() {
peerEdit = null; peerEdit = null;
peerForm = { neighbor: '', remote_asn: 0, bgp_speaker_id: null }; peerForm = { neighbor: '', remote_asn: 0, bgp_speaker_id: null, enabled: true };
peerDialog = true; peerDialog = true;
} }
function openPeerEdit(p: PeerRow) { function openPeerEdit(p: PeerRow) {
peerEdit = p; peerEdit = p;
peerForm = { neighbor: p.neighbor, remote_asn: p.remote_asn ?? 0, bgp_speaker_id: p.bgp_speaker_id }; peerForm = {
neighbor: p.neighbor,
remote_asn: p.remote_asn ?? 0,
bgp_speaker_id: p.bgp_speaker_id,
enabled: p.enabled !== false
};
peerDialog = true; peerDialog = true;
} }
async function setPeerEnabled(p: PeerRow, enabled: boolean) {
peerToggleId = p.id;
try {
await apiMutate(`/v1/peers/${p.id}`, 'PATCH', { enabled });
toast.success(enabled ? 'Пир включён' : 'Пир отключён');
await loadAll();
} catch (e) {
toast.error(e instanceof Error ? e.message : String(e));
} finally {
peerToggleId = null;
}
}
async function savePeer() { async function savePeer() {
if (!peerForm.neighbor || !peerForm.remote_asn) { if (!peerForm.neighbor || !peerForm.remote_asn) {
toast.error('Укажите адрес и ASN'); toast.error('Укажите адрес и ASN');
@@ -112,8 +132,10 @@
await apiMutate(`/v1/peers/${peerEdit.id}`, 'PATCH', peerForm); await apiMutate(`/v1/peers/${peerEdit.id}`, 'PATCH', peerForm);
toast.success('Пир обновлён'); toast.success('Пир обновлён');
} else { } else {
// API: omitted "enabled" decodes as false in Go; default new peers to enabled. await apiMutate('/v1/peers', 'POST', {
await apiMutate('/v1/peers', 'POST', { ...peerForm, enabled: true }); ...peerForm,
enabled: peerForm.enabled !== false
});
toast.success('Пир создан'); toast.success('Пир создан');
} }
peerDialog = false; peerDialog = false;
@@ -218,6 +240,7 @@
<TableRow> <TableRow>
<TableHead>Адрес</TableHead> <TableHead>Адрес</TableHead>
<TableHead>Remote ASN</TableHead> <TableHead>Remote ASN</TableHead>
<TableHead class="w-[4.5rem] text-center">Вкл.</TableHead>
<TableHead>Состояние сессии</TableHead> <TableHead>Состояние сессии</TableHead>
<TableHead>Спикер</TableHead> <TableHead>Спикер</TableHead>
<TableHead class="w-20"></TableHead> <TableHead class="w-20"></TableHead>
@@ -228,6 +251,14 @@
<TableRow> <TableRow>
<TableCell class="font-mono">{p.neighbor}</TableCell> <TableCell class="font-mono">{p.neighbor}</TableCell>
<TableCell class="font-mono">{p.remote_asn ?? '—'}</TableCell> <TableCell class="font-mono">{p.remote_asn ?? '—'}</TableCell>
<TableCell class="text-center">
<Switch
size="sm"
checked={p.enabled !== false}
disabled={peersLoading || peerToggleId === p.id}
onCheckedChange={(v) => setPeerEnabled(p, v)}
/>
</TableCell>
<TableCell> <TableCell>
<Badge variant={sessionBadge(p.session_state)}>{p.session_state || '—'}</Badge> <Badge variant={sessionBadge(p.session_state)}>{p.session_state || '—'}</Badge>
</TableCell> </TableCell>
@@ -241,7 +272,7 @@
</TableRow> </TableRow>
{:else} {:else}
<TableRow> <TableRow>
<TableCell colspan={5} class="text-muted-foreground text-center py-8"> <TableCell colspan={6} class="text-muted-foreground text-center py-8">
{peersLoading ? 'Загрузка…' : 'Нет пиров.'} {peersLoading ? 'Загрузка…' : 'Нет пиров.'}
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -328,6 +359,24 @@
<Label for="p-speaker">Спикер (ID, опционально)</Label> <Label for="p-speaker">Спикер (ID, опционально)</Label>
<Input id="p-speaker" placeholder="speaker-id" bind:value={peerForm.bgp_speaker_id} /> <Input id="p-speaker" placeholder="speaker-id" bind:value={peerForm.bgp_speaker_id} />
</div> </div>
<div
class="border-border bg-muted/30 flex flex-row items-center justify-between gap-4 rounded-lg border p-3"
>
<div class="grid min-w-0 flex-1 gap-1 pr-2">
<Label for="p-enabled" class="text-foreground leading-snug">Включён</Label>
<p class="text-muted-foreground text-xs leading-snug">
Выключенный пир не попадает в конфиг BIRD до следующей ревизии.
</p>
</div>
<Switch
id="p-enabled"
class="shrink-0"
checked={peerForm.enabled !== false}
onCheckedChange={(v) => {
peerForm = { ...peerForm, enabled: v };
}}
/>
</div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onclick={() => (peerDialog = false)}>Отмена</Button> <Button variant="outline" onclick={() => (peerDialog = false)}>Отмена</Button>