From 60b2e099f06ca2c6ce28e226816ced0c6fac1f82 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 10 Oct 2026 20:42:58 +0700 Subject: [PATCH] =?UTF-8?q?fix(ipsec):=20=D1=83=D1=87=D0=B8=D1=82=D1=8B?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B5=D1=81=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D1=81=D0=B5=D1=81=D1=81=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=B1=D0=B5=D0=B7=20=D0=BB=D0=BE=D0=B6=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=20=D0=BE=D1=84=D0=BB=D0=B0=D0=B9=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/services/ipsec-live.ts | 24 ++-- backend/src/services/ipsec-state.test.ts | 30 ++++- docs/ux-ui-external-acceptance.md | 19 +++- docs/ux-ui-plan.md | 4 +- docs/ux-ui-progress.md | 21 +++- docs/ux-ui-remaining-acceptance.md | 25 +++- docs/ux-ui-stage-12.md | 19 +++- docs/ux-ui-test-server.md | 21 +++- .../ipsec-multiple-sessions-2026-10-10.json | 107 ++++++++++++++++++ tests/e2e/ipsec-data.spec.ts | 26 +++++ 10 files changed, 272 insertions(+), 24 deletions(-) create mode 100644 docs/ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json diff --git a/backend/src/services/ipsec-live.ts b/backend/src/services/ipsec-live.ts index 6c2795c4..864b408d 100644 --- a/backend/src/services/ipsec-live.ts +++ b/backend/src/services/ipsec-live.ts @@ -248,17 +248,16 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { const shared = selectSharedIke2Identity(state.identities, ike2PeerNames) const sharedRosId = shared?.[".id"] ? String(shared[".id"]) : undefined - const activeByRemote = new Map() - const ambiguousRemoteIds = new Set() + const activeByRemote = new Map() for (const a of state.active) { if (a.state !== "established") continue const rid = String(a.id ?? a["remote-id"] ?? "").trim() if (rid) { - if (activeByRemote.has(rid)) ambiguousRemoteIds.add(rid) - else activeByRemote.set(rid, a) + const sessions = activeByRemote.get(rid) ?? [] + sessions.push(a) + activeByRemote.set(rid, sessions) } } - for (const rid of ambiguousRemoteIds) activeByRemote.delete(rid) const subjectCounts = new Map() const cnCounts = new Map() @@ -286,8 +285,8 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { } const out: IpsecClientDto[] = [] - const sessionClaims = new Map() - const claimSession = (active: RosIpsecActivePeer | undefined) => { + const sessionClaims = new Map() + const claimSession = (active: RosIpsecActivePeer[] | undefined) => { if (!active) return const claims = sessionClaims.get(active) ?? [] claims.push(out[out.length - 1]!) @@ -316,6 +315,8 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { const byCommonName = uniqueCn && !subjectCounts.has(cn) ? activeByRemote.get(cn) : undefined const active = bySubject ?? byCommonName ?? (identity?.["remote-id"] ? findActive(String(identity["remote-id"]).trim()) : undefined) + // Multiple established sessions confirm presence, but no single lease/uptime. + const single = active?.length === 1 ? active[0] : undefined out.push({ id: `${server.id}:${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`, rosId: `${identity ? "identity" : "cert"}:${String(identity?.[".id"] ?? cert[".id"] ?? certName)}`, @@ -333,8 +334,8 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { modeConfigName: mcName || undefined, peerName: (identity?.peer ?? "").trim() || undefined, online: Boolean(active), - activeAddress: active?.["dynamic-address"] || active?.["remote-address"] || active?.address || undefined, - activeSince: active?.uptime || active?.established || undefined, + activeAddress: single?.["dynamic-address"] || single?.["remote-address"] || single?.address || undefined, + activeSince: single?.uptime || single?.established || undefined, disabled: asBool(identity?.disabled), comment: comment || undefined, managed: isUser || isIpsecManagedComment(comment), @@ -352,6 +353,7 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { const mcName = (i["mode-config"] ?? "").trim() const remoteId = (i["remote-id"] ?? "").trim() const active = remoteId ? findActive(remoteId) : undefined + const single = active?.length === 1 ? active[0] : undefined out.push({ id: `${server.id}:identity:${rosId}`, rosId: `identity:${rosId}`, @@ -365,8 +367,8 @@ export function mapClients(state: IpsecServerState): IpsecClientDto[] { modeConfigName: mcName || undefined, peerName: (i.peer ?? "").trim() || undefined, online: Boolean(active), - activeAddress: active?.["dynamic-address"] || active?.["remote-address"] || active?.address || undefined, - activeSince: active?.uptime || active?.established || undefined, + activeAddress: single?.["dynamic-address"] || single?.["remote-address"] || single?.address || undefined, + activeSince: single?.uptime || single?.established || undefined, disabled: asBool(i.disabled), comment: i.comment || undefined, managed, diff --git a/backend/src/services/ipsec-state.test.ts b/backend/src/services/ipsec-state.test.ts index 2e8b6234..8f67afd1 100644 --- a/backend/src/services/ipsec-state.test.ts +++ b/backend/src/services/ipsec-state.test.ts @@ -122,7 +122,11 @@ state.certs = state.certs.filter(c => c[".id"] !== "*AMBIGUOUS") Object.assign(certificate, { organization: "Example, Ltd", unit: "QA+Operations" }) state.active[0]!.id = 'C=US, O=Example, Ltd, OU=QA+Operations, CN=QA, Team+VPN="client"\\Lab' state.active.push({ ...state.active[0]!, ".id": "*DUPSESSION", "dynamic-address": "198.18.79.251" }) -assert.equal(mapClients(state).find(c => c.certName === "client")?.online, false); checks++ +const multiCertificate = mapClients(state).find(c => c.certName === "client")! +assert.equal(multiCertificate.online, true) +assert.equal(multiCertificate.activeAddress, undefined) +assert.equal(multiCertificate.activeSince, undefined) +assert.equal(multiCertificate.sessionAmbiguous, undefined); checks++ state.active.pop() Object.assign(certificate, { state: "New York", locality: "Albany" }) state.active[0]!.id = 'C=US, S=New York, L=Albany, O=Example, Ltd, OU=QA+Operations, CN=QA, Team+VPN="client"\\Lab' @@ -169,4 +173,28 @@ const uniqueClients = mapClients(aliasesState) assert.equal(uniqueClients.find(c => c.kind === "cert")?.online, true) assert.equal(uniqueClients.find(c => c.kind === "psk")?.online, false) assert(uniqueClients.every(c => !c.sessionAmbiguous)); checks++ +// Repeated observed IDs are sessions of one principal, not proof of being offline. +aliasesState.active.push({ ...aliasesState.active[0]!, ".id": "*SECOND", "dynamic-address": "198.18.113.251", uptime: "2m" }) +for (const reverse of [false, true]) { + if (reverse) aliasesState.active.reverse() + const clients = mapClients(aliasesState) + const cert = clients.find(c => c.kind === "cert")! + assert.equal(cert.online, true) + assert.equal(cert.activeAddress, undefined) + assert.equal(cert.activeSince, undefined) + assert.equal(mapServerSummary(aliasesState, clients).clientsOnline, 1); checks++ +} +aliasesState.identities[1]!["remote-id"] = "key-id:shared.example" +assert(mapClients(aliasesState).every(c => c.sessionAmbiguous && !c.online && !c.activeAddress && !c.activeSince)); checks++ +aliasesState.certs = [] +aliasesState.identities = [aliasesState.identities[1]!] +const multiplePsk = mapClients(aliasesState)[0]! +assert.equal(multiplePsk.online, true) +assert.equal(multiplePsk.activeAddress, undefined) +assert.equal(multiplePsk.activeSince, undefined) +assert.equal(multiplePsk.sessionAmbiguous, undefined); checks++ +aliasesState.active[1]!.state = "dying" +assert.equal(mapClients(aliasesState)[0]?.activeAddress, aliasesState.active[0]!["dynamic-address"]); checks++ +aliasesState.active[0]!.state = "starting" +assert.equal(mapClients(aliasesState)[0]?.online, false); checks++ console.log(`ipsec-state: ${checks} checks passed`) diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 087dbdcd..a65b8c66 100644 --- a/docs/ux-ui-external-acceptance.md +++ b/docs/ux-ui-external-acceptance.md @@ -1,6 +1,6 @@ # UX/UI: протокол внешней приёмки 12.5 -Актуальное продолжение 2026-10-10: завершён12.5.112 — одна native сессия не подтверждает online нескольким клиентам; certificate/PSK aliases получают sessionAmbiguous, «Не определено» и сводку «—» вместо ложного нуля. Backend ux-alias112/frontend ux-alias112-version, обе версии1.0.0-test.alias112. State102/102, профильные проверки/build, E2E14 unique+2 enhanced reruns, live4/4, recovery original online и cleanup13 коллекций×2 passed. Следующий113 — duplicate native IDs/сертификат с несколькими identity refs; внешние Portal/Telegram ожидают настройки/разрешения.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.113 — несколько established сессий с одним ID больше не дают ложный офлайн; один клиент online без произвольного адреса/uptime, общие claims разных клиентов остаются «Не определено». State108/108, 8 профильных скриптов, backend build/ESLint, E2E16/16. Backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Два live reader/API и два browser smoke passed; множественные сессии проверены контролируемым снимком. Следующий114 — один сертификат с несколькими identity и безопасность выбора объекта для операций.12.5 открыт. IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md). @@ -1115,3 +1115,20 @@ Red-before state test остановился на ложном online; посл | --- | --- | | [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Native ID → claims → подтверждено либо «—» | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Контекст устройства → статус «Не определено» → пояснение → проверка Remote ID | + +## 12.5.113 — IPsec: несколько сессий одного клиента без ложного офлайн (2026-10-10) + +Исправлен mapper: ранее повторяющиеся established active-peers.id удалялись из индекса целиком, и даже единственный соответствующий клиент выглядел офлайн. Теперь индекс сохраняет группу сессий. Единственный клиент с совпадающим ID получает online=true; activeAddress/activeSince выдаются только при одной established сессии. При нескольких сессиях reader не выбирает произвольный lease/uptime. Claims считаются на группу: если её разделяют разные catalog clients, у них sessionAmbiguous и нет подтверждённого online/адреса, как в112. Начальные/завершающиеся сессии не подтверждают online. + +Изменены backend/src/services/ipsec-live.ts, ipsec-state.test.ts и tests/e2e/ipsec-data.spec.ts. Контракт и существующие компоненты ReUI не менялись: таблица уже показывает «онлайн» без адреса. Проверены certificate/PSK, перестановка native rows, конфликт aliases, переход несколько→одна→нет established сессий. State108/108; все8 профильных скриптов, backend build, ESLint прошли. E2E16 unique passed33.6s: два новых сценария1280/320 и прежняя профильная регрессия IPsec. + +На стенд собран/развёрнут только backend ux-sessions113/version1.0.0-test.sessions113. Source base cb6ec09/context build-alias112 плюс один явный override; source и compiled ipsec-live.js SHA256 совпали с локальными байтами. Label sessions113 не является Git SHA. Frontend ux-alias112-version/version1.0.0-test.alias112 сохранён, backend/PG healthy; у frontend нет healthcheck, подтверждены HTTP/browser. Rollback .env сохранён privately как pre-sessions113.env, прежний образ оставлен. + +Оба CHR прошли реальный fetchIpsecState, aggregate GET /api/ipsec вернул200 без failures. В собранном reader проверены контролируемые snapshots с повторяющимися ID и aliases на базе прочитанных состояний; это НЕ создание нескольких реальных IKE подключений. Каталог live пустой: browser smoke1280/320 проверил API, пустое состояние, отсутствие ошибки и overflow; pageerrors0/APIwrites0. Первый browser запуск получил ECONNREFUSED: прежний localhost туннель отсутствовал; восстановлен согласованный SSH forward3000/8000, повтор passed. RouterOS writes0, acceptance DBwrites0, тестовых сетевых объектов не создавалось; пароли прежние. + +Протокол: [ipsec-multiple-sessions](ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json). Следующий114 — один сертификат с несколькими identity refs: текущий last-reference-wins ещё не исправлен, нужно проверить представление строк и выбор target для мутаций. Одновременные реальные подключения с одним ID, внешние интеграции, client OS, полнаяPKI и итоговая общая регрессия остаются отдельной приёмкой. + +| Референс | Схема | +| --- | --- | +| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | ID → группа established → один клиент «онлайн» / разные клиенты «Не определено» | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Несколько сессий → один подтверждённый клиент → сводка без двойного счёта | diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 213535da..5b598382 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-10: завершён12.5.112 — одна native сессия не подтверждает online нескольким клиентам; certificate/PSK aliases получают sessionAmbiguous, «Не определено» и сводку «—» вместо ложного нуля. Backend ux-alias112/frontend ux-alias112-version, обе версии1.0.0-test.alias112. State102/102, профильные проверки/build, E2E14 unique+2 enhanced reruns, live4/4, recovery original online и cleanup13 коллекций×2 passed. Следующий113 — duplicate native IDs/сертификат с несколькими identity refs; внешние Portal/Telegram ожидают настройки/разрешения.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.113 — несколько established сессий с одним ID больше не дают ложный офлайн; один клиент online без произвольного адреса/uptime, общие claims разных клиентов остаются «Не определено». State108/108, 8 профильных скриптов, backend build/ESLint, E2E16/16. Backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Два live reader/API и два browser smoke passed; множественные сессии проверены контролируемым снимком. Следующий114 — один сертификат с несколькими identity и безопасность выбора объекта для операций.12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-10 -Актуальное продолжение 2026-10-10: завершён12.5.112 — одна native сессия не подтверждает online нескольким клиентам; certificate/PSK aliases получают sessionAmbiguous, «Не определено» и сводку «—» вместо ложного нуля. Backend ux-alias112/frontend ux-alias112-version, обе версии1.0.0-test.alias112. State102/102, профильные проверки/build, E2E14 unique+2 enhanced reruns, live4/4, recovery original online и cleanup13 коллекций×2 passed. Следующий113 — duplicate native IDs/сертификат с несколькими identity refs; внешние Portal/Telegram ожидают настройки/разрешения.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.113 — несколько established сессий с одним ID больше не дают ложный офлайн; один клиент online без произвольного адреса/uptime, общие claims разных клиентов остаются «Не определено». State108/108, 8 профильных скриптов, backend build/ESLint, E2E16/16. Backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Два live reader/API и два browser smoke passed; множественные сессии проверены контролируемым снимком. Следующий114 — один сертификат с несколькими identity и безопасность выбора объекта для операций.12.5 открыт. Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты. diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 352017cc..ae3850da 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-10: завершён12.5.112 — одна native сессия не подтверждает online нескольким клиентам; certificate/PSK aliases получают sessionAmbiguous, «Не определено» и сводку «—» вместо ложного нуля. Backend ux-alias112/frontend ux-alias112-version, обе версии1.0.0-test.alias112. State102/102, профильные проверки/build, E2E14 unique+2 enhanced reruns, live4/4, recovery original online и cleanup13 коллекций×2 passed. Следующий113 — duplicate native IDs/сертификат с несколькими identity refs; внешние Portal/Telegram ожидают настройки/разрешения.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.113 — несколько established сессий с одним ID больше не дают ложный офлайн; один клиент online без произвольного адреса/uptime, общие claims разных клиентов остаются «Не определено». State108/108, 8 профильных скриптов, backend build/ESLint, E2E16/16. Backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Два live reader/API и два browser smoke passed; множественные сессии проверены контролируемым снимком. Следующий114 — один сертификат с несколькими identity и безопасность выбора объекта для операций.12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -36,7 +36,24 @@ | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 | -| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–112 и ограничения ниже. Runtime backend ux-alias112/frontend ux-alias112-version. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–113 и ограничения ниже. Runtime backend ux-sessions113/frontend ux-alias112-version. | + +## 12.5.113 — IPsec: несколько сессий одного клиента без ложного офлайн (2026-10-10) + +Исправлен mapper: ранее повторяющиеся established active-peers.id удалялись из индекса целиком, и даже единственный соответствующий клиент выглядел офлайн. Теперь индекс сохраняет группу сессий. Единственный клиент с совпадающим ID получает online=true; activeAddress/activeSince выдаются только при одной established сессии. При нескольких сессиях reader не выбирает произвольный lease/uptime. Claims считаются на группу: если её разделяют разные catalog clients, у них sessionAmbiguous и нет подтверждённого online/адреса, как в112. Начальные/завершающиеся сессии не подтверждают online. + +Изменены backend/src/services/ipsec-live.ts, ipsec-state.test.ts и tests/e2e/ipsec-data.spec.ts. Контракт и существующие компоненты ReUI не менялись: таблица уже показывает «онлайн» без адреса. Проверены certificate/PSK, перестановка native rows, конфликт aliases, переход несколько→одна→нет established сессий. State108/108; все8 профильных скриптов, backend build, ESLint прошли. E2E16 unique passed33.6s: два новых сценария1280/320 и прежняя профильная регрессия IPsec. + +На стенд собран/развёрнут только backend ux-sessions113/version1.0.0-test.sessions113. Source base cb6ec09/context build-alias112 плюс один явный override; source и compiled ipsec-live.js SHA256 совпали с локальными байтами. Label sessions113 не является Git SHA. Frontend ux-alias112-version/version1.0.0-test.alias112 сохранён, backend/PG healthy; у frontend нет healthcheck, подтверждены HTTP/browser. Rollback .env сохранён privately как pre-sessions113.env, прежний образ оставлен. + +Оба CHR прошли реальный fetchIpsecState, aggregate GET /api/ipsec вернул200 без failures. В собранном reader проверены контролируемые snapshots с повторяющимися ID и aliases на базе прочитанных состояний; это НЕ создание нескольких реальных IKE подключений. Каталог live пустой: browser smoke1280/320 проверил API, пустое состояние, отсутствие ошибки и overflow; pageerrors0/APIwrites0. Первый browser запуск получил ECONNREFUSED: прежний localhost туннель отсутствовал; восстановлен согласованный SSH forward3000/8000, повтор passed. RouterOS writes0, acceptance DBwrites0, тестовых сетевых объектов не создавалось; пароли прежние. + +Протокол: [ipsec-multiple-sessions](ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json). Следующий114 — один сертификат с несколькими identity refs: текущий last-reference-wins ещё не исправлен, нужно проверить представление строк и выбор target для мутаций. Одновременные реальные подключения с одним ID, внешние интеграции, client OS, полнаяPKI и итоговая общая регрессия остаются отдельной приёмкой. + +| Референс | Схема | +| --- | --- | +| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | ID → группа established → один клиент «онлайн» / разные клиенты «Не определено» | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Несколько сессий → один подтверждённый клиент → сводка без двойного счёта | ## 12.5.112 — IPsec: неоднозначные certificate/PSK Remote ID и понятный статус (2026-10-10) diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md index 7f34fac9..7a87e29e 100644 --- a/docs/ux-ui-remaining-acceptance.md +++ b/docs/ux-ui-remaining-acceptance.md @@ -1,8 +1,8 @@ # Оставшаяся внешняя приёмка — 2026-10-10 -Это актуальная матрица после 12.5.112. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. +Это актуальная матрица после 12.5.113. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии. -Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-alias112`, frontend `ux-alias112-version`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. +Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-sessions113`, frontend `ux-alias112-version`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись. | Раздел | Подтверждено | Осталось / условие завершения | | --- | --- | --- | @@ -12,7 +12,7 @@ | GRE | Native lifecycle, metadata, live формы/меню/URL/Ping53–57; GRE используется в OSPF/BGP/IPsec последующих блоков; native IPv6 ULA inner109 | IPv6 outer и application IPv6 interface address lifecycle, независимые WAN, масштаб и длительное восстановление; принятие GRE не означает приёмку всех сценариев автоматического расписания | | WireGuard | Native handshake/data plane58, deletion60, live create/peer/disable/enable/delete62, mobile64, export/import67–74; native IPv6 ULA inner/allowedIps/handshake109 | Клиентские ОС и произвольные конфигурации, IPv6 outer/scale и application IPv6 interface address lifecycle, физический lost ACK и устойчивое восстановление; `.conf` с placeholders — шаблон, не принятый клиентский tunnel | | VXLAN | Собственная пара/private Ping/catalog/URL/export75–76; bridge/FDB/port loss/recovery107; IPv6 ULA inner по IPv4 outer108 | API приложения read-only: create/delete проверены native. IPv6 outer, масштаб/производительность, профильные mutation RBAC/concurrency отдельно не приняты | -| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112 | Клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput | +| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113 | Реальные одновременные sessions с одним ID, один сертификат с несколькими identity refs/target guards; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput | | OSPF/BFD | Real Full/own routes84–85, раздельные наблюдения BFD86, effective disabled/optimizer cost/partial/RBAC87, alternate path90 и CIDR91 | Sustained BFD-only gate не принят: в86 сосед мог вновь статьFull при доступныхHello. В90 переходный Ping18/40 с55% loss: бесшовность не принята. REST timing — наблюдаемая граница, не SLA; IPv6/scale/independent WAN открыты | | BGP | Established83, exact /32 exchange/API/UI/administrative withdrawal92, BFD primary GRE loss/alternate/recovery93 | Hold timer/TCP loss безBFD принят104; packet loss в переходе, разные внешние peers/WAN policy, IPv6/scale. Два логических пути используют тот же public WAN; physical redundancy не принята | | Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята | @@ -25,8 +25,25 @@ ## Следующая последовательность 1. **12.5.95 завершён:** [source/context/compiled audit](ux-ui/acceptance/deployment-source-audit-2026-10-10.json), production source drift не выявлен; нормализация и исключения явно указаны. -2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — duplicate native IDs/multiple identity refs. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение. +2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — repeated native ID groups исправлены на controlled snapshots; следующий114 — multiple identity refs/target guards. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение. 3. **Заключительный профильный контроль:** после последнего исправления собрать только затронутую часть, проверить её live и обновить матрицу. Общую регрессию выполнить один раз на согласованном runtime. 4. **Внешние зависимости:** Telegram/Portal/client OS/multi-WAN/scale проходят отдельную приёмку при наличии нужных аккаунтов, устройств и разрешений. Лабораторный результат двух CHR не закрывает их автоматически. Каждый блок: конкретный результат → профильные проверки → независимая сверка cleanup → датированная запись → локальный коммит без push. Не повторять уже принятые сценарии без изменения исходников или нового выявленного риска. + +## 12.5.113 — IPsec: несколько сессий одного клиента без ложного офлайн (2026-10-10) + +Исправлен mapper: ранее повторяющиеся established active-peers.id удалялись из индекса целиком, и даже единственный соответствующий клиент выглядел офлайн. Теперь индекс сохраняет группу сессий. Единственный клиент с совпадающим ID получает online=true; activeAddress/activeSince выдаются только при одной established сессии. При нескольких сессиях reader не выбирает произвольный lease/uptime. Claims считаются на группу: если её разделяют разные catalog clients, у них sessionAmbiguous и нет подтверждённого online/адреса, как в112. Начальные/завершающиеся сессии не подтверждают online. + +Изменены backend/src/services/ipsec-live.ts, ipsec-state.test.ts и tests/e2e/ipsec-data.spec.ts. Контракт и существующие компоненты ReUI не менялись: таблица уже показывает «онлайн» без адреса. Проверены certificate/PSK, перестановка native rows, конфликт aliases, переход несколько→одна→нет established сессий. State108/108; все8 профильных скриптов, backend build, ESLint прошли. E2E16 unique passed33.6s: два новых сценария1280/320 и прежняя профильная регрессия IPsec. + +На стенд собран/развёрнут только backend ux-sessions113/version1.0.0-test.sessions113. Source base cb6ec09/context build-alias112 плюс один явный override; source и compiled ipsec-live.js SHA256 совпали с локальными байтами. Label sessions113 не является Git SHA. Frontend ux-alias112-version/version1.0.0-test.alias112 сохранён, backend/PG healthy; у frontend нет healthcheck, подтверждены HTTP/browser. Rollback .env сохранён privately как pre-sessions113.env, прежний образ оставлен. + +Оба CHR прошли реальный fetchIpsecState, aggregate GET /api/ipsec вернул200 без failures. В собранном reader проверены контролируемые snapshots с повторяющимися ID и aliases на базе прочитанных состояний; это НЕ создание нескольких реальных IKE подключений. Каталог live пустой: browser smoke1280/320 проверил API, пустое состояние, отсутствие ошибки и overflow; pageerrors0/APIwrites0. Первый browser запуск получил ECONNREFUSED: прежний localhost туннель отсутствовал; восстановлен согласованный SSH forward3000/8000, повтор passed. RouterOS writes0, acceptance DBwrites0, тестовых сетевых объектов не создавалось; пароли прежние. + +Протокол: [ipsec-multiple-sessions](ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json). Следующий114 — один сертификат с несколькими identity refs: текущий last-reference-wins ещё не исправлен, нужно проверить представление строк и выбор target для мутаций. Одновременные реальные подключения с одним ID, внешние интеграции, client OS, полнаяPKI и итоговая общая регрессия остаются отдельной приёмкой. + +| Референс | Схема | +| --- | --- | +| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | ID → группа established → один клиент «онлайн» / разные клиенты «Не определено» | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Несколько сессий → один подтверждённый клиент → сводка без двойного счёта | diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index ce7e1492..87603388 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-10: завершён12.5.112 — одна native сессия не подтверждает online нескольким клиентам; certificate/PSK aliases получают sessionAmbiguous, «Не определено» и сводку «—» вместо ложного нуля. Backend ux-alias112/frontend ux-alias112-version, обе версии1.0.0-test.alias112. State102/102, профильные проверки/build, E2E14 unique+2 enhanced reruns, live4/4, recovery original online и cleanup13 коллекций×2 passed. Следующий113 — duplicate native IDs/сертификат с несколькими identity refs; внешние Portal/Telegram ожидают настройки/разрешения.12.5 открыт. +Актуальное продолжение 2026-10-10: завершён12.5.113 — несколько established сессий с одним ID больше не дают ложный офлайн; один клиент online без произвольного адреса/uptime, общие claims разных клиентов остаются «Не определено». State108/108, 8 профильных скриптов, backend build/ESLint, E2E16/16. Backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Два live reader/API и два browser smoke passed; множественные сессии проверены контролируемым снимком. Следующий114 — один сертификат с несколькими identity и безопасность выбора объекта для операций.12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -2420,3 +2420,20 @@ Red-before state test остановился на ложном online; посл | --- | --- | | [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Native ID → claims → подтверждено либо «—» | | [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Контекст устройства → статус «Не определено» → пояснение → проверка Remote ID | + +## 12.5.113 — IPsec: несколько сессий одного клиента без ложного офлайн (2026-10-10) + +Исправлен mapper: ранее повторяющиеся established active-peers.id удалялись из индекса целиком, и даже единственный соответствующий клиент выглядел офлайн. Теперь индекс сохраняет группу сессий. Единственный клиент с совпадающим ID получает online=true; activeAddress/activeSince выдаются только при одной established сессии. При нескольких сессиях reader не выбирает произвольный lease/uptime. Claims считаются на группу: если её разделяют разные catalog clients, у них sessionAmbiguous и нет подтверждённого online/адреса, как в112. Начальные/завершающиеся сессии не подтверждают online. + +Изменены backend/src/services/ipsec-live.ts, ipsec-state.test.ts и tests/e2e/ipsec-data.spec.ts. Контракт и существующие компоненты ReUI не менялись: таблица уже показывает «онлайн» без адреса. Проверены certificate/PSK, перестановка native rows, конфликт aliases, переход несколько→одна→нет established сессий. State108/108; все8 профильных скриптов, backend build, ESLint прошли. E2E16 unique passed33.6s: два новых сценария1280/320 и прежняя профильная регрессия IPsec. + +На стенд собран/развёрнут только backend ux-sessions113/version1.0.0-test.sessions113. Source base cb6ec09/context build-alias112 плюс один явный override; source и compiled ipsec-live.js SHA256 совпали с локальными байтами. Label sessions113 не является Git SHA. Frontend ux-alias112-version/version1.0.0-test.alias112 сохранён, backend/PG healthy; у frontend нет healthcheck, подтверждены HTTP/browser. Rollback .env сохранён privately как pre-sessions113.env, прежний образ оставлен. + +Оба CHR прошли реальный fetchIpsecState, aggregate GET /api/ipsec вернул200 без failures. В собранном reader проверены контролируемые snapshots с повторяющимися ID и aliases на базе прочитанных состояний; это НЕ создание нескольких реальных IKE подключений. Каталог live пустой: browser smoke1280/320 проверил API, пустое состояние, отсутствие ошибки и overflow; pageerrors0/APIwrites0. Первый browser запуск получил ECONNREFUSED: прежний localhost туннель отсутствовал; восстановлен согласованный SSH forward3000/8000, повтор passed. RouterOS writes0, acceptance DBwrites0, тестовых сетевых объектов не создавалось; пароли прежние. + +Протокол: [ipsec-multiple-sessions](ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json). Следующий114 — один сертификат с несколькими identity refs: текущий last-reference-wins ещё не исправлен, нужно проверить представление строк и выбор target для мутаций. Одновременные реальные подключения с одним ID, внешние интеграции, client OS, полнаяPKI и итоговая общая регрессия остаются отдельной приёмкой. + +| Референс | Схема | +| --- | --- | +| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | ID → группа established → один клиент «онлайн» / разные клиенты «Не определено» | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Несколько сессий → один подтверждённый клиент → сводка без двойного счёта | diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index 08566fbc..5637812c 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -1,6 +1,6 @@ # Тестовый сервер UX/UI — 2026-10-10 -Сервер161.104.46.222 предоставлен для временного тестирования. Backend ux-alias112/version1.0.0-test.alias112, frontend ux-alias112-version/version1.0.0-test.alias112; backend/PG healthy, frontend HTTP/browser passed. VPN107–109 и IPsec110–112: bridge/FDB/IPv6 inner и исправление online по DN/CN, explicit FQDN и unknown при shared ID приняты в границах журнала; собственные объекты удалены, baseline восстановлен; каталог flash CHR2 сохранён из-за неподтверждённого ownership. SSH/оба CHR доступны, пароли прежние. +Сервер161.104.46.222: backend ux-sessions113/version1.0.0-test.sessions113, frontend ux-alias112-version/version1.0.0-test.alias112. Backend/PG healthy, live API и browser smoke1280/320 passed. Оба CHR доступны;113 не менял RouterOS/пароли и не создавал объектов. Множественные сессии проверены контролируемым snapshot, не новыми одновременными туннелями. Следующий114 — несколько identity refs одного сертификата. | Параметр | Проверенное состояние | | --- | --- | @@ -14,7 +14,7 @@ | PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed | | Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data | | Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска | -| Образы приложения | mmapp-test/backend:ux-alias112 и mmapp-test/frontend:ux-alias112-version; запущены | +| Образы приложения | mmapp-test/backend:ux-sessions113 и mmapp-test/frontend:ux-alias112-version; запущены | Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example). @@ -846,3 +846,20 @@ Runtime backend ux-patch102/version1.0.0-test.patch102/image34fca84b8721…; fro Вопрос о наличии тестовых Portal/Telegram задан пользователю; ответа/разрешения на сообщение пока нет. Реальная приёмка этих веток не заменяется локальными fake sender/JWT checks35–49/89. Следующий107 требует тестовую Portal-конфигурацию/аккаунт и отдельный bot/chat; перед отправкой подготовить конкретное сообщение и получить прямое разрешение. Доступы кCHR/SSH этого разрешения не дают. Остальные clientOS/IPv6/WAN policy/scale/multi-instance/physical failure/durable retry bounds остаются в [матрице](ux-ui-remaining-acceptance.md). За окно сохранены14 local commits93–106, push0. Backend patch102/frontend a11y101, source904/compiled984 matches, main1207/1208+geometry delta5/5/context221/221/backend171+parser8 profiles/accessibility24local20live вточных границах99–105. ОбаCHR доступны, свои конфиги/DB fixtures очищены, credentials не менялись. Квота94% использовано перед записью; этап12.5 целиком открыт. Один локальный commit без push. + +## 12.5.113 — IPsec: несколько сессий одного клиента без ложного офлайн (2026-10-10) + +Исправлен mapper: ранее повторяющиеся established active-peers.id удалялись из индекса целиком, и даже единственный соответствующий клиент выглядел офлайн. Теперь индекс сохраняет группу сессий. Единственный клиент с совпадающим ID получает online=true; activeAddress/activeSince выдаются только при одной established сессии. При нескольких сессиях reader не выбирает произвольный lease/uptime. Claims считаются на группу: если её разделяют разные catalog clients, у них sessionAmbiguous и нет подтверждённого online/адреса, как в112. Начальные/завершающиеся сессии не подтверждают online. + +Изменены backend/src/services/ipsec-live.ts, ipsec-state.test.ts и tests/e2e/ipsec-data.spec.ts. Контракт и существующие компоненты ReUI не менялись: таблица уже показывает «онлайн» без адреса. Проверены certificate/PSK, перестановка native rows, конфликт aliases, переход несколько→одна→нет established сессий. State108/108; все8 профильных скриптов, backend build, ESLint прошли. E2E16 unique passed33.6s: два новых сценария1280/320 и прежняя профильная регрессия IPsec. + +На стенд собран/развёрнут только backend ux-sessions113/version1.0.0-test.sessions113. Source base cb6ec09/context build-alias112 плюс один явный override; source и compiled ipsec-live.js SHA256 совпали с локальными байтами. Label sessions113 не является Git SHA. Frontend ux-alias112-version/version1.0.0-test.alias112 сохранён, backend/PG healthy; у frontend нет healthcheck, подтверждены HTTP/browser. Rollback .env сохранён privately как pre-sessions113.env, прежний образ оставлен. + +Оба CHR прошли реальный fetchIpsecState, aggregate GET /api/ipsec вернул200 без failures. В собранном reader проверены контролируемые snapshots с повторяющимися ID и aliases на базе прочитанных состояний; это НЕ создание нескольких реальных IKE подключений. Каталог live пустой: browser smoke1280/320 проверил API, пустое состояние, отсутствие ошибки и overflow; pageerrors0/APIwrites0. Первый browser запуск получил ECONNREFUSED: прежний localhost туннель отсутствовал; восстановлен согласованный SSH forward3000/8000, повтор passed. RouterOS writes0, acceptance DBwrites0, тестовых сетевых объектов не создавалось; пароли прежние. + +Протокол: [ipsec-multiple-sessions](ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json). Следующий114 — один сертификат с несколькими identity refs: текущий last-reference-wins ещё не исправлен, нужно проверить представление строк и выбор target для мутаций. Одновременные реальные подключения с одним ID, внешние интеграции, client OS, полнаяPKI и итоговая общая регрессия остаются отдельной приёмкой. + +| Референс | Схема | +| --- | --- | +| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | ID → группа established → один клиент «онлайн» / разные клиенты «Не определено» | +| [ReUI Stats12](https://reui.io/preview/base/stats-12?ref=mcp) | Несколько сессий → один подтверждённый клиент → сводка без двойного счёта | diff --git a/docs/ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json b/docs/ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json new file mode 100644 index 00000000..411f4be3 --- /dev/null +++ b/docs/ux-ui/acceptance/ipsec-multiple-sessions-2026-10-10.json @@ -0,0 +1,107 @@ +{ + "date": "2026-10-10", + "block": "12.5.113", + "local": { + "stateChecks": 108, + "profileScripts": 8, + "backendBuildPassed": true, + "eslintPassed": true, + "e2eUnique": 16, + "e2eSeconds": 33.6 + }, + "deployment": { + "date": "2026-10-10", + "backendContext": "build-sessions113", + "baseContext": "build-alias112", + "baseCommit": "cb6ec09", + "override": "backend/src/services/ipsec-live.ts", + "sourceSha256": "3466ed611dfcb6fd8cd61224f0d95cabf08819cf05c7885988946ea0b9b2dfa4", + "containers": { + "backend": { + "imageTag": "mmapp-test/backend:ux-sessions113", + "imageId": "sha256:6357461efe59b7e8bc362a412e59bb07cad43f402e2a3b6853d059a4f97c53bd", + "state": "running", + "health": "healthy" + }, + "frontend": { + "imageTag": "mmapp-test/frontend:ux-alias112-version", + "imageId": "sha256:4bf4cad8b3c990a7259f84cf27fd6bb9ee34e3db904223b559c0559dbbe094a2", + "state": "running", + "health": null + }, + "postgres": { + "imageTag": "postgres:18-alpine", + "imageId": "sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873", + "state": "running", + "health": "healthy" + } + }, + "compiledSha256": "b366b9a5e93bd26b5463a342dafe9646dcd27e1a850e2cdd5c5a5cad914bd228" + }, + "sourceByteHashMatches": true, + "compiledByteHashMatches": true, + "smoke": { + "date": "2026-10-10", + "block": "12.5.113", + "routerWrites": 0, + "databaseWrites": 0, + "live": [ + { + "serverId": 1, + "host": "194.226.121.150", + "readerPassed": true, + "clients": 0 + }, + { + "serverId": 2, + "host": "104.165.244.7", + "readerPassed": true, + "clients": 0 + } + ], + "controlledSnapshot": [ + { + "serverId": 1, + "onePrincipalMultipleSessionsOnline": true, + "noArbitraryAddressOrUptime": true, + "sharedPrincipalUnknown": true + }, + { + "serverId": 2, + "onePrincipalMultipleSessionsOnline": true, + "noArbitraryAddressOrUptime": true, + "sharedPrincipalUnknown": true + } + ], + "aggregateApiPassed": true + }, + "browser": { + "date": "2026-10-10", + "block": "12.5.113", + "cases": [ + { + "width": 1280, + "liveApiPassed": true, + "emptyCatalogVisible": true, + "noOverflow": true + }, + { + "width": 320, + "liveApiPassed": true, + "emptyCatalogVisible": true, + "noOverflow": true + } + ], + "pageErrors": [], + "apiWrites": [], + "bounds": "Live empty catalog smoke only; multiple-session UI covered by HTTP fixtures, not simultaneous native tunnels" + }, + "initialBrowserAttempt": "ECONNREFUSED on localhost:3000; restored authorized SSH tunnel then both cases passed", + "bounds": [ + "Multiple native sessions are injected snapshots, not newly established simultaneous IKE connections", + "Live native readers and aggregate API passed for both CHR; catalog empty", + "One certificate referenced by multiple identities remains next block", + "No RouterOS configuration or credentials modified; no database writes by acceptance helper", + "No full regression or new data-plane/throughput acceptance" + ] +} diff --git a/tests/e2e/ipsec-data.spec.ts b/tests/e2e/ipsec-data.spec.ts index 8ab1c9d2..6ce1665f 100644 --- a/tests/e2e/ipsec-data.spec.ts +++ b/tests/e2e/ipsec-data.spec.ts @@ -6,6 +6,32 @@ const servers = [1, 2].map(id => ({ id, name: "Роутер " + id, host: "192.0 const full = { servers: [summary("1"), summary("2")], clients: [client("1"), client("2")] } for (const width of [1280, 320]) { + test(`IPsec: несколько сессий одного клиента без произвольного адреса ${width}px`, async ({ page }) => { + await page.setViewportSize({ width, height: 1000 }) + let phase = 0 + 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/ipsec" ? { + servers: [{ ...summary("1"), clientsOnline: phase === 2 ? 0 : 1 }], + clients: [{ ...client("1"), online: phase !== 2, ...(phase === 1 ? { activeAddress: "198.18.113.250", activeSince: "1m" } : {}) }], + } : path === "/api/servers" ? servers : path === "/api/auth/config" ? { required: false } : {} }) + }) + await page.goto("/ipsec?server=1") + const rows = page.locator("tr[data-row-id]") + await expect(rows.getByText("онлайн", { exact: true })).toHaveCount(1) + await expect(rows.getByText("офлайн", { exact: true })).toHaveCount(0) + await expect(rows.getByText("Не определено", { exact: true })).toHaveCount(0) + await expect(page.getByLabel("Сводка IPsec")).not.toContainText("—") + expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + phase = 1 + await page.getByRole("button", { name: "Обновить", exact: true }).click() + await expect(rows.getByText("198.18.113.250", { exact: true })).toHaveCount(1) + phase = 2 + await page.getByRole("button", { name: "Обновить", exact: true }).click() + await expect(rows.getByText("офлайн", { exact: true })).toHaveCount(1) + await expect(rows.getByText("198.18.113.250", { exact: true })).toHaveCount(0) + }) test(`IPsec: неоднозначная сессия и восстановление статуса ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 1000 }) let phase = 0