From 9f0b1281f2966020a902004913b08c667af78c65 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 6 Apr 2026 20:34:49 +0700 Subject: [PATCH] feat: enhance API client with pagination and new data fetching functions. Implement `apiPageAll` for paginated API requests, and add functions to fetch all revision prefixes and module source catalogs based on module type. Update type definitions to support new response structures, improving data handling in the application. --- web/src/lib/api/client.ts | 63 +++- web/src/lib/api/types.ts | 2 + .../routes/modules/[moduleId]/+page.svelte | 80 ++++- web/src/routes/operations/+page.svelte | 288 +++++++++++++++++- web/src/routes/schedule/+page.svelte | 52 +++- 5 files changed, 470 insertions(+), 15 deletions(-) diff --git a/web/src/lib/api/client.ts b/web/src/lib/api/client.ts index fac96b0..ab3bfaf 100644 --- a/web/src/lib/api/client.ts +++ b/web/src/lib/api/client.ts @@ -1,5 +1,14 @@ import { browser } from '$app/environment'; -import type { JobRow } from './types.js'; +import type { + AsEntriesResponse, + CdnSourcesResponse, + DomainEntriesResponse, + IpRangeEntriesResponse, + JobRow, + ModuleType, + RevisionPrefix, + RevisionPrefixesResponse +} from './types.js'; export const TOKEN_STORAGE_KEY = 'evobgp_api_token'; @@ -122,3 +131,55 @@ export async function waitForJob( } throw new Error(`Таймаут ожидания задачи ${jobId}`); } + +export async function apiPageAll(path: string, limit = 500): Promise { + const items: T[] = []; + let cursor: string | null = null; + while (true) { + const [basePath, rawQuery = ''] = path.split('?'); + const query = new URLSearchParams(rawQuery); + if (!query.has('limit')) query.set('limit', String(limit)); + if (cursor) query.set('cursor', cursor); + else query.delete('cursor'); + const page = await apiJSON<{ items?: T[]; next_cursor?: string | null; has_more?: boolean }>( + `${basePath}?${query.toString()}` + ); + items.push(...(page.items ?? [])); + if (!page.has_more || !page.next_cursor) break; + cursor = page.next_cursor; + } + return items; +} + +export async function fetchRevisionPrefixesAll(revisionId: string): Promise { + const items = await apiPageAll(`/v1/revisions/${revisionId}/prefixes`); + return items; +} + +export async function fetchModuleSourceCatalog(moduleId: string, moduleType: ModuleType) { + if (moduleType === 'DOMAINS') { + const entries = await apiPageAll( + `/v1/modules/${moduleId}/domain-entries` + ); + return { domains: entries }; + } + if (moduleType === 'AS_PREFIXES') { + const entries = await apiPageAll( + `/v1/modules/${moduleId}/as-entries` + ); + return { asns: entries }; + } + if (moduleType === 'CDN_CIDRS') { + const entries = await apiPageAll( + `/v1/modules/${moduleId}/cdn-sources` + ); + return { cdnSources: entries }; + } + if (moduleType === 'IP_RANGES') { + const entries = await apiPageAll( + `/v1/modules/${moduleId}/ip-range-entries` + ); + return { ipRanges: entries }; + } + return {}; +} diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts index 04723cf..da406a1 100644 --- a/web/src/lib/api/types.ts +++ b/web/src/lib/api/types.ts @@ -179,6 +179,8 @@ export type RevisionsResponse = Page; export type RevisionPrefix = { /** Обычно CIDR; для AS-модуля в снимке ревизии — строка вида `as:<номер_asn>`. */ prefix: string; + /** Источник материализации (например, domain:, as:, cdn:, ip_range). */ + source?: string; community_id?: string | null; }; export type RevisionPrefixesResponse = Page; diff --git a/web/src/routes/modules/[moduleId]/+page.svelte b/web/src/routes/modules/[moduleId]/+page.svelte index e9f134b..b9b8389 100644 --- a/web/src/routes/modules/[moduleId]/+page.svelte +++ b/web/src/routes/modules/[moduleId]/+page.svelte @@ -725,6 +725,10 @@ default: return 'as'; } }); + + const asPrefixTotal = $derived( + asEntries.reduce((acc, entry) => acc + (entry.prefix_count ?? 0), 0) + ); {#if loadingMod} @@ -795,6 +799,70 @@ + + + Операционный отчёт модуля + + Читаемая сводка по данным модуля: источники, объёмы и ожидаемый результат для refresh/агрегации. + + + + {#if mod.type === 'DOMAINS'} +
+

Домены и ожидаемые IP

+

После refresh домены резолвятся в IP и конвертируются в префиксы.

+
+ {#each domainEntries.slice(0, 8) as entry (entry.id)} +

{entry.fqdn}

+ {:else} +

Нет доменов

+ {/each} +
+
+ {:else if mod.type === 'AS_PREFIXES'} +
+

ASN и число полученных префиксов

+

Счётчик префиксов обновляется после успешного refresh.

+
+

Всего ASN: {asEntries.length}

+

Сумма префиксов: {asPrefixTotal}

+
+
+ {:else if mod.type === 'CDN_CIDRS'} +
+

CDN ссылки и импортируемые префиксы

+

Каждый URL поставляет список CIDR для агрегации.

+
+ {#each cdnSources.slice(0, 6) as src (src.id)} +

{src.url}

+ {:else} +

Нет CDN источников

+ {/each} +
+
+ {:else if mod.type === 'IP_RANGES'} +
+

IP ranges для агрегации

+

Статические CIDR, которые попадают в итоговую ревизию.

+
+ {#each ipEntries.slice(0, 8) as entry (entry.id)} +

{entry.prefix}

+ {:else} +

Нет диапазонов

+ {/each} +
+
+ {/if} +
+

Результат операции

+

+ Подробный результат по конкретному запуску refresh смотрите в `Операции -> Задачи -> module_refresh`: + там отображаются источники, количество префиксов и итог агрегации. +

+
+
+
+ {#if mod.type === 'AS_PREFIXES'} @@ -1189,7 +1257,7 @@ Не выбрано - {#each communities as c} + {#each communities as c (c.id)} {communityOptionLabel(c)} {/each} @@ -1203,7 +1271,7 @@ Не выбрано - {#each dohProfiles as d} + {#each dohProfiles as d (d.id)} {d.url} {/each} @@ -1271,7 +1339,7 @@ Не выбрано - {#each communities as c} + {#each communities as c (c.id)} {communityOptionLabel(c)} {/each} @@ -1368,7 +1436,7 @@ Не выбрано - {#each communities as c} + {#each communities as c (c.id)} {communityOptionLabel(c)} {/each} @@ -1455,7 +1523,7 @@ Не выбрано - {#each communities as c} + {#each communities as c (c.id)} {communityOptionLabel(c)} {/each} @@ -1515,7 +1583,7 @@ {ipForm.community_id ? communityLabel(ipForm.community_id) : 'Выберите community'} - {#each communities as c} + {#each communities as c (c.id)} {communityOptionLabel(c)} {/each} diff --git a/web/src/routes/operations/+page.svelte b/web/src/routes/operations/+page.svelte index f9b5346..0ae48c4 100644 --- a/web/src/routes/operations/+page.svelte +++ b/web/src/routes/operations/+page.svelte @@ -1,12 +1,21 @@
@@ -84,6 +88,21 @@
+
+ +

Всего задач

+

{jobs.length}

+
+ +

В работе

+

{runningJobsCount}

+
+ +

С ошибкой

+

{failedJobsCount}

+
+
+ Модули @@ -129,7 +148,7 @@ Последние задачи - GET /v1/jobs + Задачи разложены по статусу и времени запуска. @@ -160,4 +179,35 @@
+ + + + Операции обновления модулей + Отдельная лента задач `module_refresh` для контроля по модулям. + + + + + + Статус + Создана + revision_id + + + + {#each refreshJobs as j (j.job_id)} + + {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} + + Нет задач module_refresh + + {/each} + +
+
+