feat: update BGP community structure and related documentation. Refactor BGP community handling by renaming fields from 'name' and 'kind' to 'community' and 'title' across the codebase. Update OpenAPI specifications, database interactions, and UI components to reflect these changes, enhancing clarity and consistency in community management.
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 25s
CI / go (push) Successful in 24s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m1s
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 / bird2 (push) Successful in 15s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m1s

This commit is contained in:
Denozordec
2026-04-06 14:52:29 +07:00
parent 482eeb122d
commit a2cbfacd45
20 changed files with 226 additions and 88 deletions
+4 -4
View File
@@ -113,12 +113,12 @@ export type DohProfilesResponse = Page<DohProfile>;
// ---- Communities ----
export type BgpCommunity = {
id: string;
name: string;
kind: string | null;
community: string;
title: string;
};
export type BgpCommunityCreate = {
name: string;
kind?: string;
community: string;
title?: string;
};
export type BgpCommunityPatch = Partial<BgpCommunityCreate>;
export type CommunitiesResponse = Page<BgpCommunity>;
+19 -20
View File
@@ -9,7 +9,6 @@
DohProfileCreate,
DohProfilesResponse
} from '$lib/api/types.js';
import { Badge } from '$lib/components/ui/badge/index.js';
import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
@@ -52,7 +51,7 @@
let commLoading = $state(false);
let commDialog = $state(false);
let commEdit = $state<BgpCommunity | null>(null);
let commForm = $state<BgpCommunityCreate>({ name: '', kind: '' });
let commForm = $state<BgpCommunityCreate>({ community: '', title: '' });
let commSaving = $state(false);
let commDeleteTarget = $state<BgpCommunity | null>(null);
@@ -92,21 +91,27 @@
onMount(() => { loadComm(); loadDoh(); });
// --- Community actions ---
function commDisplay(c: BgpCommunity | null) {
if (!c) return '';
const t = c.title?.trim();
return t || c.community;
}
function openCommCreate() {
commEdit = null;
commForm = { name: '', kind: '' };
commForm = { community: '', title: '' };
commDialog = true;
}
function openCommEdit(c: BgpCommunity) {
commEdit = c;
commForm = { name: c.name, kind: c.kind ?? '' };
commForm = { community: c.community, title: c.title ?? '' };
commDialog = true;
}
async function saveComm() {
if (!commForm.name.trim()) { toast.error('Укажите название'); return; }
if (!commForm.community.trim()) { toast.error('Укажите community'); return; }
commSaving = true;
try {
const body = { ...commForm, kind: commForm.kind || undefined };
const body = { ...commForm, title: commForm.title?.trim() || undefined };
if (commEdit) {
await apiMutate(`/v1/communities/${commEdit.id}`, 'PATCH', body);
toast.success('Community обновлена');
@@ -208,8 +213,8 @@
<Table>
<TableHeader>
<TableRow>
<TableHead>Community</TableHead>
<TableHead>Название</TableHead>
<TableHead>Вид</TableHead>
<TableHead>ID</TableHead>
<TableHead class="w-20"></TableHead>
</TableRow>
@@ -217,14 +222,8 @@
<TableBody>
{#each communities as c (c.id)}
<TableRow>
<TableCell class="font-medium">{c.name}</TableCell>
<TableCell>
{#if c.kind}
<Badge variant="outline">{c.kind}</Badge>
{:else}
<span class="text-muted-foreground text-sm"></span>
{/if}
</TableCell>
<TableCell class="font-mono text-sm font-medium">{c.community}</TableCell>
<TableCell>{c.title?.trim() || '—'}</TableCell>
<TableCell class="font-mono text-xs text-muted-foreground">{c.id}</TableCell>
<TableCell>
<div class="flex gap-1">
@@ -307,12 +306,12 @@
</DialogHeader>
<div class="space-y-4 py-2">
<div class="space-y-1.5">
<Label for="c-name">Название</Label>
<Input id="c-name" bind:value={commForm.name} placeholder="rt:65000:100" />
<Label for="c-community">Community</Label>
<Input id="c-community" bind:value={commForm.community} placeholder="65001:120" />
</div>
<div class="space-y-1.5">
<Label for="c-kind">Вид (опционально)</Label>
<Input id="c-kind" bind:value={commForm.kind} placeholder="large / standard" />
<Label for="c-title">Название</Label>
<Input id="c-title" bind:value={commForm.title} placeholder="Человекочитаемое имя для списков" />
</div>
</div>
<DialogFooter>
@@ -325,7 +324,7 @@
<AlertDialog open={!!commDeleteTarget} onOpenChange={(v) => { if (!v) commDeleteTarget = null; }}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Удалить community «{commDeleteTarget?.name}»?</AlertDialogTitle>
<AlertDialogTitle>Удалить community «{commDisplay(commDeleteTarget)}»?</AlertDialogTitle>
<AlertDialogDescription>Это приведёт к удалению привязки во всех модулях.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
+25 -17
View File
@@ -390,9 +390,17 @@
}
}
function communityName(id: string | null) {
function communityLabel(id: string | null) {
if (!id) return '—';
return communities.find((c) => c.id === id)?.name ?? id.slice(0, 8) + '…';
const c = communities.find((x) => x.id === id);
if (!c) return id.slice(0, 8) + '…';
const t = c.title?.trim();
return t || c.community;
}
function communityOptionLabel(c: BgpCommunity) {
const t = c.title?.trim();
return t || c.community;
}
const activeTab = $derived.by(() => {
@@ -464,7 +472,7 @@
</Card>
<Card class="p-3">
<p class="text-muted-foreground text-xs">Community по умолч.</p>
<p class="font-semibold text-sm">{communityName(mod.default_community_id)}</p>
<p class="font-semibold text-sm">{communityLabel(mod.default_community_id)}</p>
</Card>
</div>
@@ -510,7 +518,7 @@
? new Date(entry.asn_resolved_at).toLocaleString('ru-RU')
: '—'}
</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityName(entry.community_id)}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityLabel(entry.community_id)}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button variant="ghost" size="icon-sm" onclick={() => openAsEdit(entry)}><Pencil class="size-3.5" /></Button>
@@ -552,7 +560,7 @@
<TableRow>
<TableCell class="font-mono text-xs max-w-xs truncate">{src.url}</TableCell>
<TableCell><Badge variant="outline">{src.source_kind}</Badge></TableCell>
<TableCell class="text-muted-foreground text-sm">{communityName(src.community_id)}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityLabel(src.community_id)}</TableCell>
<TableCell class="text-muted-foreground text-sm">{src.refresh_interval_sec ? `${src.refresh_interval_sec}с` : '—'}</TableCell>
<TableCell>
<div class="flex gap-1">
@@ -592,7 +600,7 @@
{#each domainEntries as entry (entry.id)}
<TableRow>
<TableCell class="font-mono">{entry.fqdn}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityName(entry.community_id)}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityLabel(entry.community_id)}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button variant="ghost" size="icon-sm" onclick={() => openDomainEdit(entry)}><Pencil class="size-3.5" /></Button>
@@ -631,7 +639,7 @@
{#each ipEntries as entry (entry.id)}
<TableRow>
<TableCell class="font-mono">{entry.prefix}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityName(entry.community_id)}</TableCell>
<TableCell class="text-muted-foreground text-sm">{communityLabel(entry.community_id)}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button variant="ghost" size="icon-sm" onclick={() => openIpEdit(entry)}><Pencil class="size-3.5" /></Button>
@@ -683,12 +691,12 @@
<Label for="e-comm">Community по умолчанию</Label>
<Select type="single" value={editForm.default_community_id ?? ''} onValueChange={(v) => { editForm.default_community_id = v || null; }}>
<SelectTrigger id="e-comm" class="w-full">
{editForm.default_community_id ? communityName(editForm.default_community_id) : 'Не выбрано'}
{editForm.default_community_id ? communityLabel(editForm.default_community_id) : 'Не выбрано'}
</SelectTrigger>
<SelectContent>
<SelectItem value="">Не выбрано</SelectItem>
{#each communities as c}
<SelectItem value={c.id}>{c.name}</SelectItem>
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
{/each}
</SelectContent>
</Select>
@@ -765,12 +773,12 @@
<Label for="as-comm">Community</Label>
<Select type="single" value={asForm.community_id ?? ''} onValueChange={(v) => { asForm.community_id = v || null; }}>
<SelectTrigger id="as-comm" class="w-full">
{asForm.community_id ? communityName(asForm.community_id) : 'Не выбрано'}
{asForm.community_id ? communityLabel(asForm.community_id) : 'Не выбрано'}
</SelectTrigger>
<SelectContent>
<SelectItem value="">Не выбрано</SelectItem>
{#each communities as c}
<SelectItem value={c.id}>{c.name}</SelectItem>
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
{/each}
</SelectContent>
</Select>
@@ -815,12 +823,12 @@
<Label for="cdn-comm">Community</Label>
<Select type="single" value={cdnForm.community_id ?? ''} onValueChange={(v) => { cdnForm.community_id = v || null; }}>
<SelectTrigger id="cdn-comm" class="w-full">
{cdnForm.community_id ? communityName(cdnForm.community_id) : 'Не выбрано'}
{cdnForm.community_id ? communityLabel(cdnForm.community_id) : 'Не выбрано'}
</SelectTrigger>
<SelectContent>
<SelectItem value="">Не выбрано</SelectItem>
{#each communities as c}
<SelectItem value={c.id}>{c.name}</SelectItem>
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
{/each}
</SelectContent>
</Select>
@@ -865,12 +873,12 @@
<Label for="dom-comm">Community</Label>
<Select type="single" value={domainForm.community_id ?? ''} onValueChange={(v) => { domainForm.community_id = v || null; }}>
<SelectTrigger id="dom-comm" class="w-full">
{domainForm.community_id ? communityName(domainForm.community_id) : 'Не выбрано'}
{domainForm.community_id ? communityLabel(domainForm.community_id) : 'Не выбрано'}
</SelectTrigger>
<SelectContent>
<SelectItem value="">Не выбрано</SelectItem>
{#each communities as c}
<SelectItem value={c.id}>{c.name}</SelectItem>
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
{/each}
</SelectContent>
</Select>
@@ -911,11 +919,11 @@
<Label for="ip-comm">Community (обязательно)</Label>
<Select type="single" value={ipForm.community_id} onValueChange={(v) => { ipForm.community_id = v; }}>
<SelectTrigger id="ip-comm" class="w-full">
{ipForm.community_id ? communityName(ipForm.community_id) : 'Выберите community'}
{ipForm.community_id ? communityLabel(ipForm.community_id) : 'Выберите community'}
</SelectTrigger>
<SelectContent>
{#each communities as c}
<SelectItem value={c.id}>{c.name}</SelectItem>
<SelectItem value={c.id}>{communityOptionLabel(c)}</SelectItem>
{/each}
</SelectContent>
</Select>