fix(ui): проверять диапазон AS в каталоге BGP
This commit is contained in:
1 parent
748a3c00ad
commit
2199950681
4 files changed
+36
-2
No files matched your search
@@ -26,11 +26,16 @@
|
|||||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||||
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
||||||
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
| 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 |
|
||||||
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.14, состояния и cost соседей OSPF; далее показатели/URL/layout |
|
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.15, OSPF и проверка диапазона AS; далее показатели/URL/layout |
|
||||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
## Блок 10.15 — диапазон AS в клиенте BGP (2026-09-30)
|
||||||
|
|
||||||
|
- Клиентская схема согласована со строгим backend parser: localAs/remoteAs — null или целое 1..4294967295. Ноль и переполнение отклоняют ответ; предыдущий снимок сохраняется как неизвестный, повторный успешный опрос восстанавливает данные.
|
||||||
|
- 13 BGP data E2E и TypeScript прошли, включая обе AS × ноль/переполнение. На nullable показатели OSPF, URL и визуальную приёмку это не влияет; они остаются следующим основным объёмом. Локальный коммит без push.
|
||||||
|
|
||||||
## Блок 10.14 — неизвестный cost соседа OSPF (2026-09-30)
|
## Блок 10.14 — неизвестный cost соседа OSPF (2026-09-30)
|
||||||
|
|
||||||
- При отсутствии интерфейса в каталоге cost соседа остаётся null: таблица, линия топологии и панель узла показывают «—». Значение 10 больше не выдумывается; известные costs и сортировка сохранены.
|
- При отсутствии интерфейса в каталоге cost соседа остаётся null: таблица, линия топологии и панель узла показывают «—». Значение 10 больше не выдумывается; известные costs и сортировка сохранены.
|
||||||
|
|||||||
@@ -96,3 +96,7 @@ Lock/readiness, существующее mm:servers:write, валидация п
|
|||||||
## 10.14. Неизвестный cost соседа
|
## 10.14. Неизвестный cost соседа
|
||||||
|
|
||||||
Отсутствующий интерфейс не означает cost=10: таблица/линия/панель показывают «—». 11 E2E и TS прошли. Backend defaults/nullable показатели остаются следующим блоком.
|
Отсутствующий интерфейс не означает cost=10: таблица/линия/панель показывают «—». 11 E2E и TS прошли. Backend defaults/nullable показатели остаются следующим блоком.
|
||||||
|
|
||||||
|
## 10.15. Диапазон AS клиента BGP
|
||||||
|
|
||||||
|
Схема клиента принимает null либо 1..4294967295, согласованно со строгим backend parser. Неверный ответ сохраняет кэш как неизвестный. 13 data E2E и TS прошли.
|
||||||
+2
-1
@@ -2,10 +2,11 @@ import { z } from "zod"
|
|||||||
import { vxlanServersSchema } from "@/lib/vxlan-response"
|
import { vxlanServersSchema } from "@/lib/vxlan-response"
|
||||||
|
|
||||||
const counter = z.number().int().nonnegative()
|
const counter = z.number().int().nonnegative()
|
||||||
|
const autonomousSystem = z.number().int().min(1).max(4294967295).nullable()
|
||||||
export const bgpSessionSchema = z.object({
|
export const bgpSessionSchema = z.object({
|
||||||
id: z.string().trim().min(1), serverId: z.number().int().positive(),
|
id: z.string().trim().min(1), serverId: z.number().int().positive(),
|
||||||
serverName: z.string(), serverSite: z.string(), serverCountry: z.string(),
|
serverName: z.string(), serverSite: z.string(), serverCountry: z.string(),
|
||||||
name: z.string(), peerIp: z.string(), remoteAs: counter.nullable(), localAs: counter.nullable(),
|
name: z.string(), peerIp: z.string(), remoteAs: autonomousSystem, localAs: autonomousSystem,
|
||||||
localId: z.string(), remoteId: z.string(), state: z.string().trim().min(1), type: z.enum(["eBGP", "iBGP", "Unknown"]),
|
localId: z.string(), remoteId: z.string(), state: z.string().trim().min(1), type: z.enum(["eBGP", "iBGP", "Unknown"]),
|
||||||
uptime: z.string().nullable(), holdTime: counter.nullable(), keepalive: counter.nullable(),
|
uptime: z.string().nullable(), holdTime: counter.nullable(), keepalive: counter.nullable(),
|
||||||
prefixesRx: counter.nullable(), prefixesTx: counter.nullable(), inputMessages: counter.nullable(), outputMessages: counter.nullable(),
|
prefixesRx: counter.nullable(), prefixesTx: counter.nullable(), inputMessages: counter.nullable(), outputMessages: counter.nullable(),
|
||||||
|
|||||||
@@ -93,3 +93,27 @@ test("BGP: неизвестные поля и наблюдаемый ноль н
|
|||||||
await expect(page.getByLabel("Сводка префиксов BGP")).toContainText("нет данных API")
|
await expect(page.getByLabel("Сводка префиксов BGP")).toContainText("нет данных API")
|
||||||
await expect(page.getByText("Недостаточно данных о префиксах для сравнения", { exact: true })).toBeVisible()
|
await expect(page.getByText("Недостаточно данных о префиксах для сравнения", { exact: true })).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
for (const field of ["localAs", "remoteAs"]) for (const invalid of [0, 4294967296]) {
|
||||||
|
test("BGP: невалидная AS сохраняет снимок " + field + "=" + invalid, async ({ page }) => {
|
||||||
|
let broken = false
|
||||||
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||||
|
await page.route("**/api/**", r => {
|
||||||
|
const path = new URL(r.request().url()).pathname
|
||||||
|
return r.fulfill({ json: path === "/api/servers" ? [server(1)] : path === "/api/bgp/catalog" ? { sessions: [{ ...session(1), ...(broken ? { [field]: invalid } : {}) }], failures: [] } : path === "/api/auth/config" ? { required: false } : {} })
|
||||||
|
})
|
||||||
|
await page.goto("/bgp")
|
||||||
|
const rows = page.locator("tr[data-row-id]")
|
||||||
|
await expect(rows).toHaveCount(1)
|
||||||
|
broken = true
|
||||||
|
await page.getByRole("button", { name: "Обновить", exact: true }).click()
|
||||||
|
const alert = page.getByRole("alert").filter({ hasText: "Не удалось загрузить BGP" })
|
||||||
|
await expect(alert).toContainText("Показаны последние полученные данные")
|
||||||
|
await expect(rows).toHaveCount(1)
|
||||||
|
await expect(rows).toContainText("Неизвестно")
|
||||||
|
broken = false
|
||||||
|
await alert.getByRole("button", { name: "Повторить загрузку BGP" }).click()
|
||||||
|
await expect(alert).toHaveCount(0)
|
||||||
|
await expect(rows).toContainText("Established")
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user