feat: enhance community label handling and improve UI localization across operations pages. Introduce community label mapping for better readability in job logs and update various components to reflect Russian translations, enhancing user experience and consistency in the interface.
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 43s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m8s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m9s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 17s
CI / docker-go-prime (push) Successful in 23s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m5s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 2m29s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m31s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m27s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m27s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m15s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m28s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m30s
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 43s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m8s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m9s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 17s
CI / docker-go-prime (push) Successful in 23s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m5s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 2m29s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m31s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m27s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m27s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m15s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m28s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m30s
This commit is contained in:
@@ -70,7 +70,7 @@
|
||||
{#if !collapsed}
|
||||
<div class="min-w-0 flex-1">
|
||||
<a href={resolve('/')} class="text-sidebar-foreground block truncate font-semibold tracking-tight">EvoBGP</a>
|
||||
<p class="text-sidebar-foreground/50 truncate text-xs">Control plane</p>
|
||||
<p class="text-sidebar-foreground/50 truncate text-xs">Панель управления</p>
|
||||
</div>
|
||||
{/if}
|
||||
<DropdownMenu>
|
||||
@@ -133,7 +133,8 @@
|
||||
href={resolve(item.href)}
|
||||
class={cn(
|
||||
buttonVariants({ variant: active ? 'secondary' : 'ghost', size: 'icon-sm' }),
|
||||
'w-full no-underline flex items-center justify-center'
|
||||
'w-full no-underline flex items-center justify-center',
|
||||
active && 'ring-sidebar-primary/50 bg-sidebar-accent/90 text-sidebar-accent-foreground ring-1'
|
||||
)}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
@@ -147,7 +148,9 @@
|
||||
href={resolve(item.href)}
|
||||
class={cn(
|
||||
buttonVariants({ variant: active ? 'secondary' : 'ghost', size: 'sm' }),
|
||||
'w-full justify-start gap-2 no-underline'
|
||||
'w-full justify-start gap-2 no-underline',
|
||||
active &&
|
||||
'border-l-sidebar-primary bg-sidebar-accent/80 text-sidebar-accent-foreground border-l-2 shadow-sm'
|
||||
)}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
@@ -171,7 +174,8 @@
|
||||
href={resolve(item.href)}
|
||||
class={cn(
|
||||
buttonVariants({ variant: active ? 'secondary' : 'ghost', size: 'icon-sm' }),
|
||||
'w-full no-underline flex items-center justify-center'
|
||||
'w-full no-underline flex items-center justify-center',
|
||||
active && 'ring-sidebar-primary/50 bg-sidebar-accent/90 text-sidebar-accent-foreground ring-1'
|
||||
)}
|
||||
>
|
||||
<Icon class="size-4" />
|
||||
@@ -184,7 +188,9 @@
|
||||
href={resolve(item.href)}
|
||||
class={cn(
|
||||
buttonVariants({ variant: active ? 'secondary' : 'ghost', size: 'sm' }),
|
||||
'w-full justify-start gap-2 no-underline'
|
||||
'w-full justify-start gap-2 no-underline',
|
||||
active &&
|
||||
'border-l-sidebar-primary bg-sidebar-accent/80 text-sidebar-accent-foreground border-l-2 shadow-sm'
|
||||
)}
|
||||
>
|
||||
<Icon class="size-4" />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Filter from '@lucide/svelte/icons/filter';
|
||||
import Search from '@lucide/svelte/icons/search';
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import { jobKindFilterRu, jobStatusRu } from '$lib/ui-labels.js';
|
||||
|
||||
type Props = {
|
||||
searchQ: string;
|
||||
@@ -42,19 +43,19 @@
|
||||
|
||||
const statusOptions: { value: string; label: string }[] = [
|
||||
{ value: '', label: 'Все статусы' },
|
||||
{ value: 'queued', label: 'queued' },
|
||||
{ value: 'running', label: 'running' },
|
||||
{ value: 'succeeded', label: 'succeeded' },
|
||||
{ value: 'failed', label: 'failed' },
|
||||
{ value: 'cancelled', label: 'cancelled' }
|
||||
{ value: 'queued', label: jobStatusRu('queued') },
|
||||
{ value: 'running', label: jobStatusRu('running') },
|
||||
{ value: 'succeeded', label: jobStatusRu('succeeded') },
|
||||
{ value: 'failed', label: jobStatusRu('failed') },
|
||||
{ value: 'cancelled', label: jobStatusRu('cancelled') }
|
||||
];
|
||||
|
||||
const kindOptions: { value: string; label: string }[] = [
|
||||
{ value: '', label: 'Все типы' },
|
||||
{ value: 'module_refresh', label: 'module_refresh' },
|
||||
{ value: 'deploy_apply', label: 'deploy_apply' },
|
||||
{ value: 'revision_rollback', label: 'revision_rollback' },
|
||||
{ value: 'bird_reload', label: 'bird_reload' }
|
||||
{ value: 'module_refresh', label: jobKindFilterRu('module_refresh') },
|
||||
{ value: 'deploy_apply', label: jobKindFilterRu('deploy_apply') },
|
||||
{ value: 'revision_rollback', label: jobKindFilterRu('revision_rollback') },
|
||||
{ value: 'bird_reload', label: jobKindFilterRu('bird_reload') }
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -109,7 +110,7 @@
|
||||
|
||||
<div class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="job-filter-status" class="text-xs">Статус (API)</Label>
|
||||
<Label for="job-filter-status" class="text-xs">Статус</Label>
|
||||
<select
|
||||
id="job-filter-status"
|
||||
class="border-border bg-background h-9 w-full rounded-md border px-2 text-sm"
|
||||
@@ -123,7 +124,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label for="job-filter-kind" class="text-xs">Тип задачи (API)</Label>
|
||||
<Label for="job-filter-kind" class="text-xs">Тип задачи</Label>
|
||||
<select
|
||||
id="job-filter-kind"
|
||||
class="border-border bg-background h-9 w-full rounded-md border px-2 text-sm"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import Link2 from '@lucide/svelte/icons/link-2';
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { jobKindSubtitle, jobKindTitle } from '$lib/operations/job-kind-label.js';
|
||||
import { jobStatusRu, logKindRu, moduleTypeRu } from '$lib/ui-labels.js';
|
||||
import JobReportTableBlock from './job-report-table-block.svelte';
|
||||
import { asnReportColumns, reportRowColumns } from './job-report-columns.js';
|
||||
import type { RowData } from '@tanstack/table-core';
|
||||
@@ -84,7 +85,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<CardTitle class="text-base">Задачи</CardTitle>
|
||||
<CardDescription class="flex flex-wrap items-center gap-x-2 gap-y-1">
|
||||
<span>Фоновые задачи (ingest, apply, refresh)</span>
|
||||
<span>Фоновые задачи (ingest, применение, обновление)</span>
|
||||
{#if jobsFetchedTotal !== undefined}
|
||||
<span class="text-muted-foreground font-normal tabular-nums">
|
||||
· Показано {jobs.length} из {jobsFetchedTotal}
|
||||
@@ -168,7 +169,7 @@
|
||||
<CircleDot class="size-3.5 shrink-0" aria-hidden="true" />
|
||||
Статус
|
||||
</p>
|
||||
<Badge class="mt-1" variant={jobStatusVariant(job.status)}>{job.status}</Badge>
|
||||
<Badge class="mt-1" variant={jobStatusVariant(job.status)}>{jobStatusRu(job.status)}</Badge>
|
||||
</div>
|
||||
<div
|
||||
class="rounded-md border border-chart-2/25 bg-chart-2/5 px-2.5 py-2 dark:bg-chart-2/10"
|
||||
@@ -221,7 +222,7 @@
|
||||
|
||||
{#if detailedJob.error}
|
||||
<div class="rounded-md border border-destructive/30 bg-destructive/5 p-3">
|
||||
<p class="mb-1 text-xs text-muted-foreground">error</p>
|
||||
<p class="mb-1 text-xs text-muted-foreground">Ошибка</p>
|
||||
<p class="text-sm break-words text-destructive">{detailedJob.error}</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -249,7 +250,7 @@
|
||||
<div class="min-w-0 rounded-md border bg-muted/60 px-2.5 py-2">
|
||||
<span
|
||||
class="text-[11px] tracking-wide text-muted-foreground uppercase"
|
||||
>source</span
|
||||
>Источник</span
|
||||
>
|
||||
<p
|
||||
class="mt-1 rounded bg-background px-1.5 py-0.5 font-mono text-xs break-all"
|
||||
@@ -260,29 +261,30 @@
|
||||
<div class="min-w-0 rounded-md border bg-muted/60 px-2.5 py-2">
|
||||
<span
|
||||
class="text-[11px] tracking-wide text-muted-foreground uppercase"
|
||||
>kind</span
|
||||
>Тип</span
|
||||
>
|
||||
<p
|
||||
class="mt-1 rounded bg-background px-1.5 py-0.5 font-mono text-xs break-all"
|
||||
>
|
||||
{entry.kind}
|
||||
{logKindRu(entry.kind)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="min-w-0 rounded-md border bg-muted/60 px-2.5 py-2">
|
||||
<span
|
||||
class="text-[11px] tracking-wide text-muted-foreground uppercase"
|
||||
>community</span
|
||||
>Сообщество BGP</span
|
||||
>
|
||||
<p
|
||||
class="mt-1 rounded bg-background px-1.5 py-0.5 font-mono text-xs break-all"
|
||||
class="mt-1 rounded bg-background px-1.5 py-0.5 text-xs break-words"
|
||||
title={entry.community !== 'none' ? entry.community : undefined}
|
||||
>
|
||||
{entry.community}
|
||||
{entry.community_label?.trim() || entry.community}
|
||||
</p>
|
||||
</div>
|
||||
<div class="min-w-0 rounded-md border bg-muted/60 px-2.5 py-2">
|
||||
<span
|
||||
class="text-[11px] tracking-wide text-muted-foreground uppercase"
|
||||
>prefixes</span
|
||||
>Префиксы</span
|
||||
>
|
||||
<p class="mt-1 rounded bg-background px-1.5 py-0.5 font-mono text-xs">
|
||||
{entry.prefix_count}
|
||||
@@ -292,7 +294,7 @@
|
||||
{#if entry.sample && entry.sample.length > 0}
|
||||
<div class="space-y-1.5">
|
||||
<p class="text-[11px] tracking-wide text-muted-foreground uppercase">
|
||||
sample
|
||||
Примеры
|
||||
</p>
|
||||
<div class="rounded-md border bg-muted/35 p-2.5">
|
||||
<div class="space-y-1.5">
|
||||
@@ -326,7 +328,7 @@
|
||||
<p class="text-sm font-medium">Операции по модулю</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{#if jobReport.module}
|
||||
<Badge variant="outline">{jobReport.module.type}</Badge>
|
||||
<Badge variant="outline">{moduleTypeRu(jobReport.module.type)}</Badge>
|
||||
<Badge variant="secondary">{jobReport.module.name}</Badge>
|
||||
{/if}
|
||||
{#if jobReport.revisionId}
|
||||
@@ -377,7 +379,7 @@
|
||||
class="text-chart-4 flex items-center gap-1 text-[11px] font-medium uppercase"
|
||||
>
|
||||
<Link2 class="size-3.5 shrink-0" aria-hidden="true" />
|
||||
CDN / IP Range
|
||||
CDN / IP-диапазоны
|
||||
</p>
|
||||
<p class="text-sm font-semibold">
|
||||
{jobReport.cdn.length}/{jobReport.ipRanges.length}
|
||||
@@ -389,7 +391,7 @@
|
||||
<p class="text-xs text-muted-foreground">Результат агрегации по типам</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{#each jobReport.aggregationByKind as row (`${job.job_id}-agg-${row.kind}`)}
|
||||
<Badge variant="outline">{row.kind}: {row.prefixCount}</Badge>
|
||||
<Badge variant="outline">{logKindRu(row.kind)}: {row.prefixCount}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
@@ -425,7 +427,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 space-y-1.5 rounded-lg border p-3">
|
||||
<p class="text-sm font-medium">IP range: итог по статическим диапазонам</p>
|
||||
<p class="text-sm font-medium">IP-диапазоны: итог по статическим диапазонам</p>
|
||||
<JobReportTableBlock
|
||||
rows={jobReport.ipRanges as RowData[]}
|
||||
columns={reportCols}
|
||||
@@ -437,7 +439,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="space-y-1">
|
||||
<p class="text-xs text-muted-foreground">Meta (raw JSON)</p>
|
||||
<p class="text-xs text-muted-foreground">Meta (JSON)</p>
|
||||
<div class="rounded-md border bg-muted/30 p-3">
|
||||
<pre
|
||||
class="font-mono text-xs [overflow-wrap:anywhere] whitespace-pre-wrap">{JSON.stringify(
|
||||
|
||||
@@ -51,13 +51,13 @@
|
||||
<Play class="text-chart-1 size-5" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<p class="font-semibold">Apply all speakers</p>
|
||||
<p class="text-muted-foreground max-w-[42ch] text-sm">Применить текущую конфигурацию на всех спикерах</p>
|
||||
<p class="font-semibold">Применить ко всем спикерам</p>
|
||||
<p class="text-muted-foreground max-w-[42ch] text-sm">Применить текущую конфигурацию на всех BIRD-спикерах</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button class="w-full shrink-0 self-start sm:w-auto sm:self-auto" onclick={onApply} disabled={applying}>
|
||||
<Play class="size-4" aria-hidden="true" />
|
||||
Apply
|
||||
Применить
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -76,7 +76,7 @@
|
||||
<RotateCcw class="text-chart-4 size-5" />
|
||||
</div>
|
||||
<div class="min-w-0 flex-1 space-y-1">
|
||||
<p class="font-semibold">BIRD Reload</p>
|
||||
<p class="font-semibold">Перезагрузка BIRD</p>
|
||||
<p class="text-muted-foreground max-w-[42ch] text-sm">Перезагрузить конфигурацию BIRD на всех спикерах</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
disabled={reloading}
|
||||
>
|
||||
<RotateCcw class="size-4" aria-hidden="true" />
|
||||
Reload
|
||||
Перезагрузить
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -127,7 +127,7 @@
|
||||
<span class="font-medium">{birdStatus.bgp_established}</span>
|
||||
<span class="text-muted-foreground">/</span>
|
||||
<span class="font-medium">{birdStatus.bgp_sessions_total}</span>
|
||||
<span class="text-muted-foreground"> Established / всего</span>
|
||||
<span class="text-muted-foreground"> установлено / всего</span>
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
@@ -4,6 +4,8 @@ export type JobLogEntry = {
|
||||
kind: string;
|
||||
source: string;
|
||||
community: string;
|
||||
/** Человекочитаемое имя из справочника (title или BGP community). */
|
||||
community_label?: string;
|
||||
prefix_count: number;
|
||||
sample?: string[];
|
||||
message: string;
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/** Русские подписи для enum из API (задачи, модули, логи refresh). */
|
||||
|
||||
export function jobStatusRu(status: string): string {
|
||||
switch (status) {
|
||||
case 'queued':
|
||||
return 'В очереди';
|
||||
case 'running':
|
||||
return 'Выполняется';
|
||||
case 'succeeded':
|
||||
return 'Успешно';
|
||||
case 'failed':
|
||||
return 'Ошибка';
|
||||
case 'cancelled':
|
||||
return 'Отменена';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/** Подпись типа задачи для фильтров (значения API те же). */
|
||||
export function jobKindFilterRu(kind: string): string {
|
||||
switch (kind) {
|
||||
case 'module_refresh':
|
||||
return 'Обновление модуля';
|
||||
case 'deploy_apply':
|
||||
return 'Применение конфигурации';
|
||||
case 'revision_rollback':
|
||||
return 'Откат ревизии';
|
||||
case 'bird_reload':
|
||||
return 'Перезагрузка BIRD';
|
||||
default:
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleTypeRu(type: string): string {
|
||||
switch (type) {
|
||||
case 'AS_PREFIXES':
|
||||
return 'AS (номера)';
|
||||
case 'CDN_CIDRS':
|
||||
return 'CDN CIDR';
|
||||
case 'DOMAINS':
|
||||
return 'Домены';
|
||||
case 'IP_RANGES':
|
||||
return 'IP-диапазоны';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
/** Тип строки в логе агрегации префиксов (поле kind). */
|
||||
export function logKindRu(kind: string): string {
|
||||
switch (kind) {
|
||||
case 'asn':
|
||||
return 'ASN';
|
||||
case 'domain':
|
||||
return 'Домен';
|
||||
case 'cdn':
|
||||
return 'CDN';
|
||||
case 'ip_range':
|
||||
return 'Статические диапазоны';
|
||||
case 'unknown':
|
||||
return 'Неизвестно';
|
||||
case 'source':
|
||||
return 'Источник';
|
||||
default:
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user