fix(ui): ограничить граф OSPF с сохранением полного контекста

This commit is contained in:
Denozordec committed 2026-10-04 17:25:55 +07:00
1 parent 1a82c1b41f
commit 8d86c5cbb0
10 files changed
+194 -13

No files matched your search

+85 -4
View File
@@ -23,13 +23,23 @@ test("OSPF1000 соседей: профиль всего экрана с topolog
const rows = page.locator("tr[data-row-id]")
await expect(rows).toHaveCount(50)
await expect(rows.first()).toHaveAttribute("data-row-id", "1-n951")
await expect(page.locator("[data-ospf-node]")).toHaveCount(1001)
await expect(page.locator("[data-ospf-node]")).toHaveCount(2)
const profile = { count: neighbors.length, bytes: Buffer.byteLength(JSON.stringify(catalog)), rows: await rows.count(), graphNodes: await page.locator("[data-ospf-node]").count(), svgNodes: await page.getByRole("group", { name: "Топология OSPF", exact: true }).locator("*").count(), pageNodes: await page.locator("*").count(), readyMs: Date.now() - start }
console.log("OSPF neighbors table", JSON.stringify(profile))
console.log("OSPF neighbors topology", JSON.stringify(profile))
writeFileSync(info.outputPath("ospf-neighbors-profile.json"), JSON.stringify(profile, null, 2))
await info.attach("profile", { body: JSON.stringify(profile), contentType: "application/json" })
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
await page.screenshot({ path: info.outputPath("ospf-neighbors-table-1440.png"), animations: "disabled" })
await page.screenshot({ path: info.outputPath("ospf-neighbors-topology-1440.png"), animations: "disabled" })
const scope = page.getByRole("region", { name: "Область топологии OSPF" })
await expect(scope.getByRole("status")).toContainText("Показаны 2 из 1001 узлов")
await expect(scope.getByRole("status")).toContainText("Всего в топологии 1000 связей")
await scope.getByRole("textbox", { name: "Найти узел топологии" }).fill("10.0.0.1")
await scope.getByRole("button", { name: "Открыть узел топологии 10.0.0.1 (remote-10.0.0.1)", exact: true }).click()
await expect(page).toHaveURL(url => url.searchParams.get("node") === "remote-10.0.0.1" && url.searchParams.get("keep") === "1")
await expect(page.locator("[data-ospf-node]")).toHaveCount(2)
await scope.getByRole("textbox", { name: "Найти узел топологии" }).fill("10.3.231.1")
await scope.getByRole("button", { name: "Открыть узел топологии 10.3.231.1 (remote-10.3.231.1)", exact: true }).click()
await expect(rows.first()).toHaveAttribute("data-row-id", "1-n951")
const next = page.getByRole("button", { name: "Следующая страница", exact: true })
await expect(next).toBeDisabled()
const before = reads; await page.getByRole("button", { name: "Обновить", exact: true }).click()
@@ -58,8 +68,79 @@ test("OSPF1000 соседей: профиль всего экрана с topolog
expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([])
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320)
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
await page.screenshot({ path: info.outputPath("ospf-neighbors-table-320.png"), animations: "disabled" })
await page.screenshot({ path: info.outputPath("ospf-neighbors-topology-320.png"), animations: "disabled" })
failed = false; neighbors = [neighbors[999]]; await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(rows).toHaveCount(1); await expect(rows.first()).toHaveAttribute("data-row-id", "1-n1000")
expect(writes).toEqual([])
})
test("OSPF topology: полный статус, страницы связей и поиск скрытого узла", async ({ page }, info) => {
test.setTimeout(90000)
const host = { serverId: 1, serverName: "Роутер 1", serverSite: "", serverCountry: "RU" }
let neighbors = Array.from({ length: 1000 }, (_, i) => ({ ...host, id: `n${i + 1}`, address: `10.${Math.floor(i / 256)}.${i % 256}.1`, routerId: `10.${Math.floor(i / 256)}.${i % 256}.1`, instance: "default", area: "backbone", areaId: "0.0.0.0", interface: `ether${i + 1}`, state: "Full", uptime: "1h", stateChanges: 0, priority: 1 }))
const interfaces = neighbors.map((n, i) => ({ ...host, id: `i${i + 1}`, instance: "default", area: "backbone", areaId: "0.0.0.0", interface: n.interface, cost: i + 1, type: "broadcast", disabled: false, inactive: false, priority: 1, helloInterval: 10, deadInterval: 40, useBfd: false }))
const catalog = ospfCatalogSchema.parse({ neighbors, interfaces, instances: [{ ...host, id: "instance1", name: "default", routerId: "192.0.2.1", version: 2, disabled: false, inactive: false, redistribute: "" }], bfdSessions: [], routes: [], failures: [] })
neighbors = neighbors.map((n,i) => ({ ...n, state: i === 999 ? "Full" : "Down" }))
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/auth/config" ? { required: false } : path === "/api/ospf/catalog" ? { ...catalog, neighbors } : path === "/api/servers" ? [{ id: 1, name: "Роутер 1", host: "192.0.2.1", enabled: true, country: "RU", status: null }] : {} })
})
await page.goto("/ospf?tab=neighbors&server=1&keep=1")
const scope = page.getByRole("region", { name: "Область топологии OSPF" })
await expect(scope.getByRole("status")).toContainText("Показаны 9 из 1001 узлов. Связи узла: 1–8 из 1000")
await expect(page.locator("[data-ospf-node]")).toHaveCount(9)
const local = page.locator('[data-ospf-node="1"]')
await expect(local).toHaveAttribute("data-ospf-state", "Full")
await scope.getByRole("button", { name: "Следующая страница связей OSPF" }).click()
await expect(scope.getByRole("status")).toContainText("9–16 из 1000")
await expect(local).toHaveAttribute("data-ospf-state", "Full")
await expect(page.locator('[data-ospf-node="remote-10.0.8.1"]')).toHaveCount(1)
await scope.getByRole("button", { name: "Предыдущая страница связей OSPF" }).click()
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
await page.screenshot({ path: info.outputPath("ospf-topology-scope-1440.png"), animations: "disabled" })
await page.setViewportSize({ width: 320, height: 1000 })
await page.screenshot({ path: info.outputPath("ospf-topology-scope-320.png"), animations: "disabled" })
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320)
for (const button of await scope.locator("button").all()) expect((await button.boundingBox())!.height).toBeGreaterThanOrEqual(44)
await page.addScriptTag({ content: axe.source })
expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[aria-label="Область топологии OSPF"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([])
const picker = scope.getByRole("textbox", { name: "Найти узел топологии" })
await picker.fill("10.3.231.1")
await scope.getByRole("button", { name: "Открыть узел топологии 10.3.231.1 (remote-10.3.231.1)", exact: true }).click()
await expect(page).toHaveURL(url => url.searchParams.get("node") === "remote-10.3.231.1" && url.searchParams.get("keep") === "1")
await expect(page.locator("[data-ospf-node]")).toHaveCount(2)
await page.getByRole("region", { name: "Детали узла OSPF" }).getByRole("button", { name: "Закрыть", exact: true }).click()
await expect(picker).toBeFocused()
await expect(scope.getByRole("status")).toContainText("1–8 из 1000")
await scope.getByRole("button", { name: "Следующая страница связей OSPF" }).click()
await scope.getByRole("button", { name: "Следующая страница связей OSPF" }).click()
neighbors = neighbors.slice(0, 13)
await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(scope.getByRole("status")).toContainText("9–13 из 13")
await expect(scope.getByRole("button", { name: "Следующая страница связей OSPF" })).toBeDisabled()
neighbors = neighbors.slice(0, 12)
await page.getByRole("button", { name: "Обновить", exact: true }).click()
await expect(scope.getByRole("status")).toContainText("9–12 из 12")
})
test("OSPF: тысяча параллельных связей не обходит ограничение графа", async ({ page }) => {
const host = { serverId: 1, serverName: "Роутер 1", serverSite: "", serverCountry: "RU" }
let neighbors = Array.from({ length: 1000 }, (_, i) => ({ ...host, id: `n${i + 1}`, address: `10.${Math.floor(i / 256)}.${i % 256}.1`, routerId: `10.${Math.floor(i / 256)}.${i % 256}.1`, instance: "default", area: "backbone", areaId: "0.0.0.0", interface: `ether${i + 1}`, state: "Full", uptime: "1h", stateChanges: 0, priority: 1 }))
const interfaces = neighbors.map((n, i) => ({ ...host, id: `i${i + 1}`, instance: "default", area: "backbone", areaId: "0.0.0.0", interface: n.interface, cost: i + 1, type: "broadcast", disabled: false, inactive: false, priority: 1, helloInterval: 10, deadInterval: 40, useBfd: false }))
const catalog = ospfCatalogSchema.parse({ neighbors, interfaces, instances: [{ ...host, id: "instance1", name: "default", routerId: "192.0.2.1", version: 2, disabled: false, inactive: false, redistribute: "" }], bfdSessions: [], routes: [], failures: [] })
neighbors = neighbors.map(n => ({ ...n, routerId: "10.0.0.1", address: "10.0.0.1" }))
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/auth/config" ? { required: false } : path === "/api/ospf/catalog" ? { ...catalog, neighbors } : path === "/api/servers" ? [{ id: 1, name: "Роутер 1", host: "192.0.2.1", enabled: true, country: "RU", status: null }] : {} })
})
await page.goto("/ospf?tab=neighbors&server=1")
await expect(page.getByRole("region", { name: "Область топологии OSPF" }).getByRole("status")).toContainText("Показаны 2 из 2 узлов. Связи узла: 1–8 из 1000")
await expect(page.locator("[data-ospf-node]")).toHaveCount(2)
expect(await page.getByRole("group", { name: "Топология OSPF", exact: true }).locator("*").count()).toBeLessThan(200)
})