diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts index 236f652..a6a5a35 100644 --- a/web/src/lib/api/types.ts +++ b/web/src/lib/api/types.ts @@ -123,6 +123,7 @@ export type PeerRow = { name?: string; neighbor: string; remote_asn?: number; + enabled?: boolean; session_state: string; bgp_speaker_id: string | null; }; @@ -131,6 +132,7 @@ export type BgpPeerCreate = { neighbor: string; remote_asn: number; bgp_speaker_id?: string | null; + enabled?: boolean; }; export type BgpPeerPatch = Partial; diff --git a/web/src/routes/network/+page.svelte b/web/src/routes/network/+page.svelte index 3ab4bc6..4198021 100644 --- a/web/src/routes/network/+page.svelte +++ b/web/src/routes/network/+page.svelte @@ -41,6 +41,7 @@ TableHeader, TableRow } from '$lib/components/ui/table/index.js'; + import { Switch } from '$lib/components/ui/switch/index.js'; import { toast } from 'svelte-sonner'; import Plus from '@lucide/svelte/icons/plus'; import Pencil from '@lucide/svelte/icons/pencil'; @@ -56,9 +57,11 @@ let peerForm = $state({ neighbor: '', remote_asn: 0, - bgp_speaker_id: null + bgp_speaker_id: null, + enabled: true }); let peerSaving = $state(false); + let peerToggleId = $state(null); let peerDeleteTarget = $state(null); // --- Speakers --- @@ -93,14 +96,31 @@ // --- Peer actions --- function openPeerCreate() { peerEdit = null; - peerForm = { neighbor: '', remote_asn: 0, bgp_speaker_id: null }; + peerForm = { neighbor: '', remote_asn: 0, bgp_speaker_id: null, enabled: true }; peerDialog = true; } function openPeerEdit(p: PeerRow) { 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; } + 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() { if (!peerForm.neighbor || !peerForm.remote_asn) { toast.error('Укажите адрес и ASN'); @@ -112,8 +132,10 @@ await apiMutate(`/v1/peers/${peerEdit.id}`, 'PATCH', peerForm); toast.success('Пир обновлён'); } else { - // API: omitted "enabled" decodes as false in Go; default new peers to enabled. - await apiMutate('/v1/peers', 'POST', { ...peerForm, enabled: true }); + await apiMutate('/v1/peers', 'POST', { + ...peerForm, + enabled: peerForm.enabled !== false + }); toast.success('Пир создан'); } peerDialog = false; @@ -218,6 +240,7 @@ Адрес Remote ASN + Вкл. Состояние сессии Спикер @@ -228,6 +251,14 @@ {p.neighbor} {p.remote_asn ?? '—'} + + setPeerEnabled(p, v)} + /> + {p.session_state || '—'} @@ -241,7 +272,7 @@ {:else} - + {peersLoading ? 'Загрузка…' : 'Нет пиров.'} @@ -328,6 +359,24 @@ +
+
+ +

+ Выключенный пир не попадает в конфиг BIRD до следующей ревизии. +

+
+ { + peerForm = { ...peerForm, enabled: v }; + }} + /> +