feat: add bird configuration preview functionality with tabbed interface for fragments and prefixes. Implement fragment extraction and default path selection for improved user experience in previewing BIRD configurations.
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 1m3s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m3s
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
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 1m3s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m3s
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:
@@ -60,6 +60,24 @@
|
||||
let previewData = $state<unknown>(null);
|
||||
let prefixesData = $state<string[]>([]);
|
||||
let previewLoading = $state(false);
|
||||
let previewSubTab = $state<'bird' | 'prefixes'>('bird');
|
||||
let birdPreviewPath = $state('');
|
||||
|
||||
function asPreviewFragments(d: unknown): Record<string, string> {
|
||||
if (!d || typeof d !== 'object' || Array.isArray(d)) return {};
|
||||
const out: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(d as Record<string, unknown>)) {
|
||||
if (typeof v === 'string') out[k] = v;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function defaultBirdPreviewPath(frags: Record<string, string>): string {
|
||||
if (frags['_bird_full_expanded.conf']) return '_bird_full_expanded.conf';
|
||||
if (frags['bird.conf']) return 'bird.conf';
|
||||
const keys = Object.keys(frags).sort();
|
||||
return keys[0] ?? '';
|
||||
}
|
||||
|
||||
// Diff
|
||||
let diffDialog = $state(false);
|
||||
@@ -112,8 +130,10 @@
|
||||
previewRevision = rev;
|
||||
previewLoading = true;
|
||||
previewDialog = true;
|
||||
previewSubTab = 'bird';
|
||||
previewData = null;
|
||||
prefixesData = [];
|
||||
birdPreviewPath = '';
|
||||
try {
|
||||
const [prev, pfx] = await Promise.all([
|
||||
apiJSON<unknown>(`/v1/revisions/${rev.id}/preview`),
|
||||
@@ -121,6 +141,8 @@
|
||||
]);
|
||||
previewData = prev;
|
||||
prefixesData = (pfx.items ?? []).map((p: { prefix: string }) => p.prefix);
|
||||
const frags = asPreviewFragments(prev);
|
||||
birdPreviewPath = defaultBirdPreviewPath(frags);
|
||||
} catch (e) {
|
||||
toast.error(e instanceof Error ? e.message : String(e));
|
||||
} finally {
|
||||
@@ -483,22 +505,58 @@
|
||||
|
||||
<!-- Preview dialog -->
|
||||
<Dialog bind:open={previewDialog}>
|
||||
<DialogContent class="sm:max-w-2xl">
|
||||
<DialogContent class="sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Ревизия {previewRevision?.id.slice(0, 8)}…</DialogTitle>
|
||||
<DialogDescription>Предпросмотр префиксов ревизии</DialogDescription>
|
||||
<DialogDescription>
|
||||
Срендеренный конфиг BIRD 2 (фрагменты из control plane) и материализованные префиксы.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{#if previewLoading}
|
||||
<p class="text-muted-foreground py-4 text-center text-sm">Загрузка…</p>
|
||||
{:else}
|
||||
<p class="text-sm mb-2"><span class="font-medium">Префиксов:</span> {prefixesData.length}</p>
|
||||
<ScrollArea class="max-h-80 rounded border p-3">
|
||||
{#each prefixesData as pfx}
|
||||
<p class="font-mono text-xs">{pfx}</p>
|
||||
{:else}
|
||||
<p class="text-muted-foreground text-xs">Нет префиксов</p>
|
||||
{/each}
|
||||
</ScrollArea>
|
||||
<Tabs bind:value={previewSubTab} class="w-full">
|
||||
<TabsList class="grid w-full max-w-md grid-cols-2">
|
||||
<TabsTrigger value="bird">Конфиг BIRD</TabsTrigger>
|
||||
<TabsTrigger value="prefixes">Префиксы</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="bird" class="mt-3 space-y-2">
|
||||
{@const frags = asPreviewFragments(previewData)}
|
||||
{#if Object.keys(frags).length === 0}
|
||||
<p class="text-muted-foreground text-sm">Нет фрагментов превью (старая ревизия или пустой render).</p>
|
||||
{:else}
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<label for="bird-frag" class="text-muted-foreground text-sm">Файл</label>
|
||||
<select
|
||||
id="bird-frag"
|
||||
class="border-input bg-background ring-offset-background focus-visible:ring-ring max-w-xl flex-1 rounded-md border px-2 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none"
|
||||
bind:value={birdPreviewPath}
|
||||
>
|
||||
{#each Object.keys(frags).sort() as path}
|
||||
<option value={path}>{path}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
Совет: откройте <code class="bg-muted rounded px-1">_bird_full_expanded.conf</code> — один текст с
|
||||
<code class="bg-muted rounded px-1">bird.conf</code> и содержимым всех include.
|
||||
</p>
|
||||
<ScrollArea class="max-h-[min(70vh,28rem)] rounded border">
|
||||
<pre class="p-3 font-mono text-xs whitespace-pre-wrap break-words">{frags[birdPreviewPath] ?? ''}</pre>
|
||||
</ScrollArea>
|
||||
{/if}
|
||||
</TabsContent>
|
||||
<TabsContent value="prefixes" class="mt-3">
|
||||
<p class="text-sm mb-2"><span class="font-medium">Префиксов:</span> {prefixesData.length}</p>
|
||||
<ScrollArea class="max-h-80 rounded border p-3">
|
||||
{#each prefixesData as pfx}
|
||||
<p class="font-mono text-xs">{pfx}</p>
|
||||
{:else}
|
||||
<p class="text-muted-foreground text-xs">Нет префиксов</p>
|
||||
{/each}
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
{/if}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user