From 6a46fb1e40291024202455460dfc233f05e9e5d5 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Thu, 8 Oct 2026 22:32:57 +0700 Subject: [PATCH] =?UTF-8?q?fix(wireguard):=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D1=8F=D1=82=D1=8C=20=D0=BC=D0=B5=D0=BD=D1=8E=20?= =?UTF-8?q?=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B9=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B8=D1=81=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/data-grids/wireguard-data-grid.tsx | 169 ++++++++------- docs/ux-ui-external-acceptance.md | 14 +- docs/ux-ui-plan.md | 4 +- docs/ux-ui-progress.md | 16 +- docs/ux-ui-stage-12.md | 14 +- docs/ux-ui-test-server.md | 12 ++ .../wg-ui-db-controls-2026-10-08.json | 16 ++ ...wg-ui-final-native-cleanup-2026-10-08.json | 36 ++++ ...wg-ui-first-attempt-native-2026-10-08.json | 199 ++++++++++++++++++ .../wg-ui-second-api-cleanup-2026-10-08.json | 19 ++ ...g-ui-second-observer-error-2026-10-08.json | 17 ++ .../wg-ui-create-server2-320-2026-10-08.png | Bin 0 -> 60983 bytes tests/e2e/wireguard-menu-refresh.spec.ts | 35 +++ 13 files changed, 466 insertions(+), 85 deletions(-) create mode 100644 docs/ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json create mode 100644 docs/ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json create mode 100644 docs/ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json create mode 100644 docs/ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json create mode 100644 docs/ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json create mode 100644 docs/ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png create mode 100644 tests/e2e/wireguard-menu-refresh.spec.ts diff --git a/components/data-grids/wireguard-data-grid.tsx b/components/data-grids/wireguard-data-grid.tsx index 68cb633d..08385aff 100644 --- a/components/data-grids/wireguard-data-grid.tsx +++ b/components/data-grids/wireguard-data-grid.tsx @@ -2,7 +2,7 @@ import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid" -import { useEffect, useMemo, type ReactNode } from "react" +import { createContext, useContext, useEffect, useMemo, type ReactNode } from "react" import { type ColumnDef, useTable, @@ -58,6 +58,76 @@ interface WireguardDataGridProps { onExportPeer?: (iface: WgIfaceWithServer, peerId: string) => void } +type WireguardActions = Pick +const WireguardActionsContext = createContext(null) + +// Preserve menu state while callbacks and the current row data update. +function WireguardActionsCell({ row }: { row: { original: WgIfaceWithServer } }) { + const actions = useContext(WireguardActionsContext) + if (!actions) throw new Error("WireGuard actions context is missing") + const { onExport, onAddPeer, onToggle, toggleBusy, onDelete } = actions + const iface = row.original + return ( +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > + + e.stopPropagation()} + aria-label={`Действия: ${iface.name}`} + > + + + } + /> + + onExport(iface)}> + + Экспорт + + {onAddPeer && ( + onAddPeer(iface)}> + + Добавить пира + + )} + {onToggle && ( + <> + + onToggle(iface)}> + + {iface.enabled ? "Отключить" : "Включить"} + + + )} + {onDelete && ( + <> + + onDelete(iface)}> + + Удалить + + + )} + + +
+ ) +} + function WireguardDataGrid({ interfaces, compactServer = false, @@ -198,68 +268,7 @@ function WireguardDataGrid({ { id: "actions", header: () => Действия, - cell: ({ row }) => { - const iface = row.original - return ( -
e.stopPropagation()} - onKeyDown={(e) => e.stopPropagation()} - > - - e.stopPropagation()} - aria-label={`Действия: ${iface.name}`} - > - - - } - /> - - onExport(iface)}> - - Экспорт - - {onAddPeer && ( - onAddPeer(iface)}> - - Добавить пира - - )} - {onToggle && ( - <> - - onToggle(iface)}> - - {iface.enabled ? "Отключить" : "Включить"} - - - )} - {onDelete && ( - <> - - onDelete(iface)}> - - Удалить - - - )} - - -
- ) - }, + cell: WireguardActionsCell, enableSorting: false, size: 72, meta: { @@ -268,7 +277,7 @@ function WireguardDataGrid({ }, }, ], - [compactServer, onExport, onAddPeer, onToggle, toggleBusy, onDelete, onDeletePeer, onExportPeer], + [compactServer, onDeletePeer, onExportPeer], ) const table = useTable({ @@ -302,21 +311,23 @@ function WireguardDataGrid({ } return ( - 50 || pageSize !== 50} - tableLayout={{ dense: true, columnsPinnable: true }} - onRowClick={(row) => table.getRow(JSON.stringify([row.serverId, row.id])).toggleExpanded()} - tableClassNames={{ - headerRow: "border-b border-border", - bodyRow: cn("group/row", "[&[data-disabled=true]]:opacity-50"), - }} - > - - - - + + 50 || pageSize !== 50} + tableLayout={{ dense: true, columnsPinnable: true }} + onRowClick={(row) => table.getRow(JSON.stringify([row.serverId, row.id])).toggleExpanded()} + tableClassNames={{ + headerRow: "border-b border-border", + bodyRow: cn("group/row", "[&[data-disabled=true]]:opacity-50"), + }} + > + + + + + ) } diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md index 6c44d6dd..23275bc9 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-08: backend/frontend3722fad, backend healthy. Мобильная GRE форма и native сохранение на обоих CHR приняты в12.5.57. WireGuard handshake/короткий Ping/native API/read-only UI приняты в границах12.5.58; 12.5.59 воспроизвёл оставшиеся peers/IP; 12.5.60 развернул исправление и принял обычное удаление own WG интерфейса с peer/IPv4 адресом на обоих CHR. Устойчивость меню GRE при live refresh на обоих CHR принята в12.5.55. GRE peer metadata и реальный lifecycle/API/UI/двусторонний Ping подтверждены в12.5.53. Browser retry/checkpoint на реальном HTTP/PG принят в границах12.5.43–45; Telegram sender responses подменены, реальная доставка не проверена. PG-1/2/3/4/5 приняты в описанных границах, PG-6 integrated для одного isolated instance, PG-7 real JWT/processcounts и browser restore passed. PG-8 real PG/native и browser unknown→history done приняты с fault injection после настоящего COMMIT. ROS-8/9 real API/SQL/native приняты в границах12.5.23; browser pair/duration change и delete приняты в12.5.24. ROS-2 authentication failure принят в12.5.25. PortalSSO, физический TCP disconnect, мультиэкземпляры и остальные ROS/Q/интеграции остаются открытыми. [Паспорт](ux-ui-test-server.md), [журнал](ux-ui-progress.md). +Статус на2026-10-08: backend/frontend3722fad, backend healthy. Мобильная GRE форма и native сохранение на обоих CHR приняты в12.5.57. WireGuard handshake/короткий Ping/native API/read-only UI приняты в границах12.5.58; 12.5.59 воспроизвёл оставшиеся peers/IP; 12.5.60 развернул исправление и принял обычное удаление own WG интерфейса с peer/IPv4 адресом на обоих CHR. UI создание WG принято в границах12.5.61, WireGuard toggle не завершён; локальная устойчивость его меню ещё не развёрнута. Устойчивость меню GRE при live refresh на обоих CHR принята в12.5.55. GRE peer metadata и реальный lifecycle/API/UI/двусторонний Ping подтверждены в12.5.53. Browser retry/checkpoint на реальном HTTP/PG принят в границах12.5.43–45; Telegram sender responses подменены, реальная доставка не проверена. PG-1/2/3/4/5 приняты в описанных границах, PG-6 integrated для одного isolated instance, PG-7 real JWT/processcounts и browser restore passed. PG-8 real PG/native и browser unknown→history done приняты с fault injection после настоящего COMMIT. ROS-8/9 real API/SQL/native приняты в границах12.5.23; browser pair/duration change и delete приняты в12.5.24. ROS-2 authentication failure принят в12.5.25. PortalSSO, физический TCP disconnect, мультиэкземпляры и остальные ROS/Q/интеграции остаются открытыми. [Паспорт](ux-ui-test-server.md), [журнал](ux-ui-progress.md). ## Паспорт стенда @@ -453,3 +453,15 @@ DELETE200/200, собственные GRE/IP/connected routes удалены; в Итоговый harness exit0: afterBrowserDelete пуст на обеих сторонах, выбранные WG/peers/IP/routes/IPv4 Firewall поля совпали с baseline. Fallback не понадобился для peers/IP/interfaces; ephemeral контейнер удалён, остались три основные Compose-службы. WireGuard revisions намеренно сохранены как история операций. [Smoke](ux-ui/acceptance/deploy-browser-3722fad-2026-10-08.json): dashboard/servers/uptime/settings/filters/alerts200,320px fit, JS/API errors0. Приняты успешное обычное удаление own интерфейса с IPv4 адресом и пиром и соответствующий UI результат. Partial failures/read errors/сохранение чужих WG объектов проверены локальными тестами59; на этом стенде такие отказы не инжектировались. IPv6 lifecycle, concurrency/multi-instance, throughput, Telegram/Portal и остальные внешние ограничения остаются открытыми. Следующий12.5.61 — реальные WireGuard действия через UI: создание интерфейса и пира, переключение и очистка собственной пары; ключи не публиковать. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.61 — UI создание WireGuard и локальное исправление нестабильного меню (2026-10-08) + +[Первый native observer](ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json): собственная пара mm-acceptance-wg-ui-1791472667527, свободные UDP51879/198.18.255.240/30, интерфейсы и пиры созданы только через production формы приложения. Observer не создавал конфигурацию. Оба интерфейса/IP/native key generation и поля peers/endpoint/allowed/keepalive/comment подтвердились, native peer key association и первый двусторонний Ping3/3 прошли. Публичные ключи переданы в форму в памяти, ключи не включены в отчёты. Создание интерфейса наCHR1 выполнено наПК, наCHR2 при320px. [Мобильная форма](ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png) сохранена без ключей и визуально просмотрена; этот снимок второй попытки, не доказательство переключения. + +Полный сценарий НЕ завершён: первая попытка не получила PATCH переключения; вторая, mm-acceptance-wg-ui-1791472879650, после создания интерфейсов остановилась на Добавить пира. Playwright зафиксировал unstable→detached DOM menuitem при перерисовке. Callback props пересоздавали columns/inline actions cell, React сбрасывал Base UI menu. Временные browser harness имеют эти ограничения; успешный финальный browser report не создавался и не заявляется. + +Локально components/data-grids/wireguard-data-grid.tsx адаптирован по уже принятому GRE образцу: стабильный WireguardActionsCell и типизированный context передают свежие onExport/onAddPeer/onToggle/onDelete/toggleBusy. Данные строки остаются текущими; composition, styles, row key и installed ReUI APIs сохранены. Tests/e2e/wireguard-menu-refresh.spec.ts воспроизвёл исчезнувшее меню на прежнем коде; после исправления меню переживает pending refresh, показывает новое enabled state и вызывает PATCH с актуальным disabled=false. Вместе с wireguard-tables.spec.ts —6/6 passed. ESLint и scoped TypeScript passed. Первый TypeScript запуск совпал с генерацией .next/dev/types и получил временные syntax errors; повтор после остановки Next прошёл. ReUI checklist проверен, общие primitives не менялись. + +Первая пара очищена observer, выбранный baseline полностью совпал. Во второй попытке [observer получил401](ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json) на завершающем native чтении до cleanup; причина не установлена, пароли не менялись. Повторное чтение через приложение успешно. [Штатный API cleanup](ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json) проверил точные name/ownership comment и удалил оба own интерфейса DELETE200/200. [Независимая native проверка](ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json) подтвердила отсутствие own WG/peers/IP/routes на обоих CHR, total IPv4 addresses1/2 и routes4/5; WG interfaces/peers0. Второй baseline был только в памяти и потерян при выходе observer: точное равенство его baseline не заявляется. [DB controls](ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json) servers2/speedHistory9/retention14/migrations9 совпали. Эфемерные контейнеры завершены, остались три основные службы, backend healthy. Audit revisions намеренно сохранены. + +Runtime backend/frontend3722fad, локальное меню ещё не развёрнуто. Следующий12.5.62 — frontend Docker deploy и полный UI create interface/peer→disable/enable→повторный handshake/Ping→delete на новой реальной паре, без повторных открытий меню. Перед повтором observer должен сохранить приватный baseline на сервере и продолжать независимую cleanup каждого устройства при ошибке финального state read; запуск browser только после подтверждения готовности нового observer. Переключение, экспорт/import, throughput/RBAC и прочие внешние ограничения остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 827091f8..f799780e 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -1,12 +1,12 @@ # План реализации UX/UI по пятичасовым окнам -Актуальное продолжение 2026-10-08: 12.5.60 развернул backend/frontend3722fad и подтвердил исправление удаления WG на новой реальной паре CHR: обычный UI DELETE200/200 удалил интерфейсы, peers и IPv4 addresses без предварительной очистки. Native состояние проверено до fallback, выбранный baseline и DB controls совпали, backend healthy; production builds/TypeScript и smoke6 passed. Следующий12.5.61 — WireGuard через UI: создание интерфейса/пира, переключение и очистка собственной реальной пары; секреты не публиковать. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.61 подтвердил UI создание WireGuard interfaces/peers, native поля, handshake и первый Ping; переключение не завершено из-за remount меню (DOM detached). Локально WireGuard actions вынесен в стабильный renderer/context, red→green regression и profile6/6/TypeScript/ESLint passed. Обе попытки own объектов очищены, native отсутствие остатков и DB controls подтверждены; transient observer401/recovery отражены отдельно. Runtime backend/frontend3722fad остаётся прежним. Следующий12.5.62 — frontend deploy и полный UI create/peer/toggle/Ping/delete без повторного открытия меню; observer cleanup защитить от ошибок завершающего чтения. Этап12.5 открыт. Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-08. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат. ## Актуальная точка продолжения — 2026-10-08 -Этапы0–11 завершены в описанных локальных границах. Этап12 открыт. На тестовом сервере161.104.46.222 работают backend/frontend3722fad и два CHR. Внешние проверки подтвердили PG backup/restore/rollback, остановку фоновой работы для одного экземпляра, API JWT/RBAC и браузерные состояния восстановления. Native TCP/UDP speed, история и UI проверены в границах журнала; доступ ко второму CHR восстановлен пользователем. Очередь Telegram проверена на реальном PG: dedupe, конкурентный claim, retry/version, ошибки сохранения и неопределённый исход; ответы отправителя подменялись, реальной доставки это не доказывает. Блоки12.5.43–45 подтвердили browser retry/checkpoint и развернули понятное пояснение HTTP500. 12.5.46 подтвердил browser JWT/RBAC очереди на реальном HTTP/PG; 12.5.47 подтвердил изоляцию checkpoint при смене двух реальных HTTP/PG источников в конфигурации local; 12.5.48 подтвердил scoped axe/клавиатуру в обеих темах и320px; 12.5.49 собрал Q-матрицу и подтвердил отсутствие настройки Telegram; 12.5.50 подтвердил GET-only инвентаризацию: VPN/BGP/OSPF пусты, роли home-router/home-router; По уточнению пользователя12.5.51 создал реальные GRE туннели и подтвердил lifecycle/связность; обнаружена потеря peer IP, 12.5.52 локально исправил сохранение peer metadata, профильные проверки passed; 12.5.53 развернул исправление и подтвердил его на реальной GRE паре/API/UI; 12.5.54 локально исправил устойчивость меню GRE, профильные проверки passed; 12.5.55 развернул frontend731803b и подтвердил устойчивость меню на реальной GRE паре; 12.5.56 локально адаптировал внутренние IP поля GRE для320px, обе темы и профильные проверки passed; 12.5.57 развернул frontendfd521f6 и подтвердил мобильную GRE форму на реальной паре CHR; 12.5.58 подтвердил настоящую WireGuard пару, handshake/Ping/native/UI и финальную очистку; 12.5.59 подтвердил оставшиеся peers/IP после удаления WG и локально исправил backend; 12.5.60 развернул backend/frontend3722fad и подтвердил полное обычное удаление WG; следующий12.5.61 — WireGuard UI создание/переключение собственной реальной пары. Реальная Telegram доставка, Portal SSO, физический TCP disconnect, multi-instance и остальные профильные сетевые сценарии остаются открытыми. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) содержат доказательства и ограничения. +Этапы0–11 завершены в описанных локальных границах. Этап12 открыт. На тестовом сервере161.104.46.222 работают backend/frontend3722fad и два CHR. Внешние проверки подтвердили PG backup/restore/rollback, остановку фоновой работы для одного экземпляра, API JWT/RBAC и браузерные состояния восстановления. Native TCP/UDP speed, история и UI проверены в границах журнала; доступ ко второму CHR восстановлен пользователем. Очередь Telegram проверена на реальном PG: dedupe, конкурентный claim, retry/version, ошибки сохранения и неопределённый исход; ответы отправителя подменялись, реальной доставки это не доказывает. Блоки12.5.43–45 подтвердили browser retry/checkpoint и развернули понятное пояснение HTTP500. 12.5.46 подтвердил browser JWT/RBAC очереди на реальном HTTP/PG; 12.5.47 подтвердил изоляцию checkpoint при смене двух реальных HTTP/PG источников в конфигурации local; 12.5.48 подтвердил scoped axe/клавиатуру в обеих темах и320px; 12.5.49 собрал Q-матрицу и подтвердил отсутствие настройки Telegram; 12.5.50 подтвердил GET-only инвентаризацию: VPN/BGP/OSPF пусты, роли home-router/home-router; По уточнению пользователя12.5.51 создал реальные GRE туннели и подтвердил lifecycle/связность; обнаружена потеря peer IP, 12.5.52 локально исправил сохранение peer metadata, профильные проверки passed; 12.5.53 развернул исправление и подтвердил его на реальной GRE паре/API/UI; 12.5.54 локально исправил устойчивость меню GRE, профильные проверки passed; 12.5.55 развернул frontend731803b и подтвердил устойчивость меню на реальной GRE паре; 12.5.56 локально адаптировал внутренние IP поля GRE для320px, обе темы и профильные проверки passed; 12.5.57 развернул frontendfd521f6 и подтвердил мобильную GRE форму на реальной паре CHR; 12.5.58 подтвердил настоящую WireGuard пару, handshake/Ping/native/UI и финальную очистку; 12.5.59 подтвердил оставшиеся peers/IP после удаления WG и локально исправил backend; 12.5.60 развернул backend/frontend3722fad и подтвердил полное обычное удаление WG; 12.5.61 подтвердил UI создание WG и локально исправил remount меню; переключение осталось открытым. Следующий12.5.62 — frontend deploy и полный UI lifecycle с независимой защищённой очисткой. Реальная Telegram доставка, Portal SSO, физический TCP disconnect, multi-instance и остальные профильные сетевые сценарии остаются открытыми. [Протокол](ux-ui-external-acceptance.md) и [журнал](ux-ui-stage-12.md) содержат доказательства и ограничения. Выполненные внешние сценарии не заменяют приёмку оставшихся интеграций и условий эксплуатации. Датированные продолжения ниже — история; точный следующий шаг указан в начале [журнала](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index c84e546a..ad7c4bb9 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -1,6 +1,6 @@ # UX/UI: журнал реализации -Актуальное продолжение 2026-10-08: 12.5.60 развернул backend/frontend3722fad и подтвердил исправление удаления WG на новой реальной паре CHR: обычный UI DELETE200/200 удалил интерфейсы, peers и IPv4 addresses без предварительной очистки. Native состояние проверено до fallback, выбранный baseline и DB controls совпали, backend healthy; production builds/TypeScript и smoke6 passed. Следующий12.5.61 — WireGuard через UI: создание интерфейса/пира, переключение и очистка собственной реальной пары; секреты не публиковать. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.61 подтвердил UI создание WireGuard interfaces/peers, native поля, handshake и первый Ping; переключение не завершено из-за remount меню (DOM detached). Локально WireGuard actions вынесен в стабильный renderer/context, red→green regression и profile6/6/TypeScript/ESLint passed. Обе попытки own объектов очищены, native отсутствие остатков и DB controls подтверждены; transient observer401/recovery отражены отдельно. Runtime backend/frontend3722fad остаётся прежним. Следующий12.5.62 — frontend deploy и полный UI create/peer/toggle/Ping/delete без повторного открытия меню; observer cleanup защитить от ошибок завершающего чтения. Этап12.5 открыт. Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md). @@ -34,7 +34,19 @@ | 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–60 и ограничения ниже. Runtime backend/frontend3722fad. | +| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–61 и ограничения ниже. Runtime backend/frontend3722fad. | + +## 12.5.61 — UI создание WireGuard и локальное исправление нестабильного меню (2026-10-08) + +[Первый native observer](ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json): собственная пара mm-acceptance-wg-ui-1791472667527, свободные UDP51879/198.18.255.240/30, интерфейсы и пиры созданы только через production формы приложения. Observer не создавал конфигурацию. Оба интерфейса/IP/native key generation и поля peers/endpoint/allowed/keepalive/comment подтвердились, native peer key association и первый двусторонний Ping3/3 прошли. Публичные ключи переданы в форму в памяти, ключи не включены в отчёты. Создание интерфейса наCHR1 выполнено наПК, наCHR2 при320px. [Мобильная форма](ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png) сохранена без ключей и визуально просмотрена; этот снимок второй попытки, не доказательство переключения. + +Полный сценарий НЕ завершён: первая попытка не получила PATCH переключения; вторая, mm-acceptance-wg-ui-1791472879650, после создания интерфейсов остановилась на Добавить пира. Playwright зафиксировал unstable→detached DOM menuitem при перерисовке. Callback props пересоздавали columns/inline actions cell, React сбрасывал Base UI menu. Временные browser harness имеют эти ограничения; успешный финальный browser report не создавался и не заявляется. + +Локально components/data-grids/wireguard-data-grid.tsx адаптирован по уже принятому GRE образцу: стабильный WireguardActionsCell и типизированный context передают свежие onExport/onAddPeer/onToggle/onDelete/toggleBusy. Данные строки остаются текущими; composition, styles, row key и installed ReUI APIs сохранены. Tests/e2e/wireguard-menu-refresh.spec.ts воспроизвёл исчезнувшее меню на прежнем коде; после исправления меню переживает pending refresh, показывает новое enabled state и вызывает PATCH с актуальным disabled=false. Вместе с wireguard-tables.spec.ts —6/6 passed. ESLint и scoped TypeScript passed. Первый TypeScript запуск совпал с генерацией .next/dev/types и получил временные syntax errors; повтор после остановки Next прошёл. ReUI checklist проверен, общие primitives не менялись. + +Первая пара очищена observer, выбранный baseline полностью совпал. Во второй попытке [observer получил401](ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json) на завершающем native чтении до cleanup; причина не установлена, пароли не менялись. Повторное чтение через приложение успешно. [Штатный API cleanup](ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json) проверил точные name/ownership comment и удалил оба own интерфейса DELETE200/200. [Независимая native проверка](ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json) подтвердила отсутствие own WG/peers/IP/routes на обоих CHR, total IPv4 addresses1/2 и routes4/5; WG interfaces/peers0. Второй baseline был только в памяти и потерян при выходе observer: точное равенство его baseline не заявляется. [DB controls](ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json) servers2/speedHistory9/retention14/migrations9 совпали. Эфемерные контейнеры завершены, остались три основные службы, backend healthy. Audit revisions намеренно сохранены. + +Runtime backend/frontend3722fad, локальное меню ещё не развёрнуто. Следующий12.5.62 — frontend Docker deploy и полный UI create interface/peer→disable/enable→повторный handshake/Ping→delete на новой реальной паре, без повторных открытий меню. Перед повтором observer должен сохранить приватный baseline на сервере и продолжать независимую cleanup каждого устройства при ошибке финального state read; запуск browser только после подтверждения готовности нового observer. Переключение, экспорт/import, throughput/RBAC и прочие внешние ограничения остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. ## 12.5.60 — развёртывание и реальное полное удаление WireGuard (2026-10-08) diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md index 0d070ebe..cc99c052 100644 --- a/docs/ux-ui-stage-12.md +++ b/docs/ux-ui-stage-12.md @@ -1,6 +1,6 @@ # Этап 12: итоговая приёмка UX/UI -Актуальное продолжение 2026-10-08: 12.5.60 развернул backend/frontend3722fad и подтвердил исправление удаления WG на новой реальной паре CHR: обычный UI DELETE200/200 удалил интерфейсы, peers и IPv4 addresses без предварительной очистки. Native состояние проверено до fallback, выбранный baseline и DB controls совпали, backend healthy; production builds/TypeScript и smoke6 passed. Следующий12.5.61 — WireGuard через UI: создание интерфейса/пира, переключение и очистка собственной реальной пары; секреты не публиковать. Этап12.5 открыт. +Актуальное продолжение 2026-10-08: 12.5.61 подтвердил UI создание WireGuard interfaces/peers, native поля, handshake и первый Ping; переключение не завершено из-за remount меню (DOM detached). Локально WireGuard actions вынесен в стабильный renderer/context, red→green regression и profile6/6/TypeScript/ESLint passed. Обе попытки own объектов очищены, native отсутствие остатков и DB controls подтверждены; transient observer401/recovery отражены отдельно. Runtime backend/frontend3722fad остаётся прежним. Следующий12.5.62 — frontend deploy и полный UI create/peer/toggle/Ping/delete без повторного открытия меню; observer cleanup защитить от ошибок завершающего чтения. Этап12.5 открыт. План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push. @@ -1618,3 +1618,15 @@ DELETE200/200, собственные GRE/IP/connected routes удалены; в Итоговый harness exit0: afterBrowserDelete пуст на обеих сторонах, выбранные WG/peers/IP/routes/IPv4 Firewall поля совпали с baseline. Fallback не понадобился для peers/IP/interfaces; ephemeral контейнер удалён, остались три основные Compose-службы. WireGuard revisions намеренно сохранены как история операций. [Smoke](ux-ui/acceptance/deploy-browser-3722fad-2026-10-08.json): dashboard/servers/uptime/settings/filters/alerts200,320px fit, JS/API errors0. Приняты успешное обычное удаление own интерфейса с IPv4 адресом и пиром и соответствующий UI результат. Partial failures/read errors/сохранение чужих WG объектов проверены локальными тестами59; на этом стенде такие отказы не инжектировались. IPv6 lifecycle, concurrency/multi-instance, throughput, Telegram/Portal и остальные внешние ограничения остаются открытыми. Следующий12.5.61 — реальные WireGuard действия через UI: создание интерфейса и пира, переключение и очистка собственной пары; ключи не публиковать. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.61 — UI создание WireGuard и локальное исправление нестабильного меню (2026-10-08) + +[Первый native observer](ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json): собственная пара mm-acceptance-wg-ui-1791472667527, свободные UDP51879/198.18.255.240/30, интерфейсы и пиры созданы только через production формы приложения. Observer не создавал конфигурацию. Оба интерфейса/IP/native key generation и поля peers/endpoint/allowed/keepalive/comment подтвердились, native peer key association и первый двусторонний Ping3/3 прошли. Публичные ключи переданы в форму в памяти, ключи не включены в отчёты. Создание интерфейса наCHR1 выполнено наПК, наCHR2 при320px. [Мобильная форма](ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png) сохранена без ключей и визуально просмотрена; этот снимок второй попытки, не доказательство переключения. + +Полный сценарий НЕ завершён: первая попытка не получила PATCH переключения; вторая, mm-acceptance-wg-ui-1791472879650, после создания интерфейсов остановилась на Добавить пира. Playwright зафиксировал unstable→detached DOM menuitem при перерисовке. Callback props пересоздавали columns/inline actions cell, React сбрасывал Base UI menu. Временные browser harness имеют эти ограничения; успешный финальный browser report не создавался и не заявляется. + +Локально components/data-grids/wireguard-data-grid.tsx адаптирован по уже принятому GRE образцу: стабильный WireguardActionsCell и типизированный context передают свежие onExport/onAddPeer/onToggle/onDelete/toggleBusy. Данные строки остаются текущими; composition, styles, row key и installed ReUI APIs сохранены. Tests/e2e/wireguard-menu-refresh.spec.ts воспроизвёл исчезнувшее меню на прежнем коде; после исправления меню переживает pending refresh, показывает новое enabled state и вызывает PATCH с актуальным disabled=false. Вместе с wireguard-tables.spec.ts —6/6 passed. ESLint и scoped TypeScript passed. Первый TypeScript запуск совпал с генерацией .next/dev/types и получил временные syntax errors; повтор после остановки Next прошёл. ReUI checklist проверен, общие primitives не менялись. + +Первая пара очищена observer, выбранный baseline полностью совпал. Во второй попытке [observer получил401](ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json) на завершающем native чтении до cleanup; причина не установлена, пароли не менялись. Повторное чтение через приложение успешно. [Штатный API cleanup](ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json) проверил точные name/ownership comment и удалил оба own интерфейса DELETE200/200. [Независимая native проверка](ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json) подтвердила отсутствие own WG/peers/IP/routes на обоих CHR, total IPv4 addresses1/2 и routes4/5; WG interfaces/peers0. Второй baseline был только в памяти и потерян при выходе observer: точное равенство его baseline не заявляется. [DB controls](ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json) servers2/speedHistory9/retention14/migrations9 совпали. Эфемерные контейнеры завершены, остались три основные службы, backend healthy. Audit revisions намеренно сохранены. + +Runtime backend/frontend3722fad, локальное меню ещё не развёрнуто. Следующий12.5.62 — frontend Docker deploy и полный UI create interface/peer→disable/enable→повторный handshake/Ping→delete на новой реальной паре, без повторных открытий меню. Перед повтором observer должен сохранить приватный baseline на сервере и продолжать независимую cleanup каждого устройства при ошибке финального state read; запуск browser только после подтверждения готовности нового observer. Переключение, экспорт/import, throughput/RBAC и прочие внешние ограничения остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md index d6f650a6..97c96356 100644 --- a/docs/ux-ui-test-server.md +++ b/docs/ux-ui-test-server.md @@ -461,3 +461,15 @@ DELETE200/200, собственные GRE/IP/connected routes удалены; в Итоговый harness exit0: afterBrowserDelete пуст на обеих сторонах, выбранные WG/peers/IP/routes/IPv4 Firewall поля совпали с baseline. Fallback не понадобился для peers/IP/interfaces; ephemeral контейнер удалён, остались три основные Compose-службы. WireGuard revisions намеренно сохранены как история операций. [Smoke](ux-ui/acceptance/deploy-browser-3722fad-2026-10-08.json): dashboard/servers/uptime/settings/filters/alerts200,320px fit, JS/API errors0. Приняты успешное обычное удаление own интерфейса с IPv4 адресом и пиром и соответствующий UI результат. Partial failures/read errors/сохранение чужих WG объектов проверены локальными тестами59; на этом стенде такие отказы не инжектировались. IPv6 lifecycle, concurrency/multi-instance, throughput, Telegram/Portal и остальные внешние ограничения остаются открытыми. Следующий12.5.61 — реальные WireGuard действия через UI: создание интерфейса и пира, переключение и очистка собственной пары; ключи не публиковать. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. + +## 12.5.61 — UI создание WireGuard и локальное исправление нестабильного меню (2026-10-08) + +[Первый native observer](ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json): собственная пара mm-acceptance-wg-ui-1791472667527, свободные UDP51879/198.18.255.240/30, интерфейсы и пиры созданы только через production формы приложения. Observer не создавал конфигурацию. Оба интерфейса/IP/native key generation и поля peers/endpoint/allowed/keepalive/comment подтвердились, native peer key association и первый двусторонний Ping3/3 прошли. Публичные ключи переданы в форму в памяти, ключи не включены в отчёты. Создание интерфейса наCHR1 выполнено наПК, наCHR2 при320px. [Мобильная форма](ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png) сохранена без ключей и визуально просмотрена; этот снимок второй попытки, не доказательство переключения. + +Полный сценарий НЕ завершён: первая попытка не получила PATCH переключения; вторая, mm-acceptance-wg-ui-1791472879650, после создания интерфейсов остановилась на Добавить пира. Playwright зафиксировал unstable→detached DOM menuitem при перерисовке. Callback props пересоздавали columns/inline actions cell, React сбрасывал Base UI menu. Временные browser harness имеют эти ограничения; успешный финальный browser report не создавался и не заявляется. + +Локально components/data-grids/wireguard-data-grid.tsx адаптирован по уже принятому GRE образцу: стабильный WireguardActionsCell и типизированный context передают свежие onExport/onAddPeer/onToggle/onDelete/toggleBusy. Данные строки остаются текущими; composition, styles, row key и installed ReUI APIs сохранены. Tests/e2e/wireguard-menu-refresh.spec.ts воспроизвёл исчезнувшее меню на прежнем коде; после исправления меню переживает pending refresh, показывает новое enabled state и вызывает PATCH с актуальным disabled=false. Вместе с wireguard-tables.spec.ts —6/6 passed. ESLint и scoped TypeScript passed. Первый TypeScript запуск совпал с генерацией .next/dev/types и получил временные syntax errors; повтор после остановки Next прошёл. ReUI checklist проверен, общие primitives не менялись. + +Первая пара очищена observer, выбранный baseline полностью совпал. Во второй попытке [observer получил401](ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json) на завершающем native чтении до cleanup; причина не установлена, пароли не менялись. Повторное чтение через приложение успешно. [Штатный API cleanup](ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json) проверил точные name/ownership comment и удалил оба own интерфейса DELETE200/200. [Независимая native проверка](ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json) подтвердила отсутствие own WG/peers/IP/routes на обоих CHR, total IPv4 addresses1/2 и routes4/5; WG interfaces/peers0. Второй baseline был только в памяти и потерян при выходе observer: точное равенство его baseline не заявляется. [DB controls](ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json) servers2/speedHistory9/retention14/migrations9 совпали. Эфемерные контейнеры завершены, остались три основные службы, backend healthy. Audit revisions намеренно сохранены. + +Runtime backend/frontend3722fad, локальное меню ещё не развёрнуто. Следующий12.5.62 — frontend Docker deploy и полный UI create interface/peer→disable/enable→повторный handshake/Ping→delete на новой реальной паре, без повторных открытий меню. Перед повтором observer должен сохранить приватный baseline на сервере и продолжать независимую cleanup каждого устройства при ошибке финального state read; запуск browser только после подтверждения готовности нового observer. Переключение, экспорт/import, throughput/RBAC и прочие внешние ограничения остаются открытыми. Пользовательское правило настоящих VPN объектов сохраняется. Локальный коммит без push. diff --git a/docs/ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json b/docs/ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json new file mode 100644 index 00000000..e0935927 --- /dev/null +++ b/docs/ux-ui/acceptance/wg-ui-db-controls-2026-10-08.json @@ -0,0 +1,16 @@ +{ + "date": "2026-10-08", + "before": { + "servers": 2, + "speedHistory": 9, + "retentionDays": 14, + "migrations": 9 + }, + "after": { + "servers": 2, + "speedHistory": 9, + "retentionDays": 14, + "migrations": 9 + }, + "unchanged": true +} diff --git a/docs/ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json b/docs/ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json new file mode 100644 index 00000000..6f931dff --- /dev/null +++ b/docs/ux-ui/acceptance/wg-ui-final-native-cleanup-2026-10-08.json @@ -0,0 +1,36 @@ +{ + "date": "2026-10-08", + "names": [ + "mm-acceptance-wg-ui-1791472667527", + "mm-acceptance-wg-ui-1791472879650" + ], + "results": [ + { + "serverId": 1, + "ownInterfaces": 0, + "ownPeers": 0, + "ownAddresses": 0, + "ownRoutes": 0, + "totalInterfaces": 0, + "totalPeers": 0, + "totalAddresses": 1, + "totalRoutes": 4 + }, + { + "serverId": 2, + "ownInterfaces": 0, + "ownPeers": 0, + "ownAddresses": 0, + "ownRoutes": 0, + "totalInterfaces": 0, + "totalPeers": 0, + "totalAddresses": 2, + "totalRoutes": 5 + } + ], + "keysOmitted": true, + "limits": [ + "Independent final absence check; original baseline of second attempt lost after observer exit", + "First attempt full selected baseline was verified" + ] +} diff --git a/docs/ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json b/docs/ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json new file mode 100644 index 00000000..0ad2b859 --- /dev/null +++ b/docs/ux-ui/acceptance/wg-ui-first-attempt-native-2026-10-08.json @@ -0,0 +1,199 @@ +{ + "date": "2026-10-08", + "backendSource": "3722fad", + "frontendSource": "3722fad", + "name": "mm-acceptance-wg-ui-1791472667527", + "listenPort": 51879, + "subnet": "198.18.255.240/30", + "realRouterOs": true, + "pings": [ + { + "round": 1, + "results": [ + { + "serverId": 1, + "received": 3, + "summary": { + "sent": "3", + "received": "3", + "packet-loss": "0", + "avg-rtt": "55ms80us" + } + }, + { + "serverId": 2, + "received": 3, + "summary": { + "sent": "3", + "received": "3", + "packet-loss": "0", + "avg-rtt": "55ms40us" + } + } + ], + "passed": true + } + ], + "cleanup": [ + { + "serverId": 1, + "/interface/wireguard": true, + "/interface/wireguard/peers": true, + "/ip/address": true, + "/ip/route": true, + "/ip/firewall/filter": true, + "/ip/firewall/nat": true, + "/ip/firewall/mangle": true + }, + { + "serverId": 2, + "/interface/wireguard": true, + "/interface/wireguard/peers": true, + "/ip/address": true, + "/ip/route": true, + "/ip/firewall/filter": true, + "/ip/firewall/nat": true, + "/ip/firewall/mangle": true + } + ], + "uiCreatesOnly": true, + "nativePeerAssociationConfirmed": true, + "connectedState": [ + { + "serverId": 1, + "host": "194.226.121.150", + "expectedAddress": "198.18.255.241/30", + "interface": { + "rosId": "*9", + "appId": "1:*9", + "name": "mm-acceptance-wg-ui-1791472667527", + "listenPort": "51879", + "mtu": "1420", + "disabled": "false", + "running": "true", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "keyGenerated": true + }, + "addresses": [ + "198.18.255.241/30" + ], + "peers": [ + { + "rosId": "*5", + "allowedAddresses": "198.18.255.242/32", + "endpoint": "104.165.244.7:51879", + "keepalive": "5s", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "disabled": "false", + "handshake": "2s", + "rx": "756", + "tx": "848" + } + ] + }, + { + "serverId": 2, + "host": "104.165.244.7", + "expectedAddress": "198.18.255.242/30", + "interface": { + "rosId": "*5", + "appId": "2:*5", + "name": "mm-acceptance-wg-ui-1791472667527", + "listenPort": "51879", + "mtu": "1420", + "disabled": "false", + "running": "true", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "keyGenerated": true + }, + "addresses": [ + "198.18.255.242/30" + ], + "peers": [ + { + "rosId": "*3", + "allowedAddresses": "198.18.255.241/32", + "endpoint": "194.226.121.150:51879", + "keepalive": "5s", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "disabled": "false", + "handshake": "2s", + "rx": "700", + "tx": "756" + } + ] + } + ], + "afterBrowser": [ + { + "serverId": 1, + "host": "194.226.121.150", + "expectedAddress": "198.18.255.241/30", + "interface": { + "rosId": "*9", + "appId": "1:*9", + "name": "mm-acceptance-wg-ui-1791472667527", + "listenPort": "51879", + "mtu": "1420", + "disabled": "false", + "running": "true", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "keyGenerated": true + }, + "addresses": [ + "198.18.255.241/30" + ], + "peers": [ + { + "rosId": "*5", + "allowedAddresses": "198.18.255.242/32", + "endpoint": "104.165.244.7:51879", + "keepalive": "5s", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "disabled": "false", + "handshake": "1m54s", + "rx": "1552", + "tx": "1028" + } + ] + }, + { + "serverId": 2, + "host": "104.165.244.7", + "expectedAddress": "198.18.255.242/30", + "interface": { + "rosId": "*5", + "appId": "2:*5", + "name": "mm-acceptance-wg-ui-1791472667527", + "listenPort": "51879", + "mtu": "1420", + "disabled": "false", + "running": "true", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "keyGenerated": true + }, + "addresses": [ + "198.18.255.242/30" + ], + "peers": [ + { + "rosId": "*3", + "allowedAddresses": "198.18.255.241/32", + "endpoint": "194.226.121.150:51879", + "keepalive": "5s", + "comment": "Temporary UX acceptance mm-acceptance-wg-ui-1791472667527", + "disabled": "false", + "handshake": "1m54s", + "rx": "880", + "tx": "1552" + } + ] + } + ], + "limits": [ + "Only own WireGuard resources; no default route or firewall changes", + "Public keys transferred to browser only in memory; no keys persisted in report", + "Short handshake/ICMP, not throughput or failure/RBAC acceptance", + "WireGuard revision audit retained" + ] +} diff --git a/docs/ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json b/docs/ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json new file mode 100644 index 00000000..5894c283 --- /dev/null +++ b/docs/ux-ui/acceptance/wg-ui-second-api-cleanup-2026-10-08.json @@ -0,0 +1,19 @@ +{ + "date": "2026-10-08", + "name": "mm-acceptance-wg-ui-1791472879650", + "results": [ + { + "serverId": "1", + "status": 200 + }, + { + "serverId": "2", + "status": 200 + } + ], + "applicationCatalogAbsent": true, + "limits": [ + "Cleanup recovered after transient observer 401; separate native reads verify no own remnants", + "Only exact name/ownership comment fixtures deleted" + ] +} diff --git a/docs/ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json b/docs/ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json new file mode 100644 index 00000000..a4deb312 --- /dev/null +++ b/docs/ux-ui/acceptance/wg-ui-second-observer-error-2026-10-08.json @@ -0,0 +1,17 @@ +{ + "date": "2026-10-08", + "name": "mm-acceptance-wg-ui-1791472879650", + "observerError": { + "status": 401, + "path": "/interface/wireguard", + "stage": "final native read before cleanup" + }, + "cleanupSkippedByObserver": true, + "recoveredViaApplicationDelete": true, + "finalNativeOwnObjectsAbsent": true, + "limits": [ + "Cause of transient 401 not established; later app/native reads succeeded", + "Passwords were not changed", + "Second in-memory baseline was lost; no claim of exact second baseline equality" + ] +} diff --git a/docs/ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png b/docs/ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png new file mode 100644 index 0000000000000000000000000000000000000000..d9a950bb6f63d3fc38f59e683c4e1acabcfc44be GIT binary patch literal 60983 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
IYn6 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�gzq1T2qrHuJr^XTab^+3C?2>Q%F~e*|X3HHN!# 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 E0r_d9^8f$< literal 0 HcmV?d00001 diff --git a/tests/e2e/wireguard-menu-refresh.spec.ts b/tests/e2e/wireguard-menu-refresh.spec.ts new file mode 100644 index 00000000..949ee477 --- /dev/null +++ b/tests/e2e/wireguard-menu-refresh.spec.ts @@ -0,0 +1,35 @@ +import { test, expect } from '@playwright/test' +test('WireGuard: меню переживает refresh и использует новое состояние интерфейса', async ({ page }) => { + let refreshing = false, release: (() => void) | undefined, patches = 0 + const gate = new Promise(resolve => { release = resolve }) + const iface = { id: '1:*A', rosId: '*A', name: 'wg-menu', serverId: '1', serverName: 'CHR', listenPort: 51879, mtu: 1420, comment: '', enabled: true, status: 'up', peers: [] } + await page.route('**/health', route => route.fulfill({ json: { ok: true } })) + await page.route('**/api/**', async route => { + const path = new URL(route.request().url()).pathname + if (route.request().method() === 'PATCH') { + expect(path).toBe('/api/wireguard/interfaces/1/*A') + expect(route.request().postDataJSON()).toEqual({ disabled: false }) + patches++ + return route.fulfill({ json: { ok: true } }) + } + if (path === '/api/wireguard') { + if (refreshing) await gate + return route.fulfill({ json: { interfaces: [{ ...iface, enabled: !refreshing, status: refreshing ? 'down' : 'up' }] } }) + } + return route.fulfill({ json: path === '/api/auth/config' ? { required: false } : path === '/api/servers' ? [{ id: 1, name: 'CHR', host: '192.0.2.1', enabled: true, type: 'home-router', country: 'RU', status: 'online' }] : {} }) + }) + await page.goto('/wireguard') + const refresh = page.getByRole('button', { name: 'Обновить', exact: true }) + await expect(refresh).toBeEnabled() + await page.getByRole('button', { name: 'Действия: wg-menu' }).click() + await expect(page.getByRole('menuitem', { name: 'Отключить', exact: true })).toBeVisible() + refreshing = true + await refresh.evaluate(e => (e as HTMLButtonElement).click()) + await expect(refresh).toBeDisabled() + await expect(page.getByRole('menuitem', { name: 'Добавить пира', exact: true })).toBeVisible() + release!() + await expect(refresh).toBeEnabled() + await expect(page.getByRole('menuitem', { name: 'Включить', exact: true })).toBeVisible() + await page.getByRole('menuitem', { name: 'Включить', exact: true }).click() + await expect.poll(() => patches).toBe(1) +})