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 00000000..d9a950bb Binary files /dev/null and b/docs/ux-ui/screens/wg-ui-create-server2-320-2026-10-08.png differ 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) +})