fix(bgp): сохранять неизвестные AS и тип сессии
This commit is contained in:
1 parent
2e17cbe926
commit
5c307a5b6c
9 files changed
+47
-21
No files matched your search
+11
-10
@@ -197,6 +197,7 @@ const STATE_STYLE: Record<BgpState, { bg: string; text: string; dot: string; lab
|
|||||||
OpenConfirm: { bg: "bg-violet-500/10", text: "text-violet-600 dark:text-violet-400", dot: "bg-violet-500", label: "OpenConfirm" },
|
OpenConfirm: { bg: "bg-violet-500/10", text: "text-violet-600 dark:text-violet-400", dot: "bg-violet-500", label: "OpenConfirm" },
|
||||||
}
|
}
|
||||||
const TYPE_STYLE: Record<BgpType, { bg: string; text: string }> = {
|
const TYPE_STYLE: Record<BgpType, { bg: string; text: string }> = {
|
||||||
|
Unknown: { bg: "bg-muted", text: "text-muted-foreground" },
|
||||||
eBGP: { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
|
eBGP: { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
|
||||||
iBGP: { bg: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
|
iBGP: { bg: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
|
||||||
}
|
}
|
||||||
@@ -206,7 +207,7 @@ function TypeBadge({ type }: { type: BgpType }) {
|
|||||||
return (
|
return (
|
||||||
<span className={cn("inline-flex items-center rounded border px-1.5 py-0.5 text-[10px] font-semibold",
|
<span className={cn("inline-flex items-center rounded border px-1.5 py-0.5 text-[10px] font-semibold",
|
||||||
s.bg, s.text, "border-current/20")}>
|
s.bg, s.text, "border-current/20")}>
|
||||||
{type}
|
{type === "Unknown" ? "Тип неизвестен" : type}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -276,7 +277,7 @@ function SessionsTab({ sessions, emptyTitle, selectedSessionId, onSelectSession
|
|||||||
!s.description.toLowerCase().includes(q) &&
|
!s.description.toLowerCase().includes(q) &&
|
||||||
!s.serverLabel.includes(q) &&
|
!s.serverLabel.includes(q) &&
|
||||||
!String(s.remoteAs).includes(q) &&
|
!String(s.remoteAs).includes(q) &&
|
||||||
!(AS_NAMES[s.remoteAs] ?? "").toLowerCase().includes(q)
|
!(s.remoteAs === null ? "" : AS_NAMES[s.remoteAs] ?? "").toLowerCase().includes(q)
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -358,7 +359,7 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
<ServerIcon className="size-4 text-muted-foreground shrink-0" />
|
<ServerIcon className="size-4 text-muted-foreground shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium leading-none">{router.label}</p>
|
<p className="text-sm font-medium leading-none">{router.label}</p>
|
||||||
<p className="text-[11px] font-mono text-muted-foreground mt-0.5">{router.site} · {Array.from(new Set(router.sessions.map(s => "AS" + s.localAs))).join(", ")}</p>
|
<p className="text-[11px] font-mono text-muted-foreground mt-0.5">{router.site} · {Array.from(new Set(router.sessions.map(s => s.localAs === null ? "AS неизвестна" : "AS" + s.localAs))).join(", ")}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 shrink-0">
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
{estCnt > 0 && (
|
{estCnt > 0 && (
|
||||||
@@ -389,8 +390,8 @@ function RoutersTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
<TypeBadge type={s.type} />
|
<TypeBadge type={s.type} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 mt-0.5">
|
<div className="flex items-center gap-1.5 mt-0.5">
|
||||||
<span className="text-[10px] text-muted-foreground">AS{s.remoteAs}</span>
|
<span className="text-[10px] text-muted-foreground">{s.remoteAs === null ? "AS неизвестна" : "AS" + s.remoteAs}</span>
|
||||||
{AS_NAMES[s.remoteAs] && (
|
{s.remoteAs !== null && AS_NAMES[s.remoteAs] && (
|
||||||
<span className="text-[10px] text-muted-foreground/60">· {AS_NAMES[s.remoteAs]}</span>
|
<span className="text-[10px] text-muted-foreground/60">· {AS_NAMES[s.remoteAs]}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -474,14 +475,14 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
{
|
{
|
||||||
id: "ebgp",
|
id: "ebgp",
|
||||||
label: "eBGP сессий",
|
label: "eBGP сессий",
|
||||||
value: ebgpSessions,
|
value: sessions.some(s => s.type === "Unknown") ? "—" : ebgpSessions,
|
||||||
icon: <ActivityIcon className="size-4" />,
|
icon: <ActivityIcon className="size-4" />,
|
||||||
iconClassName: "text-muted-foreground",
|
iconClassName: "text-muted-foreground",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ibgp",
|
id: "ibgp",
|
||||||
label: "iBGP сессий",
|
label: "iBGP сессий",
|
||||||
value: ibgpSessions,
|
value: sessions.some(s => s.type === "Unknown") ? "—" : ibgpSessions,
|
||||||
icon: <ServerIcon className="size-4" />,
|
icon: <ServerIcon className="size-4" />,
|
||||||
iconClassName: "text-muted-foreground",
|
iconClassName: "text-muted-foreground",
|
||||||
},
|
},
|
||||||
@@ -504,7 +505,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
<div className="flex items-center justify-between mb-1 gap-2">
|
<div className="flex items-center justify-between mb-1 gap-2">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<span className="text-xs font-mono truncate">{s.peerIp}</span>
|
<span className="text-xs font-mono truncate">{s.peerIp}</span>
|
||||||
<span className="text-[10px] text-muted-foreground shrink-0">AS{s.remoteAs}</span>
|
<span className="text-[10px] text-muted-foreground shrink-0">{s.remoteAs === null ? "AS неизвестна" : "AS" + s.remoteAs}</span>
|
||||||
<TypeBadge type={s.type} />
|
<TypeBadge type={s.type} />
|
||||||
</div>
|
</div>
|
||||||
<span className={cn("text-[10px] font-semibold shrink-0 tabular-nums font-mono", "text-emerald-600 dark:text-emerald-400")}>
|
<span className={cn("text-[10px] font-semibold shrink-0 tabular-nums font-mono", "text-emerald-600 dark:text-emerald-400")}>
|
||||||
@@ -556,7 +557,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
|
|
||||||
{/* eBGP vs iBGP */}
|
{/* eBGP vs iBGP */}
|
||||||
<OpsPanel title="eBGP vs iBGP" contentClassName="pt-4 pb-4 px-5">
|
<OpsPanel title="eBGP vs iBGP" contentClassName="pt-4 pb-4 px-5">
|
||||||
{sessions.length === 0 ? (
|
{sessions.length === 0 || sessions.some(s => s.type === "Unknown") ? (
|
||||||
<p className="text-xs text-muted-foreground">Нет данных</p>
|
<p className="text-xs text-muted-foreground">Нет данных</p>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -583,7 +584,7 @@ function AnalyticsTab({ sessions }: { sessions: BgpSession[] }) {
|
|||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{Object.entries(
|
{Object.entries(
|
||||||
sessions.filter(s => s.type === "eBGP").reduce<Record<number, number>>((acc, s) => {
|
sessions.filter(s => s.type === "eBGP").reduce<Record<number, number>>((acc, s) => {
|
||||||
acc[s.remoteAs] = (acc[s.remoteAs] ?? 0) + 1; return acc
|
if (s.remoteAs !== null) acc[s.remoteAs] = (acc[s.remoteAs] ?? 0) + 1; return acc
|
||||||
}, {})
|
}, {})
|
||||||
).sort((a, b) => b[1] - a[1]).map(([as, cnt]) => (
|
).sort((a, b) => b[1] - a[1]).map(([as, cnt]) => (
|
||||||
<div key={as} className="flex items-center justify-between text-[11px]">
|
<div key={as} className="flex items-center justify-between text-[11px]">
|
||||||
|
|||||||
@@ -19,3 +19,10 @@ assert.equal(invalid.prefixesRx, null)
|
|||||||
assert.equal(invalid.holdTime, null)
|
assert.equal(invalid.holdTime, null)
|
||||||
assert.equal(invalid.inputMessages, null)
|
assert.equal(invalid.inputMessages, null)
|
||||||
console.log("BGP parser: unknown/observed zero/timers/AFI, no UPDATE-prefix substitution, legacy preserved")
|
console.log("BGP parser: unknown/observed zero/timers/AFI, no UPDATE-prefix substitution, legacy preserved")
|
||||||
|
|
||||||
|
assert.equal(missing.remoteAs, null)
|
||||||
|
assert.equal(missing.localAs, null)
|
||||||
|
assert.equal(missing.type, "Unknown")
|
||||||
|
for (const value of ["0", "-1", "12bad", "4294967296"]) assert.equal(parseBgpCatalogSessions(server, [{ ".id": "*1", "remote.as": value }])[0].remoteAs, null)
|
||||||
|
assert.equal(parseBgpCatalogSessions(server, [{ ".id": "*1", "remote.as": "4294967295", "local.as": "4294967295" }])[0].type, "iBGP")
|
||||||
|
assert.equal(parseBgpCatalogSessions(server, [{ ".id": "*1", "remote.as": "64513", "local.as": "64512" }])[0].type, "eBGP")
|
||||||
@@ -137,9 +137,10 @@ export function parseBgpSessions(server: ServerRow, raw: RosBgpSession[]): BgpSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Каталог не заменяет отсутствующие показатели значениями по умолчанию старого API. */
|
/** Каталог не заменяет отсутствующие показатели значениями по умолчанию старого API. */
|
||||||
export type BgpCatalogSessionRead = Omit<BgpSessionRead, "holdTime" | "keepalive" | "prefixesRx" | "prefixesTx" | "inputMessages" | "outputMessages"> & {
|
export type BgpCatalogSessionRead = Omit<BgpSessionRead, "holdTime" | "keepalive" | "prefixesRx" | "prefixesTx" | "inputMessages" | "outputMessages" | "remoteAs" | "localAs" | "type"> & {
|
||||||
holdTime: number | null; keepalive: number | null; prefixesRx: number | null; prefixesTx: number | null;
|
holdTime: number | null; keepalive: number | null; prefixesRx: number | null; prefixesTx: number | null;
|
||||||
inputMessages: number | null; outputMessages: number | null; afi: string | null;
|
inputMessages: number | null; outputMessages: number | null; afi: string | null;
|
||||||
|
remoteAs: number | null; localAs: number | null; type: "eBGP" | "iBGP" | "Unknown";
|
||||||
}
|
}
|
||||||
function readCounter(value: string | undefined): number | null {
|
function readCounter(value: string | undefined): number | null {
|
||||||
if (value === undefined || !/^\d+$/.test(value.trim())) return null
|
if (value === undefined || !/^\d+$/.test(value.trim())) return null
|
||||||
@@ -154,7 +155,10 @@ function readTimer(value: string | undefined): number | null {
|
|||||||
export function parseBgpCatalogSessions(server: ServerRow, raw: RosBgpSession[]): BgpCatalogSessionRead[] {
|
export function parseBgpCatalogSessions(server: ServerRow, raw: RosBgpSession[]): BgpCatalogSessionRead[] {
|
||||||
return parseBgpSessions(server, raw).map((session, index) => {
|
return parseBgpSessions(server, raw).map((session, index) => {
|
||||||
const s = raw[index]
|
const s = raw[index]
|
||||||
|
const asn = (value: string | undefined) => { const n = readCounter(value); return n !== null && n > 0 && n <= 4294967295 ? n : null }
|
||||||
|
const remoteAs = asn(s["remote.as"]), localAs = asn(s["local.as"])
|
||||||
return { ...session,
|
return { ...session,
|
||||||
|
remoteAs, localAs, type: remoteAs === null || localAs === null ? "Unknown" : remoteAs === localAs ? "iBGP" : "eBGP",
|
||||||
state: s.established === "true" ? "Established" : s.state?.trim() ? detectState(s) : "Unknown",
|
state: s.established === "true" ? "Established" : s.state?.trim() ? detectState(s) : "Unknown",
|
||||||
holdTime: readTimer(s["hold-time"]) ?? readCounter(s["active-holdtime"]),
|
holdTime: readTimer(s["hold-time"]) ?? readCounter(s["active-holdtime"]),
|
||||||
keepalive: readTimer(s["keepalive-time"]),
|
keepalive: readTimer(s["keepalive-time"]),
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const STATE_STYLE: Record<BgpState, { bg: string; text: string; dot: string; lab
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TYPE_STYLE: Record<BgpType, { bg: string; text: string }> = {
|
const TYPE_STYLE: Record<BgpType, { bg: string; text: string }> = {
|
||||||
|
Unknown: { bg: "bg-muted", text: "text-muted-foreground" },
|
||||||
eBGP: { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
|
eBGP: { bg: "bg-blue-500/10", text: "text-blue-600 dark:text-blue-400" },
|
||||||
iBGP: { bg: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
|
iBGP: { bg: "bg-purple-500/10", text: "text-purple-600 dark:text-purple-400" },
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,7 @@ function TypeBadge({ type }: { type: BgpType }) {
|
|||||||
"border-current/20",
|
"border-current/20",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{type}
|
{type === "Unknown" ? "Тип неизвестен" : type}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -154,8 +155,8 @@ function BgpSessionsDataGrid({ sessions, emptyTitle, selectedSessionId, onSelect
|
|||||||
const as = row.original.remoteAs
|
const as = row.original.remoteAs
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5 font-mono">
|
<div className="flex items-center gap-1.5 font-mono">
|
||||||
<span>AS{as}</span>
|
<span>{as === null ? "AS неизвестна" : "AS" + as}</span>
|
||||||
{BGP_AS_NAMES[as] && (
|
{as !== null && BGP_AS_NAMES[as] && (
|
||||||
<span className="text-muted-foreground text-[10px]">{BGP_AS_NAMES[as]}</span>
|
<span className="text-muted-foreground text-[10px]">{BGP_AS_NAMES[as]}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,11 +26,18 @@
|
|||||||
| 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.5, BGP URL/свежесть; далее ASN/layout |
|
| 10 | Маршрутизация, диагностика, карта и терминал | В работе: 10.1–10.6, BGP ASN/type; далее фильтры/layout |
|
||||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
## Блок 10.6 — неизвестные AS и тип BGP (2026-09-30)
|
||||||
|
|
||||||
|
- Новый catalog parser возвращает null для отсутствующей/некорректной AS, проверяет диапазон 1–4294967295; тип Unknown при недостаточном контексте. Legacy parser и его consumers сохранены.
|
||||||
|
- Экран/таблица показывают «AS неизвестна» и «Тип неизвестен» вместо AS0/eBGP. Неизвестный тип не добавляется к iBGP/eBGP; сводка типов и их сравнение не выдаёт неполный набор за полный.
|
||||||
|
- Unit проверил отсутствие/невалидные значения/верхнюю границу, равные и разные AS. 15 E2E BGP/OSPF прошли, frontend/backend TypeScript и lint чистые.
|
||||||
|
- Далее — URL фильтров/поиска, доступные состояния и desktop/mobile BGP, затем OSPF. AFI показывается фактической строкой API; варианты фильтра AFI требуют согласования с таким представлением. Реальный RouterOS не проверен; локальный коммит без push.
|
||||||
|
|
||||||
## Блок 10.5 — URL, свежесть и источник BGP (2026-09-30)
|
## Блок 10.5 — URL, свежесть и источник BGP (2026-09-30)
|
||||||
|
|
||||||
- URL хранит server/session/tab, сохраняет сторонние параметры; раскрытие строки связано с session. Возврат и reload восстанавливают контекст. Недоступное устройство/сессия из ссылки показывают сообщение и явный переход, не подменяются всеми устройствами. Включён preserveSelection установленного rail: тест выявил сброс при пустом начальном inventory.
|
- URL хранит server/session/tab, сохраняет сторонние параметры; раскрытие строки связано с session. Возврат и reload восстанавливают контекст. Недоступное устройство/сессия из ссылки показывают сообщение и явный переход, не подменяются всеми устройствами. Включён preserveSelection установленного rail: тест выявил сброс при пустом начальном inventory.
|
||||||
|
|||||||
@@ -60,3 +60,7 @@
|
|||||||
## Блок 10.5 — BGP URL/свежесть
|
## Блок 10.5 — BGP URL/свежесть
|
||||||
|
|
||||||
server/session/tab и возврат/reload сохраняют контекст; недоступный объект не подменяется. Время успешного опроса каждого устройства не обновляется при отказе. 19 различных E2E/TypeScript/lint прошли. Далее — ASN/type, URL фильтров и layout.
|
server/session/tab и возврат/reload сохраняют контекст; недоступный объект не подменяется. Время успешного опроса каждого устройства не обновляется при отказе. 19 различных E2E/TypeScript/lint прошли. Далее — ASN/type, URL фильтров и layout.
|
||||||
|
|
||||||
|
## Блок 10.6 — BGP ASN/type
|
||||||
|
|
||||||
|
Отсутствующие/невалидные ASN и тип неизвестны; legacy parser сохранён. Unit/15 E2E/TypeScript/lint прошли. Далее — URL фильтров и layout.
|
||||||
+2
-2
@@ -5,8 +5,8 @@ const counter = z.number().int().nonnegative()
|
|||||||
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, localAs: counter,
|
name: z.string(), peerIp: z.string(), remoteAs: counter.nullable(), localAs: counter.nullable(),
|
||||||
localId: z.string(), remoteId: z.string(), state: z.string().trim().min(1), type: z.enum(["eBGP", "iBGP"]),
|
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(),
|
||||||
afi: z.string().nullable().optional(),
|
afi: z.string().nullable().optional(),
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
export type BgpState = "Established" | "Active" | "Idle" | "Connect" | "OpenSent" | "OpenConfirm" | "Unknown"
|
export type BgpState = "Established" | "Active" | "Idle" | "Connect" | "OpenSent" | "OpenConfirm" | "Unknown"
|
||||||
export type BgpType = "eBGP" | "iBGP"
|
export type BgpType = "eBGP" | "iBGP" | "Unknown"
|
||||||
export type BgpAfi = "IPv4 Unicast" | "IPv6 Unicast" | "VPNv4 Unicast"
|
export type BgpAfi = "IPv4 Unicast" | "IPv6 Unicast" | "VPNv4 Unicast"
|
||||||
|
|
||||||
export interface BgpSessionRow {
|
export interface BgpSessionRow {
|
||||||
@@ -8,8 +8,8 @@ export interface BgpSessionRow {
|
|||||||
serverLabel: string
|
serverLabel: string
|
||||||
serverSite: string
|
serverSite: string
|
||||||
peerIp: string
|
peerIp: string
|
||||||
remoteAs: number
|
remoteAs: number | null
|
||||||
localAs: number
|
localAs: number | null
|
||||||
routerId: string
|
routerId: string
|
||||||
description: string
|
description: string
|
||||||
state: BgpState
|
state: BgpState
|
||||||
|
|||||||
@@ -75,13 +75,15 @@ test("BGP: неизвестные поля и наблюдаемый ноль н
|
|||||||
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||||
await page.route("**/api/**", r => {
|
await page.route("**/api/**", r => {
|
||||||
const path = new URL(r.request().url()).pathname
|
const path = new URL(r.request().url()).pathname
|
||||||
const unknown = { ...session(1), state: "Неизвестное состояние RouterOS", holdTime: null, keepalive: null, prefixesRx: null, prefixesTx: null, inputMessages: null, outputMessages: null, afi: "ip,ipv6" }
|
const unknown = { ...session(1), state: "Неизвестное состояние RouterOS", remoteAs: null, localAs: null, type: "Unknown", holdTime: null, keepalive: null, prefixesRx: null, prefixesTx: null, inputMessages: null, outputMessages: null, afi: "ip,ipv6" }
|
||||||
return r.fulfill({ json: path === "/api/servers" ? [server(1), server(2)] : path === "/api/bgp/catalog" ? { sessions: [unknown, { ...session(2), prefixesRx: 0, prefixesTx: 0 }], failures: [] } : path === "/api/auth/config" ? { required: false } : {} })
|
return r.fulfill({ json: path === "/api/servers" ? [server(1), server(2)] : path === "/api/bgp/catalog" ? { sessions: [unknown, { ...session(2), prefixesRx: 0, prefixesTx: 0 }], failures: [] } : path === "/api/auth/config" ? { required: false } : {} })
|
||||||
})
|
})
|
||||||
await page.goto("/bgp")
|
await page.goto("/bgp")
|
||||||
const rows = page.locator("tr[data-row-id]")
|
const rows = page.locator("tr[data-row-id]")
|
||||||
await expect(rows).toHaveCount(2)
|
await expect(rows).toHaveCount(2)
|
||||||
await expect(rows.first()).toContainText("Неизвестно")
|
await expect(rows.first()).toContainText("Неизвестно")
|
||||||
|
await expect(rows.first()).toContainText("AS неизвестна")
|
||||||
|
await expect(rows.first()).toContainText("Тип неизвестен")
|
||||||
await expect(rows.last().locator("td").nth(7)).toHaveText("0")
|
await expect(rows.last().locator("td").nth(7)).toHaveText("0")
|
||||||
await rows.first().click()
|
await rows.first().click()
|
||||||
await expect(page.getByText("ip,ipv6", { exact: true })).toBeVisible()
|
await expect(page.getByText("ip,ipv6", { exact: true })).toBeVisible()
|
||||||
|
|||||||
Reference in new issue
Block a user