Translate UI text to Russian for improved localization across various components and pages, including sidebar, pagination, and server details. Update labels, tooltips, and headers to enhance user experience for Russian-speaking users.
This commit is contained in:
@@ -4,12 +4,12 @@ const SEG_LABELS: Record<string, string> = {
|
||||
users: 'Пользователи',
|
||||
ips: 'Уникальные IP',
|
||||
incidents: 'Инциденты',
|
||||
live: 'Live',
|
||||
live: 'Поток',
|
||||
mihomo: 'Mihomo',
|
||||
servers: 'Серверы',
|
||||
runtime: 'Runtime',
|
||||
security: 'Security',
|
||||
upstreams: 'Upstreams',
|
||||
runtime: 'Состояние',
|
||||
security: 'Безопасность',
|
||||
upstreams: 'Апстримы и ДЦ',
|
||||
config: 'Конфигурация',
|
||||
update: 'Обновление'
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active('/incidents')} tooltipContent="Incidents">
|
||||
<Sidebar.MenuButton isActive={active('/incidents')} tooltipContent="Инциденты">
|
||||
{#snippet child({ props })}
|
||||
<a href="/incidents" {...props}>
|
||||
<SirenIcon />
|
||||
@@ -143,11 +143,11 @@
|
||||
<Sidebar.GroupLabel>Нода</Sidebar.GroupLabel>
|
||||
<Sidebar.Menu>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={activeExact(serverPath)} tooltipContent="Dashboard">
|
||||
<Sidebar.MenuButton isActive={activeExact(serverPath)} tooltipContent="Обзор">
|
||||
{#snippet child({ props })}
|
||||
<a href={serverPath} {...props}>
|
||||
<LayoutDashboardIcon />
|
||||
<span>Dashboard</span>
|
||||
<span>Обзор</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
@@ -163,61 +163,61 @@
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/runtime')} tooltipContent="Runtime">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/runtime')} tooltipContent="Состояние">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/runtime" {...props}>
|
||||
<ActivityIcon />
|
||||
<span>Runtime</span>
|
||||
<span>Состояние</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/users')} tooltipContent="Users">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/users')} tooltipContent="Пользователи">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/users" {...props}>
|
||||
<UsersIcon />
|
||||
<span>Users</span>
|
||||
<span>Пользователи</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/security')} tooltipContent="Security">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/security')} tooltipContent="Безопасность">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/security" {...props}>
|
||||
<ShieldIcon />
|
||||
<span>Security</span>
|
||||
<span>Безопасность</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/upstreams')} tooltipContent="Upstreams">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/upstreams')} tooltipContent="Апстримы и ДЦ">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/upstreams" {...props}>
|
||||
<NetworkIcon />
|
||||
<span>Upstreams & DCs</span>
|
||||
<span>Апстримы и ДЦ</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/config')} tooltipContent="Configuration">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/config')} tooltipContent="Конфигурация">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/config" {...props}>
|
||||
<SettingsIcon />
|
||||
<span>Configuration</span>
|
||||
<span>Конфигурация</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
</Sidebar.MenuItem>
|
||||
<Sidebar.MenuItem>
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/update')} tooltipContent="Update">
|
||||
<Sidebar.MenuButton isActive={active(serverPath + '/update')} tooltipContent="Обновление">
|
||||
{#snippet child({ props })}
|
||||
<a href="{serverPath}/update" {...props}>
|
||||
<RefreshCwIcon />
|
||||
<span>Update</span>
|
||||
<span>Обновление</span>
|
||||
</a>
|
||||
{/snippet}
|
||||
</Sidebar.MenuButton>
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
</script>
|
||||
|
||||
{#snippet Fallback()}
|
||||
<span>Next</span>
|
||||
<span>Следующая</span>
|
||||
<ChevronRightIcon class="size-4" />
|
||||
{/snippet}
|
||||
|
||||
<PaginationPrimitive.NextButton
|
||||
bind:ref
|
||||
aria-label="Go to next page"
|
||||
aria-label="Перейти на следующую страницу"
|
||||
class={cn(buttonVariants({ variant: "ghost" }), "pr-1.5!", className)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
</script>
|
||||
|
||||
<PaginationLink
|
||||
aria-label="Go to next page"
|
||||
aria-label="Перейти на следующую страницу"
|
||||
size="default"
|
||||
class={cn("pr-1.5!", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<span class="cn-pagination-next-text hidden sm:block">Next</span>
|
||||
<span class="cn-pagination-next-text hidden sm:block">Следующая</span>
|
||||
<ChevronRightIcon data-icon="inline-end" />
|
||||
</PaginationLink>
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
{#snippet Fallback()}
|
||||
<ChevronLeftIcon class="size-4" />
|
||||
<span>Previous</span>
|
||||
<span>Предыдущая</span>
|
||||
{/snippet}
|
||||
|
||||
<PaginationPrimitive.PrevButton
|
||||
bind:ref
|
||||
aria-label="Go to previous page"
|
||||
aria-label="Перейти на предыдущую страницу"
|
||||
class={cn(buttonVariants({ variant: "ghost" }), "pl-1.5!", className)}
|
||||
{...restProps}
|
||||
>
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
</script>
|
||||
|
||||
<PaginationLink
|
||||
aria-label="Go to previous page"
|
||||
aria-label="Перейти на предыдущую страницу"
|
||||
size="default"
|
||||
class={cn("pl-1.5!", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ChevronLeftIcon data-icon="inline-start" />
|
||||
<span class="cn-pagination-previous-text hidden sm:block">Previous</span>
|
||||
<span class="cn-pagination-previous-text hidden sm:block">Предыдущая</span>
|
||||
</PaginationLink>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
bind:ref
|
||||
bind:page
|
||||
role="navigation"
|
||||
aria-label="pagination"
|
||||
aria-label="Пагинация"
|
||||
data-slot="pagination"
|
||||
{count}
|
||||
{perPage}
|
||||
|
||||
@@ -51,8 +51,8 @@
|
||||
{side}
|
||||
>
|
||||
<Sheet.Header class="sr-only">
|
||||
<Sheet.Title>Sidebar</Sheet.Title>
|
||||
<Sheet.Description>Displays the mobile sidebar.</Sheet.Description>
|
||||
<Sheet.Title>Боковая панель</Sheet.Title>
|
||||
<Sheet.Description>Отображает мобильную боковую панель.</Sheet.Description>
|
||||
</Sheet.Header>
|
||||
<div class="flex h-full w-full flex-col">
|
||||
{@render children?.()}
|
||||
|
||||
@@ -2,12 +2,12 @@ const SEG_LABELS: Record<string, string> = {
|
||||
users: 'Пользователи',
|
||||
ips: 'Уникальные IP',
|
||||
incidents: 'Инциденты',
|
||||
live: 'Live',
|
||||
live: 'Поток',
|
||||
mihomo: 'Mihomo',
|
||||
servers: 'Серверы',
|
||||
runtime: 'Runtime',
|
||||
security: 'Security',
|
||||
upstreams: 'Upstreams и DC',
|
||||
runtime: 'Состояние',
|
||||
security: 'Безопасность',
|
||||
upstreams: 'Апстримы и ДЦ',
|
||||
config: 'Конфигурация',
|
||||
update: 'Обновление'
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import './layout.css';
|
||||
import { page } from '$app/state';
|
||||
import { page, navigating } from '$app/state';
|
||||
import { onMount } from 'svelte';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import favicon from '$lib/assets/favicon.svg';
|
||||
@@ -15,6 +15,7 @@
|
||||
let { children } = $props();
|
||||
|
||||
const docTitle = $derived(`${panelTitleFromPath(page.url.pathname)} · Telemt Panel`);
|
||||
const isNavigating = $derived(navigating !== null);
|
||||
|
||||
let serverAliases = $state<string[]>([]);
|
||||
|
||||
@@ -54,6 +55,11 @@
|
||||
<Sidebar.Provider>
|
||||
<AppSidebar {serverAliases} {serverPath} />
|
||||
<Sidebar.Inset>
|
||||
{#if isNavigating}
|
||||
<div class="h-0.5 w-full overflow-hidden bg-transparent">
|
||||
<div class="h-full w-1/3 animate-pulse rounded-full bg-primary"></div>
|
||||
</div>
|
||||
{/if}
|
||||
<header class="flex h-14 w-full shrink-0 items-center gap-2 border-b border-border px-4">
|
||||
<SiteHeader />
|
||||
</header>
|
||||
|
||||
@@ -572,7 +572,7 @@
|
||||
{/snippet}
|
||||
{summary.fleet_total_connections}
|
||||
</KpiStatCard>
|
||||
<KpiStatCard accent="danger" label="Bad connections" footer="сумма по нодам" valueClass="text-xl">
|
||||
<KpiStatCard accent="danger" label="Плохие соединения" footer="сумма по нодам" valueClass="text-xl">
|
||||
{#snippet icon()}
|
||||
<ShieldAlertIcon class="size-5" aria-hidden="true" />
|
||||
{/snippet}
|
||||
|
||||
@@ -124,6 +124,12 @@
|
||||
return 'outline';
|
||||
}
|
||||
|
||||
function severityLabel(sev: IncidentItem['severity']): string {
|
||||
if (sev === 'critical') return 'критично';
|
||||
if (sev === 'warning') return 'предупреждение';
|
||||
return 'инфо';
|
||||
}
|
||||
|
||||
function restartPolling() {
|
||||
if (pollTimer) {
|
||||
clearInterval(pollTimer);
|
||||
@@ -283,7 +289,7 @@
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Инциденты</h1>
|
||||
<p class="text-sm text-muted-foreground">Сводка и triage по всему флоту.</p>
|
||||
<p class="text-sm text-muted-foreground">Сводка и разбор инцидентов по всему флоту.</p>
|
||||
{#if generatedAt}
|
||||
<p class="mt-1 text-xs text-muted-foreground">Снимок: {new Date(generatedAt).toLocaleString()}</p>
|
||||
{/if}
|
||||
@@ -331,38 +337,38 @@
|
||||
{#snippet children()}
|
||||
<div class="mb-4 grid gap-4 sm:grid-cols-3">
|
||||
<KpiTrendCard
|
||||
label="Critical"
|
||||
label="Критичные"
|
||||
valueDisplay={String(data?.critical_total ?? 0)}
|
||||
prevValue={kpiPrev?.critical ?? null}
|
||||
currentValue={data != null ? (data.critical_total ?? 0) : null}
|
||||
invertTrend={true}
|
||||
insight="Открытые critical; меньше — лучше. К прошлому опросу."
|
||||
insight="Открытые критичные; меньше — лучше. К прошлому опросу."
|
||||
/>
|
||||
<KpiTrendCard
|
||||
label="Warning"
|
||||
label="Предупреждения"
|
||||
valueDisplay={String(data?.warning_total ?? 0)}
|
||||
prevValue={kpiPrev?.warning ?? null}
|
||||
currentValue={data != null ? (data.warning_total ?? 0) : null}
|
||||
invertTrend={true}
|
||||
insight="Открытые warning; меньше — лучше."
|
||||
insight="Открытые предупреждения; меньше — лучше."
|
||||
/>
|
||||
<KpiTrendCard
|
||||
label="Info"
|
||||
label="Инфо"
|
||||
valueDisplay={String(data?.info_total ?? 0)}
|
||||
prevValue={kpiPrev?.info ?? null}
|
||||
currentValue={data != null ? (data.info_total ?? 0) : null}
|
||||
insight="Открытые info. К прошлому опросу."
|
||||
insight="Открытые инфо. К прошлому опросу."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTableCard title="Инциденты" description="Список и triage">
|
||||
<DataTableCard title="Инциденты" description="Список и разбор">
|
||||
{#snippet toolbar()}
|
||||
<DataTableToolbar>
|
||||
<div class="relative max-w-md flex-1">
|
||||
<SearchIcon
|
||||
class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<Input class="h-8 ps-8" placeholder="Поиск по заголовку, summary, severity…" bind:value={incSearch} />
|
||||
<Input class="h-8 ps-8" placeholder="Поиск по заголовку, описанию, критичности…" bind:value={incSearch} />
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -384,7 +390,7 @@
|
||||
active={incSortKey === 'sev'}
|
||||
dir={incSortDir}
|
||||
onToggle={() => toggleIncSort('sev')}
|
||||
>Severity</SortableTh>
|
||||
>Критичность</SortableTh>
|
||||
<SortableTh
|
||||
active={incSortKey === 'title'}
|
||||
dir={incSortDir}
|
||||
@@ -406,8 +412,8 @@
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</Table.Head>
|
||||
<Table.Head>Runbook</Table.Head>
|
||||
<Table.Head>Triage</Table.Head>
|
||||
<Table.Head>Инструкции</Table.Head>
|
||||
<Table.Head>Разбор</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -427,7 +433,7 @@
|
||||
{#each filteredIncidents as item (item.id)}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Badge variant={severityVariant(item.severity)}>{item.severity}</Badge>
|
||||
<Badge variant={severityVariant(item.severity)}>{severityLabel(item.severity)}</Badge>
|
||||
</Table.Cell>
|
||||
<Table.Cell class="max-w-[360px]">
|
||||
<div class="font-medium">{item.title}</div>
|
||||
@@ -458,21 +464,21 @@
|
||||
variant={triage.ack ? 'default' : 'outline'}
|
||||
onclick={() => toggleAck(item.id)}
|
||||
>
|
||||
{triage.ack ? 'unack' : 'ack'}
|
||||
{triage.ack ? 'Снять подтверждение' : 'Подтвердить'}
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
variant={triage.resolved ? 'default' : 'outline'}
|
||||
onclick={() => toggleResolved(item.id)}
|
||||
>
|
||||
{triage.resolved ? 'unresolve' : 'resolve'}
|
||||
{triage.resolved ? 'Вернуть в работу' : 'Решить'}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<Input
|
||||
type="text"
|
||||
class="h-8 w-full text-xs"
|
||||
placeholder="owner"
|
||||
placeholder="Ответственный"
|
||||
value={triage.owner ?? ''}
|
||||
oninput={(e) =>
|
||||
patchTriage(item.id, {
|
||||
@@ -484,7 +490,7 @@
|
||||
<Input
|
||||
type="text"
|
||||
class="h-8 w-full text-xs"
|
||||
placeholder="note"
|
||||
placeholder="Примечание"
|
||||
value={triage.note ?? ''}
|
||||
oninput={(e) =>
|
||||
patchTriage(item.id, {
|
||||
|
||||
@@ -106,12 +106,24 @@
|
||||
return 'outline';
|
||||
}
|
||||
|
||||
function severityLabel(sev: IncidentSeverity): string {
|
||||
if (sev === 'critical') return 'критично';
|
||||
if (sev === 'warning') return 'предупреждение';
|
||||
return 'инфо';
|
||||
}
|
||||
|
||||
function statusVariant(st: LiveStatus | undefined): 'default' | 'secondary' | 'destructive' {
|
||||
if (st === 'critical') return 'destructive';
|
||||
if (st === 'degraded') return 'secondary';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
function statusLabel(st: LiveStatus | undefined): string {
|
||||
if (st === 'critical') return 'критично';
|
||||
if (st === 'degraded') return 'деградация';
|
||||
return 'норма';
|
||||
}
|
||||
|
||||
function parseAliasFilter(raw: string | null): string {
|
||||
const value = raw?.trim() ?? '';
|
||||
return !value || value.includes(',') ? 'all' : value;
|
||||
@@ -312,12 +324,12 @@
|
||||
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Live</h1>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Поток</h1>
|
||||
<p class="text-sm text-muted-foreground">SSE поток инцидентов и статуса флота.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Badge variant={connected ? 'default' : 'secondary'}>
|
||||
{connected ? 'connected' : 'disconnected'}
|
||||
{connected ? 'подключено' : 'отключено'}
|
||||
</Badge>
|
||||
<FleetLiveStaleBadge
|
||||
{isStale}
|
||||
@@ -329,7 +341,7 @@
|
||||
/>
|
||||
<Button variant="outline" size="sm" onclick={() => connectStream()}>
|
||||
<RefreshCwIcon class="mr-1 size-4" />
|
||||
Reconnect
|
||||
Переподключить
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -340,11 +352,11 @@
|
||||
|
||||
{#if err}
|
||||
<Alert class="mb-4">
|
||||
<AlertTitle>Live stream</AlertTitle>
|
||||
<AlertTitle>Поток событий</AlertTitle>
|
||||
<AlertDescription>
|
||||
{err}
|
||||
{#if reconnectInSec != null}
|
||||
Переподключение через {reconnectInSec}s.
|
||||
Переподключение через {reconnectInSec} с.
|
||||
{/if}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -355,19 +367,17 @@
|
||||
{#snippet icon()}
|
||||
<ActivityIcon class="size-5" aria-hidden="true" />
|
||||
{/snippet}
|
||||
<Badge variant={statusVariant(snapshot?.status)}>
|
||||
{snapshot?.status ?? 'healthy'}
|
||||
</Badge>
|
||||
<Badge variant={statusVariant(snapshot?.status)}>{statusLabel(snapshot?.status)}</Badge>
|
||||
</KpiStatCard>
|
||||
<KpiStatCard accent="warning" label="Partial" footer="неполный снимок">
|
||||
<KpiStatCard accent="warning" label="Частичность" footer="неполный снимок">
|
||||
{#snippet icon()}
|
||||
<LayersIcon class="size-5" aria-hidden="true" />
|
||||
{/snippet}
|
||||
{snapshot?.partial ? 'yes' : 'no'}
|
||||
{snapshot?.partial ? 'да' : 'нет'}
|
||||
</KpiStatCard>
|
||||
<KpiStatCard
|
||||
accent="info"
|
||||
label="Timestamp"
|
||||
label="Временная метка"
|
||||
footer="время snapshot"
|
||||
valueClass="text-base font-normal tabular-nums leading-tight"
|
||||
>
|
||||
@@ -379,7 +389,7 @@
|
||||
</div>
|
||||
|
||||
<DataTableCard
|
||||
title="Последние incidents из snapshot"
|
||||
title="Последние инциденты из снимка"
|
||||
description="Всего: {snapshot?.counts?.total ?? snapshot?.incidents?.length ?? 0}"
|
||||
>
|
||||
{#snippet toolbar()}
|
||||
@@ -388,7 +398,7 @@
|
||||
<SearchIcon
|
||||
class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<Input class="h-8 ps-8" placeholder="Поиск по severity, title, summary…" bind:value={liveIncSearch} />
|
||||
<Input class="h-8 ps-8" placeholder="Поиск по критичности, заголовку, описанию…" bind:value={liveIncSearch} />
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -410,13 +420,13 @@
|
||||
active={liveSortKey === 'sev'}
|
||||
dir={liveSortDir}
|
||||
onToggle={() => toggleLiveSort('sev')}
|
||||
>Severity</SortableTh>
|
||||
>Критичность</SortableTh>
|
||||
<SortableTh
|
||||
active={liveSortKey === 'title'}
|
||||
dir={liveSortDir}
|
||||
onToggle={() => toggleLiveSort('title')}
|
||||
>Title</SortableTh>
|
||||
<Table.Head>Summary</Table.Head>
|
||||
>Заголовок</SortableTh>
|
||||
<Table.Head>Описание</Table.Head>
|
||||
<Table.Head>
|
||||
<div class="flex items-center gap-1">
|
||||
Серверы
|
||||
@@ -452,7 +462,7 @@
|
||||
{#each filteredLiveIncidents as item (item.id)}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Badge variant={severityVariant(item.severity)}>{item.severity}</Badge>
|
||||
<Badge variant={severityVariant(item.severity)}>{severityLabel(item.severity)}</Badge>
|
||||
</Table.Cell>
|
||||
<Table.Cell class="font-medium">{item.title}</Table.Cell>
|
||||
<Table.Cell class="max-w-[380px] text-sm text-muted-foreground">{item.summary}</Table.Cell>
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
</KpiStatCard>
|
||||
<KpiStatCard
|
||||
accent="danger"
|
||||
label="Bad connections"
|
||||
label="Плохие соединения"
|
||||
valueClass="text-xl"
|
||||
>
|
||||
{#snippet icon()}
|
||||
@@ -146,7 +146,7 @@
|
||||
</KpiStatCard>
|
||||
<KpiStatCard
|
||||
accent="warning"
|
||||
label="Handshake timeouts"
|
||||
label="Таймауты рукопожатия"
|
||||
valueClass="text-xl"
|
||||
>
|
||||
{#snippet icon()}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</script>
|
||||
|
||||
<ServerPageHeader
|
||||
title="Configuration"
|
||||
title="Конфигурация"
|
||||
subtitle="Конфигурация этой ноды не отдается через Control API"
|
||||
showRefresh={false}
|
||||
/>
|
||||
|
||||
@@ -57,22 +57,22 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ServerPageHeader title="Runtime" {loading} onRefresh={load} />
|
||||
<ServerPageHeader title="Состояние" {loading} onRefresh={load} />
|
||||
|
||||
<ServerQueryState {loading} {err} isEmpty={false}>
|
||||
{#snippet children()}
|
||||
<div class="mb-4 flex flex-wrap gap-2">
|
||||
{#if gates}
|
||||
<Badge variant="secondary" class="px-3 py-1">accepting: {onOff(gates.accepting_new_connections)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">conditional cast: {onOff(gates.conditional_cast_enabled)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">me2dc fallback: {onOff(gates.me2dc_fallback_enabled)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">ME ready: {onOff(gates.me_runtime_ready)}</Badge>
|
||||
<Badge variant="outline" class="px-3 py-1">middle proxy: {onOff(gates.use_middle_proxy)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">Приём новых: {onOff(gates.accepting_new_connections)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">Условный cast: {onOff(gates.conditional_cast_enabled)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">Резерв me2dc: {onOff(gates.me2dc_fallback_enabled)}</Badge>
|
||||
<Badge variant="secondary" class="px-3 py-1">ME готов: {onOff(gates.me_runtime_ready)}</Badge>
|
||||
<Badge variant="outline" class="px-3 py-1">Промежуточный прокси: {onOff(gates.use_middle_proxy)}</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if dcs && dcs.middle_proxy_enabled && dcs.dcs}
|
||||
<KpiPanelCard class="mb-6" accent="info" title="Datacenter status" contentClass="p-0">
|
||||
<KpiPanelCard class="mb-6" accent="info" title="Статус дата-центров" contentClass="p-0">
|
||||
{#snippet icon()}
|
||||
<BuildingIcon class="size-5" aria-hidden="true" />
|
||||
{/snippet}
|
||||
@@ -81,8 +81,8 @@
|
||||
<Table.Row class="border-b border-border/80 hover:bg-transparent">
|
||||
<Table.Head>DC</Table.Head>
|
||||
<Table.Head class="text-right">RTT</Table.Head>
|
||||
<Table.Head class="text-right">Writers</Table.Head>
|
||||
<Table.Head class="text-right">Coverage</Table.Head>
|
||||
<Table.Head class="text-right">Писатели</Table.Head>
|
||||
<Table.Head class="text-right">Покрытие</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -103,7 +103,7 @@
|
||||
</KpiPanelCard>
|
||||
{:else if dcs?.reason}
|
||||
<Alert class="mb-4">
|
||||
<AlertDescription>DC status: {dcs.reason}</AlertDescription>
|
||||
<AlertDescription>Статус ДЦ: {dcs.reason}</AlertDescription>
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ServerPageHeader title="Security" {loading} onRefresh={load} />
|
||||
<ServerPageHeader title="Безопасность" {loading} onRefresh={load} />
|
||||
|
||||
<ServerQueryState {loading} {err} isEmpty={!data} emptyDescription="Endpoint вернул пустой ответ.">
|
||||
{#snippet children()}
|
||||
{#if data}
|
||||
<ServerJsonCard accent="danger" title="Security posture" description="POST /security/posture" payload={data}>
|
||||
<ServerJsonCard accent="danger" title="Профиль безопасности" description="POST /security/posture" payload={data}>
|
||||
{#snippet iconSnippet()}
|
||||
<ShieldIcon class="size-5" aria-hidden="true" />
|
||||
{/snippet}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ServerPageHeader title="Update" {loading} onRefresh={load} />
|
||||
<ServerPageHeader title="Обновление" {loading} onRefresh={load} />
|
||||
|
||||
<ServerQueryState {loading} {err} isEmpty={!sys} emptyDescription="Информация о бинарнике не получена.">
|
||||
{#snippet children()}
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ServerPageHeader title="Upstreams & DCs" {loading} onRefresh={load} />
|
||||
<ServerPageHeader title="Апстримы и ДЦ" {loading} onRefresh={load} />
|
||||
|
||||
<ServerQueryState {loading} {err} isEmpty={!data} emptyDescription="Статистика upstream-ов пока не доступна.">
|
||||
{#snippet children()}
|
||||
{#if data}
|
||||
<ServerJsonCard
|
||||
accent="info"
|
||||
title="Upstreams & DCs"
|
||||
title="Апстримы и ДЦ"
|
||||
description="stats/upstreams"
|
||||
payload={data}
|
||||
preClass="max-h-[70vh]"
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
</script>
|
||||
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-4">
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Users</h1>
|
||||
<h1 class="text-2xl font-semibold tracking-tight">Пользователи</h1>
|
||||
<div class="flex gap-2">
|
||||
<Button onclick={() => (createOpen = true)} disabled={readOnly}>
|
||||
<PlusIcon class="mr-1 size-4" />
|
||||
@@ -236,7 +236,7 @@
|
||||
<Table.Row class="border-b border-border/80 hover:bg-transparent">
|
||||
<Table.Head>Имя</Table.Head>
|
||||
<Table.Head>Ссылки</Table.Head>
|
||||
<Table.Head class="text-right">Conn</Table.Head>
|
||||
<Table.Head class="text-right">Соединения</Table.Head>
|
||||
<Table.Head class="text-right">IP</Table.Head>
|
||||
<Table.Head class="text-right">Трафик</Table.Head>
|
||||
<Table.Head class="w-[100px]"></Table.Head>
|
||||
@@ -248,9 +248,9 @@
|
||||
<Table.Cell class="font-medium">{u.username}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#each u.links?.tls ?? [] as link (link)}
|
||||
{#each u.links?.tls ?? [] as link, idx (`${link}:${idx}`)}
|
||||
<Button variant="outline" size="xs" class="h-7" onclick={() => copy(link)}>
|
||||
TLS <CopyIcon class="size-3" />
|
||||
TLS {idx + 1} <CopyIcon class="size-3" />
|
||||
</Button>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -269,6 +269,8 @@
|
||||
size="icon-sm"
|
||||
disabled={readOnly}
|
||||
onclick={() => openEdit(u)}
|
||||
aria-label="Редактировать пользователя"
|
||||
title="Редактировать пользователя"
|
||||
>
|
||||
<PencilIcon class="size-4" />
|
||||
</Button>
|
||||
@@ -277,6 +279,8 @@
|
||||
size="icon-sm"
|
||||
disabled={readOnly}
|
||||
onclick={() => (deleteUserRow = u)}
|
||||
aria-label="Удалить пользователя"
|
||||
title="Удалить пользователя"
|
||||
>
|
||||
<TrashIcon class="size-4 text-destructive" />
|
||||
</Button>
|
||||
@@ -325,15 +329,15 @@
|
||||
<Input id="eq" bind:value={editQuota} type="number" min="0" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="em">max_unique_ips</Label>
|
||||
<Label for="em">Макс. уникальных IP (max_unique_ips)</Label>
|
||||
<Input id="em" bind:value={editMaxIp} type="number" min="0" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="et">max_tcp_conns</Label>
|
||||
<Label for="et">Макс. TCP-соединений (max_tcp_conns)</Label>
|
||||
<Input id="et" bind:value={editMaxTcp} type="number" min="0" />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="ex">expiry (RFC3339)</Label>
|
||||
<Label for="ex">Срок действия (RFC3339)</Label>
|
||||
<Input id="ex" bind:value={editExp} class="font-mono text-sm" />
|
||||
</div>
|
||||
{#if formErr}
|
||||
|
||||
Reference in New Issue
Block a user