+
{@render children?.()}
diff --git a/web/src/lib/ui/core/popover/popover-trigger.svelte b/web/src/lib/ui/core/popover/popover-trigger.svelte
index 586323c..f21b1b3 100644
--- a/web/src/lib/ui/core/popover/popover-trigger.svelte
+++ b/web/src/lib/ui/core/popover/popover-trigger.svelte
@@ -1,6 +1,6 @@
diff --git a/web/src/lib/ui/core/scroll-area/index.ts b/web/src/lib/ui/core/scroll-area/index.ts
index e86a25b..d546806 100644
--- a/web/src/lib/ui/core/scroll-area/index.ts
+++ b/web/src/lib/ui/core/scroll-area/index.ts
@@ -1,10 +1,10 @@
-import Scrollbar from "./scroll-area-scrollbar.svelte";
-import Root from "./scroll-area.svelte";
+import Scrollbar from './scroll-area-scrollbar.svelte';
+import Root from './scroll-area.svelte';
export {
Root,
Scrollbar,
//,
Root as ScrollArea,
- Scrollbar as ScrollAreaScrollbar,
+ Scrollbar as ScrollAreaScrollbar
};
diff --git a/web/src/lib/ui/core/scroll-area/scroll-area-scrollbar.svelte b/web/src/lib/ui/core/scroll-area/scroll-area-scrollbar.svelte
index b9518f3..b77de20 100644
--- a/web/src/lib/ui/core/scroll-area/scroll-area-scrollbar.svelte
+++ b/web/src/lib/ui/core/scroll-area/scroll-area-scrollbar.svelte
@@ -1,11 +1,11 @@
diff --git a/web/src/lib/ui/core/select/select-scroll-down-button.svelte b/web/src/lib/ui/core/select/select-scroll-down-button.svelte
index 94f41cd..cba5a02 100644
--- a/web/src/lib/ui/core/select/select-scroll-down-button.svelte
+++ b/web/src/lib/ui/core/select/select-scroll-down-button.svelte
@@ -1,6 +1,6 @@
@@ -19,11 +19,11 @@
data-slot="select-trigger"
data-size={size}
class={cn(
- "border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-lg border bg-transparent py-2 pr-2 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...restProps}
>
{@render children?.()}
-
+
diff --git a/web/src/lib/ui/core/select/select.svelte b/web/src/lib/ui/core/select/select.svelte
index 05eb663..b9bf9ba 100644
--- a/web/src/lib/ui/core/select/select.svelte
+++ b/web/src/lib/ui/core/select/select.svelte
@@ -1,5 +1,5 @@
@@ -14,9 +14,9 @@
bind:ref
data-slot={dataSlot}
class={cn(
- "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px",
+ 'shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px',
// this is different in shadcn/ui but self-stretch breaks things for us
- "data-[orientation=vertical]:h-full",
+ 'data-[orientation=vertical]:h-full',
className
)}
{...restProps}
diff --git a/web/src/lib/ui/core/sheet/index.ts b/web/src/lib/ui/core/sheet/index.ts
index 28d7da1..781bc48 100644
--- a/web/src/lib/ui/core/sheet/index.ts
+++ b/web/src/lib/ui/core/sheet/index.ts
@@ -1,13 +1,13 @@
-import Root from "./sheet.svelte";
-import Portal from "./sheet-portal.svelte";
-import Trigger from "./sheet-trigger.svelte";
-import Close from "./sheet-close.svelte";
-import Overlay from "./sheet-overlay.svelte";
-import Content from "./sheet-content.svelte";
-import Header from "./sheet-header.svelte";
-import Footer from "./sheet-footer.svelte";
-import Title from "./sheet-title.svelte";
-import Description from "./sheet-description.svelte";
+import Root from './sheet.svelte';
+import Portal from './sheet-portal.svelte';
+import Trigger from './sheet-trigger.svelte';
+import Close from './sheet-close.svelte';
+import Overlay from './sheet-overlay.svelte';
+import Content from './sheet-content.svelte';
+import Header from './sheet-header.svelte';
+import Footer from './sheet-footer.svelte';
+import Title from './sheet-title.svelte';
+import Description from './sheet-description.svelte';
export {
Root,
@@ -30,5 +30,5 @@ export {
Header as SheetHeader,
Footer as SheetFooter,
Title as SheetTitle,
- Description as SheetDescription,
+ Description as SheetDescription
};
diff --git a/web/src/lib/ui/core/sheet/sheet-close.svelte b/web/src/lib/ui/core/sheet/sheet-close.svelte
index ae382c1..b0180c0 100644
--- a/web/src/lib/ui/core/sheet/sheet-close.svelte
+++ b/web/src/lib/ui/core/sheet/sheet-close.svelte
@@ -1,5 +1,5 @@
diff --git a/web/src/lib/ui/core/sheet/sheet-content.svelte b/web/src/lib/ui/core/sheet/sheet-content.svelte
index d738333..296603b 100644
--- a/web/src/lib/ui/core/sheet/sheet-content.svelte
+++ b/web/src/lib/ui/core/sheet/sheet-content.svelte
@@ -1,21 +1,21 @@
diff --git a/web/src/lib/ui/core/sheet/sheet-title.svelte b/web/src/lib/ui/core/sheet/sheet-title.svelte
index 10c1717..b140d19 100644
--- a/web/src/lib/ui/core/sheet/sheet-title.svelte
+++ b/web/src/lib/ui/core/sheet/sheet-title.svelte
@@ -1,6 +1,6 @@
diff --git a/web/src/lib/ui/core/sheet/sheet.svelte b/web/src/lib/ui/core/sheet/sheet.svelte
index 5bf9783..d65b76b 100644
--- a/web/src/lib/ui/core/sheet/sheet.svelte
+++ b/web/src/lib/ui/core/sheet/sheet.svelte
@@ -1,5 +1,5 @@
diff --git a/web/src/lib/ui/core/skeleton/index.ts b/web/src/lib/ui/core/skeleton/index.ts
index 186db21..3120ce1 100644
--- a/web/src/lib/ui/core/skeleton/index.ts
+++ b/web/src/lib/ui/core/skeleton/index.ts
@@ -1,7 +1,7 @@
-import Root from "./skeleton.svelte";
+import Root from './skeleton.svelte';
export {
Root,
//
- Root as Skeleton,
+ Root as Skeleton
};
diff --git a/web/src/lib/ui/core/skeleton/skeleton.svelte b/web/src/lib/ui/core/skeleton/skeleton.svelte
index 1a940ee..e2b514b 100644
--- a/web/src/lib/ui/core/skeleton/skeleton.svelte
+++ b/web/src/lib/ui/core/skeleton/skeleton.svelte
@@ -1,6 +1,6 @@
-
+
{@render children?.()}
diff --git a/web/src/lib/ui/core/table/table-caption.svelte b/web/src/lib/ui/core/table/table-caption.svelte
index 4696cff..7ad3aee 100644
--- a/web/src/lib/ui/core/table/table-caption.svelte
+++ b/web/src/lib/ui/core/table/table-caption.svelte
@@ -1,6 +1,6 @@
-
+
{@render children?.()}
diff --git a/web/src/lib/ui/core/table/table-footer.svelte b/web/src/lib/ui/core/table/table-footer.svelte
index b9b14eb..565b244 100644
--- a/web/src/lib/ui/core/table/table-footer.svelte
+++ b/web/src/lib/ui/core/table/table-footer.svelte
@@ -1,6 +1,6 @@
-
+
{@render children?.()}
diff --git a/web/src/lib/ui/core/table/table-header.svelte b/web/src/lib/ui/core/table/table-header.svelte
index f47d259..615b5d8 100644
--- a/web/src/lib/ui/core/table/table-header.svelte
+++ b/web/src/lib/ui/core/table/table-header.svelte
@@ -1,6 +1,6 @@
-
+
{@render children?.()}
diff --git a/web/src/lib/ui/core/table/table.svelte b/web/src/lib/ui/core/table/table.svelte
index d95a02e..38b0176 100644
--- a/web/src/lib/ui/core/table/table.svelte
+++ b/web/src/lib/ui/core/table/table.svelte
@@ -1,6 +1,6 @@
-
+
diff --git a/web/src/lib/ui/core/tabs/index.ts b/web/src/lib/ui/core/tabs/index.ts
index 31267e5..2ea3623 100644
--- a/web/src/lib/ui/core/tabs/index.ts
+++ b/web/src/lib/ui/core/tabs/index.ts
@@ -1,7 +1,7 @@
-import Root from "./tabs.svelte";
-import Content from "./tabs-content.svelte";
-import List, { tabsListVariants, type TabsListVariant } from "./tabs-list.svelte";
-import Trigger from "./tabs-trigger.svelte";
+import Root from './tabs.svelte';
+import Content from './tabs-content.svelte';
+import List, { tabsListVariants, type TabsListVariant } from './tabs-list.svelte';
+import Trigger from './tabs-trigger.svelte';
export {
Root,
@@ -14,5 +14,5 @@ export {
Root as Tabs,
Content as TabsContent,
List as TabsList,
- Trigger as TabsTrigger,
+ Trigger as TabsTrigger
};
diff --git a/web/src/lib/ui/core/tabs/tabs-content.svelte b/web/src/lib/ui/core/tabs/tabs-content.svelte
index 394ab3e..2f30880 100644
--- a/web/src/lib/ui/core/tabs/tabs-content.svelte
+++ b/web/src/lib/ui/core/tabs/tabs-content.svelte
@@ -1,6 +1,6 @@
@@ -15,7 +15,7 @@
bind:this={ref}
data-slot={dataSlot}
class={cn(
- "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 disabled:bg-input/50 dark:disabled:bg-input/80 rounded-lg border bg-transparent px-2.5 py-2 text-base transition-colors focus-visible:ring-3 aria-invalid:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
+ 'flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
className
)}
bind:value
diff --git a/web/src/lib/ui/core/tooltip/index.ts b/web/src/lib/ui/core/tooltip/index.ts
index 1718604..809e219 100644
--- a/web/src/lib/ui/core/tooltip/index.ts
+++ b/web/src/lib/ui/core/tooltip/index.ts
@@ -1,8 +1,8 @@
-import Root from "./tooltip.svelte";
-import Trigger from "./tooltip-trigger.svelte";
-import Content from "./tooltip-content.svelte";
-import Provider from "./tooltip-provider.svelte";
-import Portal from "./tooltip-portal.svelte";
+import Root from './tooltip.svelte';
+import Trigger from './tooltip-trigger.svelte';
+import Content from './tooltip-content.svelte';
+import Provider from './tooltip-provider.svelte';
+import Portal from './tooltip-portal.svelte';
export {
Root,
@@ -15,5 +15,5 @@ export {
Content as TooltipContent,
Trigger as TooltipTrigger,
Provider as TooltipProvider,
- Portal as TooltipPortal,
+ Portal as TooltipPortal
};
diff --git a/web/src/lib/ui/core/tooltip/tooltip-content.svelte b/web/src/lib/ui/core/tooltip/tooltip-content.svelte
index 0cf0694..c4c4324 100644
--- a/web/src/lib/ui/core/tooltip/tooltip-content.svelte
+++ b/web/src/lib/ui/core/tooltip/tooltip-content.svelte
@@ -1,15 +1,15 @@
diff --git a/web/src/lib/ui/core/tooltip/tooltip-provider.svelte b/web/src/lib/ui/core/tooltip/tooltip-provider.svelte
index 6dba9a6..fde5bb2 100644
--- a/web/src/lib/ui/core/tooltip/tooltip-provider.svelte
+++ b/web/src/lib/ui/core/tooltip/tooltip-provider.svelte
@@ -1,5 +1,5 @@
diff --git a/web/src/lib/ui/core/tooltip/tooltip-trigger.svelte b/web/src/lib/ui/core/tooltip/tooltip-trigger.svelte
index 1acdaa4..5631d1b 100644
--- a/web/src/lib/ui/core/tooltip/tooltip-trigger.svelte
+++ b/web/src/lib/ui/core/tooltip/tooltip-trigger.svelte
@@ -1,5 +1,5 @@
diff --git a/web/src/lib/ui/core/tooltip/tooltip.svelte b/web/src/lib/ui/core/tooltip/tooltip.svelte
index 44bee9f..3a21dab 100644
--- a/web/src/lib/ui/core/tooltip/tooltip.svelte
+++ b/web/src/lib/ui/core/tooltip/tooltip.svelte
@@ -1,6 +1,6 @@
diff --git a/web/src/lib/ui/patterns/confirm/confirm-dialog.svelte b/web/src/lib/ui/patterns/confirm/confirm-dialog.svelte
index 2570230..9db74bc 100644
--- a/web/src/lib/ui/patterns/confirm/confirm-dialog.svelte
+++ b/web/src/lib/ui/patterns/confirm/confirm-dialog.svelte
@@ -33,7 +33,9 @@
{state.cancelLabel ?? 'Отмена'}
diff --git a/web/src/lib/ui/patterns/data-table/app-data-table.svelte b/web/src/lib/ui/patterns/data-table/app-data-table.svelte
index 1c000e3..fc1268b 100644
--- a/web/src/lib/ui/patterns/data-table/app-data-table.svelte
+++ b/web/src/lib/ui/patterns/data-table/app-data-table.svelte
@@ -103,7 +103,7 @@
{/if}
{:else}
-
+
{/if}
{:else}
diff --git a/web/src/lib/ui/patterns/empty-state/empty-state.svelte b/web/src/lib/ui/patterns/empty-state/empty-state.svelte
index a88ca8d..bf74c56 100644
--- a/web/src/lib/ui/patterns/empty-state/empty-state.svelte
+++ b/web/src/lib/ui/patterns/empty-state/empty-state.svelte
@@ -13,15 +13,17 @@
let { title = 'Нет данных', description, icon: Icon, action, class: className }: Props = $props();
-
+
{#if Icon}
-
+
{/if}
{title}
{#if description}
-
{description}
+
{description}
{/if}
{#if action}
{@render action()}
diff --git a/web/src/lib/ui/patterns/form/app-input.svelte b/web/src/lib/ui/patterns/form/app-input.svelte
index c6f6c47..0201915 100644
--- a/web/src/lib/ui/patterns/form/app-input.svelte
+++ b/web/src/lib/ui/patterns/form/app-input.svelte
@@ -8,12 +8,7 @@
error?: boolean;
};
- let {
- class: className,
- value = $bindable(''),
- error = false,
- ...rest
- }: Props = $props();
+ let { class: className, value = $bindable(''), error = false, ...rest }: Props = $props();
-
+
diff --git a/web/src/lib/ui/patterns/form/form-field.svelte b/web/src/lib/ui/patterns/form/form-field.svelte
index db591c1..8aa7104 100644
--- a/web/src/lib/ui/patterns/form/form-field.svelte
+++ b/web/src/lib/ui/patterns/form/form-field.svelte
@@ -13,7 +13,15 @@
children: Snippet;
};
- let { label, id, description, error, required = false, class: className, children }: Props = $props();
+ let {
+ label,
+ id,
+ description,
+ error,
+ required = false,
+ class: className,
+ children
+ }: Props = $props();
@@ -23,8 +31,8 @@
{@render children()}
{#if error}
-
{error}
+
{error}
{:else if description}
-
{description}
+
{description}
{/if}
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index dbffdea..0c80c80 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -1,13 +1,25 @@
{#if loadingMod}
-
Загрузка…
+
Загрузка…
{:else if mod}
{@const m = mod}
@@ -888,20 +958,26 @@
- Приоритет
+
+ Приоритет
+
{mod.priority}
- Интервал
-
+
+ Интервал
+
+
{moduleIntervalLabel(mod)}
- DoH
+
+ DoH
+
{#if mod.type === 'DOMAINS'}
- {dohPolicyLabel(mod.doh_resolver_policy)}
-
+
{dohPolicyLabel(mod.doh_resolver_policy)}
+
{#if moduleDohProfileIds(mod).length}
{moduleDohProfileIds(mod).map(dohProfileLabel).join('; ')}
{:else}
@@ -909,16 +985,22 @@
{/if}
{:else}
- —
+ —
{/if}
- Community по умолч.
- {communityLabel(mod.default_community_id)}
+
+ Community по умолч.
+
+ {communityLabel(mod.default_community_id)}
- Последнее обновление
- {formatDateTime(mod.last_refreshed_at)}
+
+ Последнее обновление
+
+
+ {formatDateTime(mod.last_refreshed_at)}
+
@@ -926,14 +1008,17 @@
Операционный отчёт модуля
- Читаемая сводка по данным модуля: источники, объёмы и ожидаемый результат для refresh/агрегации.
+ Читаемая сводка по данным модуля: источники, объёмы и ожидаемый результат для
+ refresh/агрегации.
{#if mod.type === 'DOMAINS'}
Домены и ожидаемые IP
-
После refresh домены резолвятся в IP и конвертируются в префиксы.
+
+ После refresh домены резолвятся в IP и конвертируются в префиксы.
+
{#each domainEntries.slice(0, 8) as entry (entry.id)}
{entry.fqdn}
@@ -945,16 +1030,24 @@
{:else if mod.type === 'AS_PREFIXES'}
ASN и число полученных префиксов
-
Счётчик префиксов обновляется после успешного refresh.
+
+ Счётчик префиксов обновляется после успешного refresh.
+
-
Всего ASN: {asEntries.length}
-
Сумма префиксов: {asPrefixTotal}
+
+ Всего ASN: {asEntries.length}
+
+
+ Сумма префиксов: {asPrefixTotal}
+
{:else if mod.type === 'CDN_CIDRS'}
CDN ссылки и импортируемые префиксы
-
Каждый URL поставляет список CIDR для агрегации.
+
+ Каждый URL поставляет список CIDR для агрегации.
+
{#each cdnSources.slice(0, 6) as src (src.id)}
{src.url}
@@ -966,7 +1059,9 @@
{:else if mod.type === 'IP_RANGES'}
IP ranges для агрегации
-
Статические CIDR, которые попадают в итоговую ревизию.
+
+ Статические CIDR, которые попадают в итоговую ревизию.
+
{#each ipEntries.slice(0, 8) as entry (entry.id)}
{entry.prefix}
@@ -978,9 +1073,9 @@
{/if}
Результат операции
-
- Подробный результат по конкретному запуску refresh смотрите в `Операции -> Задачи -> module_refresh`:
- там отображаются источники, количество префиксов и итог агрегации.
+
+ Подробный результат по конкретному запуску refresh смотрите в `Операции -> Задачи ->
+ module_refresh`: там отображаются источники, количество префиксов и итог агрегации.
@@ -993,10 +1088,13 @@
AS-записи
- Номер AS и community; имя, число префиксов и дата обновляются при успешном обновлении модуля (RIPEstat)
+ Номер AS и community; имя, число префиксов и дата обновляются при успешном обновлении
+ модуля (RIPEstat)
-
+
toggleAllAs((event.currentTarget as HTMLInputElement).checked)}
+ onchange={(event) =>
+ toggleAllAs((event.currentTarget as HTMLInputElement).checked)}
/>
ASN
@@ -1061,30 +1160,43 @@
{entry.asn}
{entry.asn_name?.trim() ? entry.asn_name : '—'}
- {entry.prefix_count != null && entry.prefix_count !== undefined ? entry.prefix_count : '—'}
+ {entry.prefix_count != null && entry.prefix_count !== undefined
+ ? entry.prefix_count
+ : '—'}
-
+
{entry.asn_resolved_at
? new Date(entry.asn_resolved_at).toLocaleString('ru-RU')
: '—'}
- {communityLabel(entry.community_id)}
+ {communityLabel(entry.community_id)}
-
openAsEdit(entry)}>
-
(asDeleteTarget = entry)}>
+
openAsEdit(entry)}
+ >
+
(asDeleteTarget = entry)}>
{:else}
- Нет записей
+ Нет записей
{/each}
@@ -1098,7 +1210,9 @@
CDN-источники
URL источников для скачивания списков CIDR
-
+
Добавить
toggleAllCdn((event.currentTarget as HTMLInputElement).checked)}
+ onchange={(event) =>
+ toggleAllCdn((event.currentTarget as HTMLInputElement).checked)}
/>
URL
@@ -1143,36 +1258,48 @@
onchange={() => toggleCdnSelection(src.id)}
/>
- {src.url}
+ {src.url}
{normalizeCdnSourceKind(src.source_kind)}
{#if src.prefix_path?.trim()}
- {src.prefix_path} {src.prefix_path}
{/if}
- {communityLabel(src.community_id)}
-
+ {communityLabel(src.community_id)}
+
{src.refresh_interval_sec !== null && src.refresh_interval_sec !== undefined
? `${src.refresh_interval_sec}с`
: '—'}
-
+
{formatDateTime(src.last_refreshed_at)}
-
openCdnEdit(src)}>
-
(cdnDeleteTarget = src)}>
+
openCdnEdit(src)}
+ >
+
(cdnDeleteTarget = src)}>
{:else}
- Нет источников
+ Нет источников
{/each}
@@ -1186,7 +1313,9 @@
Домены
FQDN для резолвинга через DoH
-
+
toggleAllDomain((event.currentTarget as HTMLInputElement).checked)}
+ onchange={(event) =>
+ toggleAllDomain((event.currentTarget as HTMLInputElement).checked)}
/>
FQDN
@@ -1247,17 +1377,29 @@
/>
{entry.fqdn}
- {communityLabel(entry.community_id)}
+ {communityLabel(entry.community_id)}
-
openDomainEdit(entry)}>
-
(domainDeleteTarget = entry)}>
+
openDomainEdit(entry)}
+ >
+
(domainDeleteTarget = entry)}
+ >
{:else}
- Нет доменов
+ Нет доменов
{/each}
@@ -1271,7 +1413,9 @@
IP-диапазоны
Статические CIDR для анонса
-
+
toggleAllIp((event.currentTarget as HTMLInputElement).checked)}
+ onchange={(event) =>
+ toggleAllIp((event.currentTarget as HTMLInputElement).checked)}
/>
Префикс (CIDR)
@@ -1332,17 +1477,28 @@
/>
{entry.prefix}
- {communityLabel(entry.community_id)}
+ {communityLabel(entry.community_id)}
-
openIpEdit(entry)}>
-
(ipDeleteTarget = entry)}>
+
openIpEdit(entry)}
+ >
+
(ipDeleteTarget = entry)}>
{:else}
- Нет диапазонов
+ Нет диапазонов
{/each}
@@ -1390,7 +1546,9 @@
}}
>
- {editForm.default_community_id ? communityLabel(editForm.default_community_id) : 'Не выбрано'}
+ {editForm.default_community_id
+ ? communityLabel(editForm.default_community_id)
+ : 'Не выбрано'}
Не выбрано
@@ -1430,13 +1588,17 @@
{/each}
-
- {dohPolicyOptions.find((o) => o.value === (editForm.doh_resolver_policy ?? 'primary_only'))?.hint}
+
+ {dohPolicyOptions.find(
+ (o) => o.value === (editForm.doh_resolver_policy ?? 'primary_only')
+ )?.hint}
DoH-профили
-
Порядок выбора = порядок в списке (сверху вниз).
+
+ Порядок выбора = порядок в списке (сверху вниз).
+
{#each dohProfiles as d (d.id)}
@@ -1447,7 +1609,7 @@
{d.name?.trim() ? d.name : d.url}
{#if d.name?.trim()}
- {d.url}
+ {d.url}
{/if}
@@ -1469,11 +1631,11 @@
{/if}
-
Включён
-
+ Включён
+
Отключённые модули не участвуют в обновлении конфигурации.
@@ -1489,7 +1651,9 @@
(editDialog = false)}>Отмена
- {editSaving ? 'Сохранение…' : 'Сохранить'}
+ {editSaving ? 'Сохранение…' : 'Сохранить'}
@@ -1499,7 +1663,9 @@
Удалить модуль «{mod?.name}»?
- Это действие необратимо. Модуль и все его записи будут удалены.
+ Это действие необратимо. Модуль и все его записи будут удалены.
Отмена
@@ -1515,12 +1681,21 @@
{asEdit ? 'Редактировать запись' : 'Новая AS-запись'}
- Номер автономной системы и community для политики анонса.
+ Номер автономной системы и community для политики анонса.
Интервал обновления (сек)
-
+
-
+
-
+
{cdnPreviewLoading ? 'Загрузка…' : 'Предпросмотр'}
{#if cdnPreviewError}
- {cdnPreviewError}
+ {cdnPreviewError}
{:else if cdnPreviewOk}
-
+
Всего: {cdnPreviewTotal}{#if cdnPreviewTruncated}
(обрезано) {/if}
{/if}
{#if cdnPreviewItems.length}
-
+
{#each cdnPreviewItems as item, i (`${i}-${item}`)}
{item}
{/each}
@@ -1674,12 +1867,19 @@
(cdnDialog = false)}>Отмена
- {cdnSaving ? 'Сохранение…' : (cdnEdit ? 'Сохранить' : 'Добавить')}
+ {cdnSaving ? 'Сохранение…' : cdnEdit ? 'Сохранить' : 'Добавить'}
-
{ if (!v) cdnDeleteTarget = null; }}>
+ {
+ if (!v) cdnDeleteTarget = null;
+ }}
+>
Удалить CDN-источник?
@@ -1741,12 +1941,19 @@
(domainDialog = false)}>Отмена
- {domainSaving ? 'Сохранение…' : (domainEdit ? 'Сохранить' : 'Добавить')}
+ {domainSaving ? 'Сохранение…' : domainEdit ? 'Сохранить' : 'Добавить'}
- { if (!v) domainDeleteTarget = null; }}>
+ {
+ if (!v) domainDeleteTarget = null;
+ }}
+>
Удалить домен?
@@ -1787,7 +1994,13 @@
Community (обязательно)
- { ipForm.community_id = v; }}>
+ {
+ ipForm.community_id = v;
+ }}
+ >
{ipForm.community_id ? communityLabel(ipForm.community_id) : 'Выберите community'}
@@ -1801,12 +2014,19 @@
(ipDialog = false)}>Отмена
- {ipSaving ? 'Сохранение…' : (ipEdit ? 'Сохранить' : 'Добавить')}
+ {ipSaving ? 'Сохранение…' : ipEdit ? 'Сохранить' : 'Добавить'}
-
{ if (!v) ipDeleteTarget = null; }}>
+ {
+ if (!v) ipDeleteTarget = null;
+ }}
+>
Удалить диапазон?
diff --git a/web/src/routes/monitoring/+page.svelte b/web/src/routes/monitoring/+page.svelte
index c289518..f7ceea8 100644
--- a/web/src/routes/monitoring/+page.svelte
+++ b/web/src/routes/monitoring/+page.svelte
@@ -4,7 +4,13 @@
import type { BirdStatus, JobsResponse } 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 {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription
+ } from '$lib/components/ui/card/index.js';
import RefreshCw from '@lucide/svelte/icons/refresh-cw';
import Gauge from '@lucide/svelte/icons/gauge';
import Activity from '@lucide/svelte/icons/activity';
@@ -15,7 +21,12 @@
import PageHeader from '$lib/ui/app/page-header/page-header.svelte';
type ReadyStatus = { status: string; checks?: Record };
- type VersionInfo = { version?: string; git_sha?: string; build_time?: string; [k: string]: unknown };
+ type VersionInfo = {
+ version?: string;
+ git_sha?: string;
+ build_time?: string;
+ [k: string]: unknown;
+ };
type JobsKpi = { running: number; failed: number; total: number };
let health = $state<{ ok: boolean; status?: string; error?: string } | null>(null);
@@ -73,7 +84,8 @@
return 'Readiness не в норме: проверьте postgres/store/jobs.';
}
if (overallStatus === 'warn') {
- if (jobs && jobs.failed > 0) return 'Есть ошибки в задачах: откройте операции и последние jobs.';
+ if (jobs && jobs.failed > 0)
+ return 'Есть ошибки в задачах: откройте операции и последние jobs.';
return 'Проверьте BGP-сессии и вывод birdc.';
}
return 'Критичных отклонений не обнаружено.';
@@ -88,7 +100,8 @@
const versionText = $derived.by(() => {
if (!version) return '—';
- const sha = typeof version.git_sha === 'string' && version.git_sha ? version.git_sha : 'unknown';
+ const sha =
+ typeof version.git_sha === 'string' && version.git_sha ? version.git_sha : 'unknown';
return sha.slice(0, 12);
});
@@ -168,7 +181,7 @@
{overallStatus}
{overallStatus}
-
{overallHint}
+
{overallHint}
@@ -184,19 +197,23 @@
{bgpText}
{#if bird?.birdc_configured}
-
{bird.healthy ? 'healthy' : 'degraded'}
+
{bird.healthy ? 'healthy' : 'degraded'}
{:else}
n/a
{/if}
{#if birdError}
-
{birdError}
+
{birdError}
{:else if bird && !bird.birdc_configured}
-
{bird.message ?? 'birdc не настроен на API-хосте.'}
+
+ {bird.message ?? 'birdc не настроен на API-хосте.'}
+
{:else if bird?.error}
-
{bird.error}
+
{bird.error}
{:else}
-
Established / total на API-хосте.
+
Established / total на API-хосте.
{/if}
@@ -217,9 +234,9 @@
{#if jobsError}
-
{jobsError}
+
{jobsError}
{:else}
-
+
Активных: {jobs?.running ?? '—'} из {jobs?.total ?? '—'} последних.
{/if}
@@ -240,9 +257,11 @@
{version ? 'loaded' : '—'}
{#if versionError}
-
{versionError}
+
{versionError}
{:else if version}
-
api_version: {String(version.api_version ?? 'unknown')}
+
+ api_version: {String(version.api_version ?? 'unknown')}
+
{/if}
@@ -274,15 +293,17 @@
{#if ready === null}
—
{:else}
-
{ready.status}
+
{ready.status}
{/if}
{#if health?.error}
-
{health.error}
+
{health.error}
{/if}
{#if readyError}
-
{readyError}
+
{readyError}
{/if}
{#if ready?.checks}
@@ -301,16 +322,28 @@
-
+
Что проверять при деградации
Короткая шпаргалка для triage
- Если health=failed — проверьте доступность процесса API и его логи.
- Если ready!=ready — сначала `postgres`, затем `store/jobs` в checks.
- Если low BGP ratio — проверьте `bird/status`, затем BGP peer состояния.
- Если ошибки jobs — откройте операции и проверьте последние неуспешные задачи.
+
+ Если health=failed — проверьте доступность
+ процесса API и его логи.
+
+
+ Если ready!=ready — сначала `postgres`,
+ затем `store/jobs` в checks.
+
+
+ Если low BGP ratio — проверьте `bird/status`, затем
+ BGP peer состояния.
+
+
+ Если ошибки jobs — откройте операции и проверьте
+ последние неуспешные задачи.
+
diff --git a/web/src/routes/network/+page.svelte b/web/src/routes/network/+page.svelte
index 854ce01..6073d3a 100644
--- a/web/src/routes/network/+page.svelte
+++ b/web/src/routes/network/+page.svelte
@@ -11,7 +11,13 @@
} 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 {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+ CardDescription
+ } from '$lib/components/ui/card/index.js';
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import {
@@ -169,7 +175,9 @@
async function deletePeer() {
if (!peerDeleteTarget) return;
try {
- await apiMutate(`/v1/peers/${peerDeleteTarget.id}`, 'DELETE', undefined, { idempotent: false });
+ await apiMutate(`/v1/peers/${peerDeleteTarget.id}`, 'DELETE', undefined, {
+ idempotent: false
+ });
toast.success('Пир удалён');
peerDeleteTarget = null;
await loadAll();
@@ -190,7 +198,10 @@
speakerDialog = true;
}
async function saveSpeaker() {
- if (!speakerForm.endpoint) { toast.error('Укажите endpoint'); return; }
+ if (!speakerForm.endpoint) {
+ toast.error('Укажите endpoint');
+ return;
+ }
speakerSaving = true;
try {
if (speakerEdit) {
@@ -286,11 +297,20 @@
{p.session_state || '—'}
-
{speakerLabelById(p.bgp_speaker_id)}
+
{speakerLabelById(p.bgp_speaker_id)}
-
openPeerEdit(p)}>
-
(peerDeleteTarget = p)}>
+
openPeerEdit(p)}
+ >
+
(peerDeleteTarget = p)}>
@@ -337,7 +357,11 @@
{s.endpoint}
{s.role}
- {s.last_applied_revision_id ? s.last_applied_revision_id.slice(0, 8) + '…' : '—'}
+ {s.last_applied_revision_id
+ ? s.last_applied_revision_id.slice(0, 8) + '…'
+ : '—'}
{applyingId === s.id ? 'Apply…' : 'Apply'}
-
openSpeakerEdit(s)}>
+
openSpeakerEdit(s)}
+ >
@@ -399,9 +425,7 @@
}}
>
- {peerForm.bgp_speaker_id
- ? speakerLabelById(peerForm.bgp_speaker_id)
- : 'Не выбрано'}
+ {peerForm.bgp_speaker_id ? speakerLabelById(peerForm.bgp_speaker_id) : 'Не выбрано'}
Не выбрано
@@ -412,11 +436,11 @@
-
Включён
-
+ Включён
+
Выключенный пир не попадает в конфиг BIRD до следующей ревизии.
@@ -432,12 +456,19 @@
(peerDialog = false)}>Отмена
- {peerSaving ? 'Сохранение…' : (peerEdit ? 'Сохранить' : 'Создать')}
+ {peerSaving ? 'Сохранение…' : peerEdit ? 'Сохранить' : 'Создать'}
-
{ if (!v) peerDeleteTarget = null; }}>
+ {
+ if (!v) peerDeleteTarget = null;
+ }}
+>
Удалить пира?
@@ -459,7 +490,11 @@
Endpoint
-
+
Роль
@@ -468,7 +503,9 @@
(speakerDialog = false)}>Отмена
- {speakerSaving ? 'Сохранение…' : (speakerEdit ? 'Сохранить' : 'Создать')}
+ {speakerSaving ? 'Сохранение…' : speakerEdit ? 'Сохранить' : 'Создать'}
diff --git a/web/src/routes/operations/+page.svelte b/web/src/routes/operations/+page.svelte
index 8bf0595..6945713 100644
--- a/web/src/routes/operations/+page.svelte
+++ b/web/src/routes/operations/+page.svelte
@@ -426,7 +426,9 @@
const job = await waitForJob(res.job_id, { timeoutMs: 120000 });
const extra = summarizeJobBirdMeta(job);
if (job.status === 'succeeded') {
- toast.success(extra ? `Перезагрузка успешна. ${extra}` : 'Команда birdc configure выполнена');
+ toast.success(
+ extra ? `Перезагрузка успешна. ${extra}` : 'Команда birdc configure выполнена'
+ );
} else {
toast.error(
job.error
@@ -1019,7 +1021,9 @@
>{jobDetail.job_id}
Статус {jobStatusRu(jobDetail.status)} {jobStatusRu(jobDetail.status)}
Создана {formatDate(jobDetail.created_at)} j.kind === 'module_refresh'));
const moduleNameById = $derived(new Map(modules.map((m) => [m.id, m.name])));
- const runningJobsCount = $derived(jobs.filter((j) => j.status === 'running' || j.status === 'queued').length);
+ const runningJobsCount = $derived(
+ jobs.filter((j) => j.status === 'running' || j.status === 'queued').length
+ );
const failedJobsCount = $derived(jobs.filter((j) => j.status === 'failed').length);
@@ -98,15 +108,15 @@
- Всего задач
+ Всего задач
{jobs.length}
- В работе
+ В работе
{runningJobsCount}
- С ошибкой
+ С ошибкой
{failedJobsCount}
@@ -126,7 +136,7 @@
Тип
Интервал
Cron
-
+
@@ -137,7 +147,12 @@
{intervalLabel(m.refresh_interval_sec)}
{m.cron_expr || '—'}
- refreshModule(m.id)}>
+ refreshModule(m.id)}
+ >
{refreshing[m.id] ? '…' : 'Обновить'}
@@ -174,9 +189,16 @@
{#each jobs as j (j.job_id)}
{jobKindTitle(j, moduleNameById)}
- {jobStatusRu(j.status)}
- {j.created_at ? new Date(j.created_at).toLocaleString('ru') : '—'}
- {j.error ?? ''}
+ {jobStatusRu(j.status)}
+ {j.created_at ? new Date(j.created_at).toLocaleString('ru') : '—'}
+ {j.error ?? ''}
{:else}
@@ -193,7 +215,9 @@
Операции обновления модулей
- Отдельная лента задач обновления модулей для контроля по модулям.
+ Отдельная лента задач обновления модулей для контроля по модулям.
@@ -207,9 +231,18 @@
{#each refreshJobs as j (j.job_id)}
- {jobStatusRu(j.status)}
- {j.created_at ? new Date(j.created_at).toLocaleString('ru') : '—'}
- {typeof j.meta?.revision_id === 'string' ? `${j.meta.revision_id.slice(0, 12)}…` : '—'}
+ {jobStatusRu(j.status)}
+ {j.created_at ? new Date(j.created_at).toLocaleString('ru') : '—'}
+ {typeof j.meta?.revision_id === 'string'
+ ? `${j.meta.revision_id.slice(0, 12)}…`
+ : '—'}
{:else}
diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte
index 7ac3553..e42da12 100644
--- a/web/src/routes/settings/+page.svelte
+++ b/web/src/routes/settings/+page.svelte
@@ -152,9 +152,7 @@
return errors;
});
- let hasValidationErrors = $derived(
- knownFieldKeys.some((key) => Boolean(knownFieldErrors[key]))
- );
+ let hasValidationErrors = $derived(knownFieldKeys.some((key) => Boolean(knownFieldErrors[key])));
function addAdditionalSetting() {
additionalSettings.push({ id: additionalIdCounter++, key: '', value: '' });
@@ -240,7 +238,8 @@
for (const key of knownFieldKeys) {
const value = String(knownFields[key] ?? '').trim();
if (!value || knownFieldErrors[key]) continue;
- if (key === 'bird_local_asn' || key === 'revision_retention_minutes') payload[key] = Number(value);
+ if (key === 'bird_local_asn' || key === 'revision_retention_minutes')
+ payload[key] = Number(value);
else payload[key] = value;
}
for (const entry of additionalSettings) {
@@ -298,21 +297,27 @@
{/each}
-
+
{#if activeSection === 'token'}
{:else if loadingSettings}
-
Загрузка…
+
Загрузка…
{:else if apiSettings === null}
Загрузить настройки
{:else}
@@ -332,7 +337,11 @@
Router ID (bird_router_id)
-
+
{#if knownFieldErrors.bird_router_id}
{knownFieldErrors.bird_router_id}
{/if}
@@ -340,7 +349,11 @@
Локальный IPv4 (bird_local_ipv4)
-
+
{#if knownFieldErrors.bird_local_ipv4}
{knownFieldErrors.bird_local_ipv4}
{/if}
@@ -348,7 +361,11 @@
Локальный IPv6 (bird_local_ipv6)
-
+
{#if knownFieldErrors.bird_local_ipv6}
{knownFieldErrors.bird_local_ipv6}
{/if}
@@ -409,10 +426,13 @@
placeholder="43200"
/>
{#if knownFieldErrors.revision_retention_minutes}
-
{knownFieldErrors.revision_retention_minutes}
+
+ {knownFieldErrors.revision_retention_minutes}
+
{/if}
-
- Старые ревизии удаляются автоматически. Последняя раскатанная ревизия не удаляется.
+
+ Старые ревизии удаляются автоматически. Последняя раскатанная ревизия не
+ удаляется.
@@ -427,13 +447,16 @@
{#if additionalSettings.length === 0}
-
Нет дополнительных параметров.
+
Нет дополнительных параметров.
{/if}