-
Timeout, мс (опционально)
+
Таймаут, мс (необязательно)
[
{
id: 'api',
- label: 'API health',
- value: healthOk ? 'OK' : '—',
+ label: 'Состояние API',
+ value: healthOk ? 'норма' : '—',
unit: '',
percent: healthOk ? 100 : 0,
icon:
,
diff --git a/apps/web/src/components/modules/module-cdn-source-dialog.tsx b/apps/web/src/components/modules/module-cdn-source-dialog.tsx
index 6774f84..e0fe796 100644
--- a/apps/web/src/components/modules/module-cdn-source-dialog.tsx
+++ b/apps/web/src/components/modules/module-cdn-source-dialog.tsx
@@ -14,8 +14,8 @@ import { normalizeCdnSourceKind } from '@/lib/modules/helpers'
import type { BgpCommunity, CdnPreviewResponse, CdnSource, CdnSourceCreate } from '@/types/api'
const CDN_KIND_ITEMS = [
- { value: 'plaintext', label: 'plaintext' },
- { value: 'json', label: 'json' },
+ { value: 'plaintext', label: 'Текст' },
+ { value: 'json', label: 'JSON' },
] as const
interface ModuleCdnSourceDialogProps {
diff --git a/apps/web/src/components/modules/module-edit-dialog.tsx b/apps/web/src/components/modules/module-edit-dialog.tsx
index f5ab3c5..a91268d 100644
--- a/apps/web/src/components/modules/module-edit-dialog.tsx
+++ b/apps/web/src/components/modules/module-edit-dialog.tsx
@@ -160,7 +160,7 @@ export function ModuleEditDialog({
Включён
- Выключенный модуль не участвует в refresh и apply.
+ Выключенный модуль не участвует в обновлении и применении.
(
- {row.original.source_kind}
+ {cdnSourceKindRu(row.original.source_kind)}
),
},
{
diff --git a/apps/web/src/components/network/network-discovered-peers-card.tsx b/apps/web/src/components/network/network-discovered-peers-card.tsx
index f299d04..569d311 100644
--- a/apps/web/src/components/network/network-discovered-peers-card.tsx
+++ b/apps/web/src/components/network/network-discovered-peers-card.tsx
@@ -35,7 +35,7 @@ import type { PeerDiscoveryRow, SpeakerRow } from '@/types/api'
function speakerLabel(s: SpeakerRow): string {
if (s.role === 'master') {
const host = s.agent_domain ?? s.endpoint
- return host ? `CP · ${host}` : 'CP (master)'
+ return host ? `Плоскость · ${host}` : 'Плоскость управления'
}
return s.agent_domain ?? s.endpoint ?? `${s.id.slice(0, 8)}…`
}
@@ -51,7 +51,7 @@ const filterFields: FilterFieldConfig[] = [
icon: ,
type: 'text',
className: 'w-48',
- placeholder: 'IP или Neighbor ID…',
+ placeholder: 'IP или ID соседа…',
},
]
@@ -94,7 +94,7 @@ export function NetworkDiscoveredPeersCard({
id: 'neighbor_id',
accessorFn: (row) => row.neighbor_id || row.neighbor,
header: ({ column }) => (
-
+
),
cell: ({ row }) => (
),
- meta: { headerTitle: 'Neighbor ID' },
+ meta: { headerTitle: 'ID соседа' },
},
{
accessorKey: 'remote_asn',
@@ -212,7 +212,7 @@ export function NetworkDiscoveredPeersCard({
title="Одобрить пира"
description={
approveTarget
- ? `Neighbor ID ${approveTarget.neighbor_id || '—'} · ${approveTarget.neighbor} AS${approveTarget.remote_asn ?? '?'}`
+ ? `ID соседа ${approveTarget.neighbor_id || '—'} · ${approveTarget.neighbor} AS${approveTarget.remote_asn ?? '?'}`
: undefined
}
className="sm:max-w-sm"
diff --git a/apps/web/src/components/network/network-kpi.tsx b/apps/web/src/components/network/network-kpi.tsx
index 75e493e..ea9e992 100644
--- a/apps/web/src/components/network/network-kpi.tsx
+++ b/apps/web/src/components/network/network-kpi.tsx
@@ -55,7 +55,7 @@ export function NetworkKpi({
}
size="sm"
>
- {loading ? '…' : 'онлайн'}
+ {loading ? '…' : 'в сети'}
),
to: '/network',
diff --git a/apps/web/src/components/network/network-peers-card.tsx b/apps/web/src/components/network/network-peers-card.tsx
index 60bf245..a7ca332 100644
--- a/apps/web/src/components/network/network-peers-card.tsx
+++ b/apps/web/src/components/network/network-peers-card.tsx
@@ -39,11 +39,11 @@ const PEER_TABS = [
const SESSION_STATE_OPTIONS = [
{ value: 'Established', label: 'Установлена' },
- { value: 'Idle', label: 'Idle' },
- { value: 'Active', label: 'Active' },
- { value: 'Connect', label: 'Connect' },
- { value: 'OpenSent', label: 'OpenSent' },
- { value: 'OpenConfirm', label: 'OpenConfirm' },
+ { value: 'Idle', label: 'Простой' },
+ { value: 'Active', label: 'Поиск' },
+ { value: 'Connect', label: 'Соединение' },
+ { value: 'OpenSent', label: 'Open отправлен' },
+ { value: 'OpenConfirm', label: 'Open подтверждён' },
]
function createDefaultPeerFilters(): Filter[] {
diff --git a/apps/web/src/components/network/network-speakers-card.tsx b/apps/web/src/components/network/network-speakers-card.tsx
index ac02e39..8b2ee07 100644
--- a/apps/web/src/components/network/network-speakers-card.tsx
+++ b/apps/web/src/components/network/network-speakers-card.tsx
@@ -31,14 +31,14 @@ function speakerDeleteLabel(s: SpeakerRow): string {
const SPEAKER_TABS = [
{ id: 'all', label: 'Все' },
- { id: 'online', label: 'Online' },
- { id: 'offline', label: 'Offline' },
+ { id: 'online', label: 'В сети' },
+ { id: 'offline', label: 'Не в сети' },
]
const ROLE_OPTIONS = [
- { value: 'primary', label: 'primary' },
- { value: 'secondary', label: 'secondary' },
- { value: 'speaker', label: 'speaker' },
+ { value: 'primary', label: 'Основной' },
+ { value: 'secondary', label: 'Резервный' },
+ { value: 'speaker', label: 'Спикер' },
]
function createDefaultSpeakerFilters(): Filter[] {
@@ -52,7 +52,7 @@ const speakerFilterFields: FilterFieldConfig[] = [
icon: ,
type: 'text',
className: 'w-52',
- placeholder: 'endpoint…',
+ placeholder: 'Адрес агента…',
},
{
key: 'role',
@@ -128,7 +128,7 @@ export function NetworkSpeakersCard({
<>
[] = [
@@ -20,7 +20,7 @@ export const speakerColumns: ColumnDef[] = [
{
accessorKey: 'role',
header: ({ column }) => ,
- cell: ({ row }) => {row.original.role} ,
+ cell: ({ row }) => {speakerRoleRu(row.original.role)} ,
meta: { headerTitle: 'Роль' },
},
{
diff --git a/apps/web/src/components/network/peer-form-dialog.tsx b/apps/web/src/components/network/peer-form-dialog.tsx
index 62ab2cc..d3f6573 100644
--- a/apps/web/src/components/network/peer-form-dialog.tsx
+++ b/apps/web/src/components/network/peer-form-dialog.tsx
@@ -22,7 +22,7 @@ interface PeerFormDialogProps {
function speakerLabel(s: SpeakerRow): string {
if (s.role === 'master') {
const host = s.agent_domain ?? s.endpoint
- return host ? `CP · ${host}` : 'CP (master)'
+ return host ? `Плоскость · ${host}` : 'Плоскость управления'
}
return s.agent_domain ?? s.endpoint ?? `${s.id.slice(0, 8)}…`
}
diff --git a/apps/web/src/components/network/speaker-form-dialog.tsx b/apps/web/src/components/network/speaker-form-dialog.tsx
index 19ad0ea..6b0a4f9 100644
--- a/apps/web/src/components/network/speaker-form-dialog.tsx
+++ b/apps/web/src/components/network/speaker-form-dialog.tsx
@@ -74,7 +74,7 @@ export function SpeakerFormDialog({ open, onOpenChange }: SpeakerFormDialogProps
const ep =
endpoint.trim() || (agentDomain.trim() ? `https://${agentDomain.trim()}` : '')
if (!ep) {
- toast.error('Укажите endpoint или agent domain')
+ toast.error('Укажите конечную точку или домен агента')
return
}
const body: BgpSpeakerCreate = {
@@ -95,7 +95,7 @@ export function SpeakerFormDialog({ open, onOpenChange }: SpeakerFormDialogProps
open={open}
onOpenChange={onOpenChange}
title="Новый спикер"
- description="BIRD-агент на ноде реплики или control plane"
+ description="BIRD-агент на ноде реплики или плоскости управления"
className="sm:max-w-md"
footer={
<>
@@ -122,7 +122,7 @@ export function SpeakerFormDialog({ open, onOpenChange }: SpeakerFormDialogProps
label="Роль"
items={[
{ value: 'replica', label: 'Реплика' },
- { value: 'master', label: 'Мастер (CP)' },
+ { value: 'master', label: 'Мастер (плоскость)' },
]}
value={role}
onValueChange={(v) => setRole(v ?? 'replica')}
@@ -146,7 +146,7 @@ export function SpeakerFormDialog({ open, onOpenChange }: SpeakerFormDialogProps
/>
-
BGP source IPv4
+
Исходный IPv4 BGP
}
title={`Откатиться к ревизии ${row.original.id.slice(0, 8)}…?`}
- description="Будет создана новая ревизия на основе выбранной. Требуется роль operator."
+ description="Будет создана новая ревизия на основе выбранной. Требуется роль оператора."
confirmLabel="Откатить"
destructive
onConfirm={() => rollbackMutation.mutate(row.original.id)}
diff --git a/apps/web/src/components/reui/data-grid/data-grid-column-filter.tsx b/apps/web/src/components/reui/data-grid/data-grid-column-filter.tsx
index 12d7d4f..61a312f 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-column-filter.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-column-filter.tsx
@@ -61,7 +61,7 @@ function DataGridColumnFilter
({
{selectedValues.size > 2 ? (
- {selectedValues.size} selected
+ {selectedValues.size} выбрано
) : (
options
@@ -94,7 +94,7 @@ function DataGridColumnFilter
({
{filteredOptions.length === 0 ? (
- No results found.
+ Ничего не найдено.
) : (
@@ -170,7 +170,7 @@ function DataGridColumnFilter({
}}
className="hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground rounded-md relative flex cursor-pointer items-center justify-center px-2 py-1.5 text-sm outline-hidden select-none"
>
- Clear filters
+ Сбросить фильтры
>
diff --git a/apps/web/src/components/reui/data-grid/data-grid-pagination.tsx b/apps/web/src/components/reui/data-grid/data-grid-pagination.tsx
index 16c2d29..e3eeb8e 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-pagination.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-pagination.tsx
@@ -39,11 +39,11 @@ function DataGridPagination(props: DataGridPaginationProps): React.JSX.Element {
sizes: [5, 10, 25, 50, 100],
sizesSkeleton: ,
moreLimit: 5,
- info: "{from} - {to} of {count}",
+ info: "{from}–{to} из {count}",
infoSkeleton: ,
- rowsPerPageLabel: "Rows per page",
- previousPageLabel: "Go to previous page",
- nextPageLabel: "Go to next page",
+ rowsPerPageLabel: "Строк на странице",
+ previousPageLabel: "Предыдущая страница",
+ nextPageLabel: "Следующая страница",
ellipsisText: "...",
}
diff --git a/apps/web/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx b/apps/web/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx
index b4ee534..12360c7 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx
@@ -73,7 +73,7 @@ function DataGridTableDndRowHandle({ className }: { className?: string }) {
"size-7 cursor-grab opacity-70 hover:bg-transparent hover:opacity-100 active:cursor-grabbing",
className
)}
- aria-label="Drag to reorder row"
+ aria-label="Перетащить строку"
disabled
>
@@ -89,7 +89,7 @@ function DataGridTableDndRowHandle({ className }: { className?: string }) {
"size-7 cursor-grab opacity-70 hover:bg-transparent hover:opacity-100 active:cursor-grabbing",
className
)}
- aria-label="Drag to reorder row"
+ aria-label="Перетащить строку"
{...context.attributes}
{...context.listeners}
>
diff --git a/apps/web/src/components/reui/data-grid/data-grid-table-dnd.tsx b/apps/web/src/components/reui/data-grid/data-grid-table-dnd.tsx
index d7f9076..7222aa8 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-table-dnd.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-table-dnd.tsx
@@ -105,7 +105,7 @@ function DataGridTableDndHeader({
className={`-ms-2 size-6 ${isDragging ? "cursor-grabbing" : "cursor-grab active:cursor-grabbing"}`}
{...attributes}
{...listeners}
- aria-label="Drag to reorder"
+ aria-label="Перетащить для изменения порядка"
>
diff --git a/apps/web/src/components/reui/data-grid/data-grid-table-virtual.tsx b/apps/web/src/components/reui/data-grid/data-grid-table-virtual.tsx
index 2874787..c9a4471 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-table-virtual.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-table-virtual.tsx
@@ -397,9 +397,9 @@ function DataGridTableVirtual({
const isVirtualizationEnabled = virtualizerOptions?.enabled !== false
const loadingMoreMessage =
- props.fetchingMoreMessage || props.loadingMessage || "Loading..."
+ props.fetchingMoreMessage || props.loadingMessage || "Загрузка…"
const allRowsLoadedMessage =
- props.allRowsLoadedMessage || "All records loaded"
+ props.allRowsLoadedMessage || "Все записи загружены"
const handleViewportRef = useCallback((node: HTMLDivElement | null) => {
setViewportElements({
diff --git a/apps/web/src/components/reui/data-grid/data-grid-table.tsx b/apps/web/src/components/reui/data-grid/data-grid-table.tsx
index 22339eb..5c2b250 100644
--- a/apps/web/src/components/reui/data-grid/data-grid-table.tsx
+++ b/apps/web/src/components/reui/data-grid/data-grid-table.tsx
@@ -1241,7 +1241,7 @@ function DataGridTableEmpty() {
colSpan={Math.max(visibleColumnCount, 1)}
className="text-muted-foreground py-6 text-center text-sm"
>
- {props.emptyMessage || "No data available"}
+ {props.emptyMessage || "Нет данных"}
)
@@ -1254,7 +1254,7 @@ function DataGridTableLoader() {
- {props.loadingMessage || "Loading..."}
+ {props.loadingMessage || "Загрузка…"}
)
@@ -1266,7 +1266,7 @@ function DataGridTableRowPin({ row }: { row: Row }) {
return (
{
if (isPinned) {
row.pin(false)
@@ -1322,7 +1322,7 @@ function DataGridTableRowSelect({ row }: { row: Row }) {
row.toggleSelected(!!value)}
- aria-label="Select row"
+ aria-label="Выбрать строку"
className="align-[inherit]"
/>
>
@@ -1341,7 +1341,7 @@ function DataGridTableRowSelectAll() {
indeterminate={isSomeSelected && !isAllSelected}
disabled={isLoading || recordCount === 0}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
- aria-label="Select all"
+ aria-label="Выбрать все"
className="align-[inherit]"
/>
)
@@ -1408,7 +1408,7 @@ function DataGridTableBodyRows({ table }: { table: Table }) {
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
>
- {props.loadingMessage || "Loading..."}
+ {props.loadingMessage || "Загрузка…"}
diff --git a/apps/web/src/components/reui/filters.tsx b/apps/web/src/components/reui/filters.tsx
index 8f6a943..00c0e85 100644
--- a/apps/web/src/components/reui/filters.tsx
+++ b/apps/web/src/components/reui/filters.tsx
@@ -123,75 +123,70 @@ export interface FilterI18nConfig {
// Default English i18n configuration
export const DEFAULT_I18N: FilterI18nConfig = {
- // UI Labels
- addFilter: "Filter",
- searchFields: "Filter...",
- noFieldsFound: "No filters found.",
- noResultsFound: "No results found.",
- select: "Select...",
- true: "True",
- false: "False",
- min: "Min",
- max: "Max",
- to: "to",
- typeAndPressEnter: "Type and press Enter to add tag",
- selected: "selected",
- selectedCount: "selected",
+ addFilter: "Фильтр",
+ searchFields: "Фильтр…",
+ noFieldsFound: "Поля не найдены.",
+ noResultsFound: "Ничего не найдено.",
+ select: "Выбрать…",
+ true: "Да",
+ false: "Нет",
+ min: "Мин.",
+ max: "Макс.",
+ to: "—",
+ typeAndPressEnter: "Введите и нажмите Enter",
+ selected: "выбрано",
+ selectedCount: "выбрано",
percent: "%",
defaultCurrency: "$",
defaultColor: "#000000",
- addFilterTitle: "Add filter",
+ addFilterTitle: "Добавить фильтр",
- // Operators
operators: {
- is: "is",
- isNot: "is not",
- isAnyOf: "is any of",
- isNotAnyOf: "is not any of",
- includesAll: "includes all",
- excludesAll: "excludes all",
- before: "before",
- after: "after",
- between: "between",
- notBetween: "not between",
- contains: "contains",
- notContains: "does not contain",
- startsWith: "starts with",
- endsWith: "ends with",
- isExactly: "is exactly",
- equals: "equals",
- notEquals: "not equals",
- greaterThan: "greater than",
- lessThan: "less than",
- overlaps: "overlaps",
- includes: "includes",
- excludes: "excludes",
- includesAllOf: "includes all of",
- includesAnyOf: "includes any of",
- empty: "is empty",
- notEmpty: "is not empty",
+ is: "равно",
+ isNot: "не равно",
+ isAnyOf: "любое из",
+ isNotAnyOf: "кроме",
+ includesAll: "включает все",
+ excludesAll: "исключает все",
+ before: "до",
+ after: "после",
+ between: "между",
+ notBetween: "вне диапазона",
+ contains: "содержит",
+ notContains: "не содержит",
+ startsWith: "начинается с",
+ endsWith: "заканчивается на",
+ isExactly: "точно",
+ equals: "равно",
+ notEquals: "не равно",
+ greaterThan: "больше",
+ lessThan: "меньше",
+ overlaps: "пересекается",
+ includes: "включает",
+ excludes: "исключает",
+ includesAllOf: "включает все",
+ includesAnyOf: "включает любое",
+ empty: "пусто",
+ notEmpty: "не пусто",
},
- // Placeholders
placeholders: {
- enterField: (fieldType: string) => `Enter ${fieldType}...`,
- selectField: "Select...",
- searchField: (fieldName: string) => `Search ${fieldName.toLowerCase()}...`,
- enterKey: "Enter key...",
- enterValue: "Enter value...",
+ enterField: (fieldType: string) => `Введите ${fieldType}…`,
+ selectField: "Выбрать…",
+ searchField: (fieldName: string) => `Поиск ${fieldName.toLowerCase()}…`,
+ enterKey: "Введите ключ…",
+ enterValue: "Введите значение…",
},
- // Helper functions
helpers: {
formatOperator: (operator: string) => operator.replace(/_/g, " "),
},
- // Validation
validation: {
- invalidEmail: "Invalid email format",
- invalidUrl: "Invalid URL format",
- invalidTel: "Invalid phone format",
- invalid: "Invalid input format",
+ invalidEmail: "Некорректный email",
+ invalidUrl: "Некорректный URL",
+ invalidTel: "Некорректный телефон",
+ invalid: "Некорректное значение",
},
}
diff --git a/apps/web/src/components/schedule/schedule-agenda-panel.tsx b/apps/web/src/components/schedule/schedule-agenda-panel.tsx
index 35d2b2e..0d32aec 100644
--- a/apps/web/src/components/schedule/schedule-agenda-panel.tsx
+++ b/apps/web/src/components/schedule/schedule-agenda-panel.tsx
@@ -107,7 +107,7 @@ export function ScheduleAgendaPanel({
return (
diff --git a/apps/web/src/components/schedule/schedule-modules-grid.tsx b/apps/web/src/components/schedule/schedule-modules-grid.tsx
index dcfc504..30253d1 100644
--- a/apps/web/src/components/schedule/schedule-modules-grid.tsx
+++ b/apps/web/src/components/schedule/schedule-modules-grid.tsx
@@ -43,7 +43,7 @@ export function ScheduleModulesGrid({
cell: ({ row }) => (
{row.original.cron_expr ??
- (row.original.refresh_interval_sec ? `${row.original.refresh_interval_sec}s` : '—')}
+ (row.original.refresh_interval_sec ? `${row.original.refresh_interval_sec} с` : '—')}
),
meta: { headerTitle: 'Расписание' },
diff --git a/apps/web/src/components/settings/appearance-settings-tab.tsx b/apps/web/src/components/settings/appearance-settings-tab.tsx
index 99fc40f..4ebe7f1 100644
--- a/apps/web/src/components/settings/appearance-settings-tab.tsx
+++ b/apps/web/src/components/settings/appearance-settings-tab.tsx
@@ -90,16 +90,16 @@ export function AppearanceSettingsTab() {
= {
@@ -40,12 +40,12 @@ const BIRD_LABELS: Record = {
bird_local_ipv4: 'Локальный IPv4',
bird_local_ipv6: 'Локальный IPv6',
bird_local_asn: 'Локальный ASN',
- bird_bgp_source_ipv4: 'BGP source IPv4',
- bird_bgp_source_ipv6: 'BGP source IPv6',
+ bird_bgp_source_ipv4: 'Исходный IPv4 BGP',
+ bird_bgp_source_ipv6: 'Исходный IPv6 BGP',
peer_discovery_enabled: 'Автообнаружение пиров',
- peer_discovery_ranges_v4: 'Discovery CIDR IPv4',
- peer_discovery_ranges_v6: 'Discovery CIDR IPv6',
- peer_discovery_require_external: 'Только external ASN',
+ peer_discovery_ranges_v4: 'CIDR автообнаружения IPv4',
+ peer_discovery_ranges_v6: 'CIDR автообнаружения IPv6',
+ peer_discovery_require_external: 'Только внешние ASN',
}
const BIRD_BOOL_ITEMS = [
@@ -55,10 +55,10 @@ const BIRD_BOOL_ITEMS = [
const BIRD_HINTS: Partial> = {
peer_discovery_enabled:
- 'Dynamic neighbor range в BIRD (карантин import/export none). Требует CIDR.',
+ 'Динамический диапазон соседей в BIRD (карантин import/export none). Нужны CIDR.',
peer_discovery_ranges_v4: 'Через запятую или пробел, напр. 198.51.100.0/24 203.0.113.0/24',
peer_discovery_ranges_v6: 'Опционально, напр. 2001:db8::/32',
- peer_discovery_require_external: 'neighbor range … external (любой чужой ASN)',
+ peer_discovery_require_external: 'Диапазон соседей с внешним (любым чужим) ASN',
}
export function BirdSettingsTab() {
@@ -103,8 +103,8 @@ export function BirdSettingsTab() {
return (
@@ -184,11 +184,11 @@ export function BirdSettingsTab() {
>
{() => (
@@ -84,7 +84,7 @@ export function ConnectionSettingsTab({ tokenRequired }: { tokenRequired: boolea
title="Токен для запросов"
description={
<>
- Ключ для заголовка Authorization. Управление ключами tenant — в разделе{' '}
+ Ключ для заголовка Authorization. Управление ключами арендатора — в разделе{' '}
Права доступа
@@ -93,7 +93,7 @@ export function ConnectionSettingsTab({ tokenRequired }: { tokenRequired: boolea
}
titleAddon={
- localStorage
+ браузер
}
labelFor="settings-token"
diff --git a/apps/web/src/components/settings/sections-settings-tab.tsx b/apps/web/src/components/settings/sections-settings-tab.tsx
index 2604ab0..99ed55e 100644
--- a/apps/web/src/components/settings/sections-settings-tab.tsx
+++ b/apps/web/src/components/settings/sections-settings-tab.tsx
@@ -28,25 +28,25 @@ const ADMIN_SECTIONS = [
title: 'Параметры арендатора',
description: 'BIRD, ревизии, файловые логи и дополнительные ключи /v1/settings.',
icon: ,
- badge: { label: 'operator', variant: 'warning-light' as const },
+ badge: { label: 'оператор', variant: 'warning-light' as const },
},
{
id: 'access',
to: '/access' as const,
search: undefined,
title: 'Права доступа',
- description: 'API-ключи tenant, роли и управление доступом.',
+ description: 'API-ключи арендатора, роли и управление доступом.',
icon: ,
- badge: { label: 'operator', variant: 'warning-light' as const },
+ badge: { label: 'оператор', variant: 'warning-light' as const },
},
{
id: 'monitoring',
to: '/monitoring' as const,
search: undefined,
title: 'Мониторинг',
- description: 'Метрики, состояние jobs и observability control plane.',
+ description: 'Метрики, состояние задач и наблюдаемость плоскости управления.',
icon: ,
- badge: { label: 'viewer+', variant: 'info-light' as const },
+ badge: { label: 'просмотр+', variant: 'info-light' as const },
},
] as const
@@ -54,8 +54,8 @@ export function SectionsSettingsTab() {
return (
{ADMIN_SECTIONS.map((section, index) => (
diff --git a/apps/web/src/components/settings/session-settings-tab.tsx b/apps/web/src/components/settings/session-settings-tab.tsx
index 762f269..9b41e0c 100644
--- a/apps/web/src/components/settings/session-settings-tab.tsx
+++ b/apps/web/src/components/settings/session-settings-tab.tsx
@@ -36,7 +36,7 @@ export function SessionSettingsTab() {
return (
-
+
- Tenant:{' '}
+ Арендатор:{' '}
{session.tenant_id}
@@ -84,7 +84,7 @@ export function SessionSettingsTab() {
>
@@ -101,7 +101,7 @@ export function SessionSettingsTab() {
>
{sessionQ.data ? (
- Ключ с ролью {sessionQ.data.role} в tenant{' '}
+ Ключ с ролью {ROLE_LABELS[sessionQ.data.role] ?? sessionQ.data.role} у арендатора{' '}
{sessionQ.data.tenant_id}.
) : (
diff --git a/apps/web/src/lib/access/api-key-labels.ts b/apps/web/src/lib/access/api-key-labels.ts
index c51ed28..af8e050 100644
--- a/apps/web/src/lib/access/api-key-labels.ts
+++ b/apps/web/src/lib/access/api-key-labels.ts
@@ -4,7 +4,7 @@ import { apiKeyRoleRu } from '@/lib/ui-labels'
export const API_KEY_ROLE_ITEMS: ReadonlyArray<{ value: ApiKeyRole; label: string }> = [
{ value: 'viewer', label: `${apiKeyRoleRu('viewer')} — только чтение` },
- { value: 'editor', label: `${apiKeyRoleRu('editor')} — CRUD без применения` },
+ { value: 'editor', label: `${apiKeyRoleRu('editor')} — изменение данных без применения` },
{ value: 'operator', label: `${apiKeyRoleRu('operator')} — полный доступ` },
{ value: 'node', label: `${apiKeyRoleRu('node')} — только API ноды` },
]
diff --git a/apps/web/src/lib/metrics/readiness-breakdown.test.ts b/apps/web/src/lib/metrics/readiness-breakdown.test.ts
index a585f56..1920df8 100644
--- a/apps/web/src/lib/metrics/readiness-breakdown.test.ts
+++ b/apps/web/src/lib/metrics/readiness-breakdown.test.ts
@@ -38,7 +38,7 @@ describe('isReadyCheckOk', () => {
describe('readyCheckStatusLabel', () => {
it('labels memory and failures', () => {
- expect(readyCheckStatusLabel('memory', true)).toBe('Memory')
+ expect(readyCheckStatusLabel('memory', true)).toBe('В памяти')
expect(readyCheckStatusLabel('ok', true)).toBe('В норме')
expect(readyCheckStatusLabel('unavailable', false)).toBe('Недоступно')
})
diff --git a/apps/web/src/lib/metrics/readiness-breakdown.ts b/apps/web/src/lib/metrics/readiness-breakdown.ts
index 583da54..9a4365c 100644
--- a/apps/web/src/lib/metrics/readiness-breakdown.ts
+++ b/apps/web/src/lib/metrics/readiness-breakdown.ts
@@ -54,7 +54,7 @@ export function readyCheckStatusLabel(value: ReadyCheckValue, ok: boolean): stri
}
if (typeof value === 'string') {
const n = value.trim().toLowerCase()
- if (n === 'memory') return 'Memory'
+ if (n === 'memory') return 'В памяти'
if (n === 'ok' || n === 'ready' || n === 'healthy' || n === 'true') return 'В норме'
if (n) return value
}
diff --git a/apps/web/src/lib/ui-labels.ts b/apps/web/src/lib/ui-labels.ts
index 014e83d..9d96f0e 100644
--- a/apps/web/src/lib/ui-labels.ts
+++ b/apps/web/src/lib/ui-labels.ts
@@ -61,18 +61,45 @@ const JOB_STATUS_RU: Record = {
paused: 'Приостановлен',
disabled: 'Выключен',
archived: 'В архиве',
- block: 'block',
- accept: 'accept',
+ block: 'Блокировать',
+ accept: 'Принимать',
}
export function jobStatusRu(status: string): string {
return JOB_STATUS_RU[status.toLowerCase()] ?? status
}
+const BGP_SESSION_STATE_RU: Record = {
+ Idle: 'Простой',
+ Connect: 'Соединение',
+ Active: 'Поиск',
+ OpenSent: 'Open отправлен',
+ OpenConfirm: 'Open подтверждён',
+ Established: 'Установлена',
+}
+
export function bgpSessionStateRu(state: string | null | undefined): string {
if (!state) return '—'
- if (state === 'Established') return 'Установлена'
- return state
+ return BGP_SESSION_STATE_RU[state] ?? state
+}
+
+export function speakerRoleRu(role: string | null | undefined): string {
+ switch (role) {
+ case 'master':
+ return 'Основной'
+ case 'primary':
+ return 'Основной'
+ case 'secondary':
+ return 'Резервный'
+ case 'speaker':
+ return 'Спикер'
+ default:
+ return role?.trim() || '—'
+ }
+}
+
+export function cdnSourceKindRu(kind: string): string {
+ return kind === 'json' ? 'JSON' : 'Текст'
}
export function speakerOnlineLabel(agentOk: boolean | undefined): string {
diff --git a/apps/web/src/queries/directories.ts b/apps/web/src/queries/directories.ts
index 7547bf8..5f12635 100644
--- a/apps/web/src/queries/directories.ts
+++ b/apps/web/src/queries/directories.ts
@@ -41,11 +41,11 @@ export function useCreateCommunityMutation() {
mutationFn: (body: BgpCommunityCreate) =>
apiMutate('/v1/communities', 'POST', body, { idempotent: false }),
onSuccess: () => {
- toast.success('Сообщество создано')
+ toast.success('Community создано')
void qc.invalidateQueries({ queryKey: directoriesKeys.communities() })
},
onError: (e) =>
- toast.error(e instanceof Error ? e.message : 'Не удалось создать сообщество'),
+ toast.error(e instanceof Error ? e.message : 'Не удалось создать community'),
})
}
@@ -55,11 +55,11 @@ export function useUpdateCommunityMutation() {
mutationFn: ({ id, body }: { id: string; body: BgpCommunityPatch }) =>
apiMutate(`/v1/communities/${id}`, 'PATCH', body, { idempotent: false }),
onSuccess: () => {
- toast.success('Сообщество обновлено')
+ toast.success('Community обновлено')
void qc.invalidateQueries({ queryKey: directoriesKeys.communities() })
},
onError: (e) =>
- toast.error(e instanceof Error ? e.message : 'Не удалось обновить сообщество'),
+ toast.error(e instanceof Error ? e.message : 'Не удалось обновить community'),
})
}
diff --git a/apps/web/src/routes/_auth/access.tsx b/apps/web/src/routes/_auth/access.tsx
index cd9f5d6..0e90f67 100644
--- a/apps/web/src/routes/_auth/access.tsx
+++ b/apps/web/src/routes/_auth/access.tsx
@@ -52,7 +52,7 @@ function AccessComponent() {
icon: ,
footer: (
- в tenant
+ в арендаторе
),
},
@@ -89,16 +89,16 @@ function AccessComponent() {
}
const sessionKindLabel =
- session?.kind === 'jwt' ? 'Portal JWT' : session?.kind === 'apikey' ? 'API-ключ' : null
+ session?.kind === 'jwt' ? 'JWT портала' : session?.kind === 'apikey' ? 'API-ключ' : null
const sessionAccessLabel = (() => {
if (!session) return null
if (session.kind === 'jwt' || session.is_admin || (session.permissions?.length ?? 0) > 0) {
- if (session.is_admin) return 'admin (portal)'
+ if (session.is_admin) return 'Администратор (портал)'
if (sessionCanManageApiKeys(session)) return 'bgp:access:admin'
return session.permissions?.length
? session.permissions.slice(0, 3).join(', ')
- : 'без access:admin'
+ : 'без права access:admin'
}
return session.role || '—'
})()
@@ -107,7 +107,7 @@ function AccessComponent() {
@@ -121,7 +121,7 @@ function AccessComponent() {
{session ? (
-
@@ -141,7 +141,7 @@ function AccessComponent() {
- Tenant:{' '}
+ Арендатор:{' '}
{session.tenant_id}
Доступ: {sessionAccessLabel}
@@ -188,8 +188,8 @@ function AccessComponent() {
) : session ? (
- Управление API-ключами доступно роли operator (API-ключ) или portal JWT
- с is_admin / правом bgp:access:admin.
+ Управление API-ключами доступно роли оператор (API-ключ) или JWT портала
+ с правом администратора / bgp:access:admin.
Текущий доступ: {sessionAccessLabel} .
diff --git a/apps/web/src/routes/_auth/directories.tsx b/apps/web/src/routes/_auth/directories.tsx
index 7299b80..5597935 100644
--- a/apps/web/src/routes/_auth/directories.tsx
+++ b/apps/web/src/routes/_auth/directories.tsx
@@ -45,7 +45,7 @@ function DirectoriesComponent() {
const items: KpiStatItem[] = [
{
- label: 'Сообщества BGP',
+ label: 'BGP community',
value: communities.length,
icon: ,
footer: (
@@ -70,7 +70,7 @@ function DirectoriesComponent() {
icon: ,
footer: (
- все модули tenant
+ все модули арендатора
),
},
@@ -114,7 +114,7 @@ function DirectoriesComponent() {
@@ -152,7 +152,7 @@ function DirectoriesComponent() {
isError={communitiesQ.isError}
error={communitiesQ.error}
empty={communities.length === 0}
- emptyTitle="Нет сообществ"
+ emptyTitle="Нет community"
emptyAction={addCommunityButton}
skeleton={ }
onRetry={() => communitiesQ.refetch()}
diff --git a/apps/web/src/routes/_auth/monitoring.tsx b/apps/web/src/routes/_auth/monitoring.tsx
index d108409..3259193 100644
--- a/apps/web/src/routes/_auth/monitoring.tsx
+++ b/apps/web/src/routes/_auth/monitoring.tsx
@@ -127,7 +127,7 @@ function MonitoringComponent() {
}
- description="GET /v1/bird/status"
+ description="Статус BIRD на хосте API"
className="h-full"
contentClassName="px-5 py-4"
>
@@ -169,7 +169,7 @@ function MonitoringComponent() {
j.status === 'running' || j.status === 'queued').length,
label: 'Активных',
@@ -230,11 +230,10 @@ function MonitoringComponent() {
Готовность не «Готов». Сначала{' '}
- postgres, затем store{' '}
- и jobs в проверках.
+ PostgreSQL, затем хранилище и очередь задач в проверках.
- Низкий ratio BGP. Проверьте{' '}
+ Низкая доля установленных BGP-сессий. Проверьте{' '}
/v1/bird/status, затем состояние пиров в Сети.
@@ -250,7 +249,7 @@ function MonitoringComponent() {
@@ -258,7 +257,7 @@ function MonitoringComponent() {