fix(ui): проверить клавиатуру и адаптивность IPsec
This commit is contained in:
1 parent
ce43b2d762
commit
6fc342cd2c
11 files changed
+92
-2
No files matched your search
@@ -67,6 +67,7 @@ function IpsecUsersGrid({
|
|||||||
type="button"
|
type="button"
|
||||||
disabled={!onEdit || (canManage && !canManage(c.serverId))}
|
disabled={!onEdit || (canManage && !canManage(c.serverId))}
|
||||||
className="truncate text-left font-medium hover:underline"
|
className="truncate text-left font-medium hover:underline"
|
||||||
|
title={c.name}
|
||||||
onClick={() => onEdit?.(c)}
|
onClick={() => onEdit?.(c)}
|
||||||
>
|
>
|
||||||
{c.name}
|
{c.name}
|
||||||
|
|||||||
+7
-1
@@ -101,7 +101,13 @@ flowchart LR
|
|||||||
|
|
||||||
Снимки HTTP-фикстур просмотрены; в блоке 9.47 прошли общая регрессия Filters (33 E2E) и production build frontend (34 страницы). Этап 9 остаётся в работе; следующие блоки — ограничения IPsec/VXLAN и общего GRE-каталога по журналу.
|
Снимки HTTP-фикстур просмотрены; в блоке 9.47 прошли общая регрессия Filters (33 E2E) и production build frontend (34 страницы). Этап 9 остаётся в работе; следующие блоки — ограничения IPsec/VXLAN и общего GRE-каталога по журналу.
|
||||||
|
|
||||||
В блоке 9.48 исправлены первоначальные и частичные ошибки чтения IPsec; далее readiness действий, свежесть и гонки этого раздела.
|
В блоках 9.48–9.52 исправлены чтение, readiness, свежесть и контекст IPsec; прошли профильные операции, ПК/телефон и frontend build. Этап 9 открыт; далее VXLAN/GRE.
|
||||||
|
|
||||||
|
| IPsec — проверенные экраны | ПК | Телефон |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Устройство → свежесть → клиенты | [Экран](ux-ui/screens/ipsec-desktop.png) | [Экран](ux-ui/screens/ipsec-mobile.png) |
|
||||||
|
| Клиент → форма → подтверждение API | [Форма](ux-ui/screens/ipsec-client-desktop.png) | [Форма](ux-ui/screens/ipsec-client-mobile.png) |
|
||||||
|
| Устройство → peer/сертификаты → действия | [Серверы](ux-ui/screens/ipsec-servers-desktop.png) | [Сервер](ux-ui/screens/ipsec-servers-mobile.png) |
|
||||||
|
|
||||||
## Порядок продолжения и журналирования
|
## Порядок продолжения и журналирования
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -25,12 +25,25 @@
|
|||||||
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
|
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
|
||||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||||
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
||||||
| 9 | VPN, Firewall и Filters | В работе: блоки 9.1–9.51 — IPsec/GRE/VXLAN, Firewall и операции Filters |
|
| 9 | VPN, Firewall и Filters | В работе: блоки 9.1–9.52 — IPsec/GRE/VXLAN, Firewall и операции Filters |
|
||||||
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
|
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
|
||||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||||
|
|
||||||
|
## Блок 9.52 — клавиатура и экраны IPsec (2026-09-30)
|
||||||
|
|
||||||
|
- Два E2E проверили ПК 1440×1000 и телефон 390×844: выбор устройства на телефоне, открытие клиента клавиатурой, сохранение через Enter с единственным PATCH в правильный serverId, отсутствие горизонтального переполнения документа, границы формы и серверные действия. Шесть PNG сохранены и просмотрены. Имена клиентов получают title для обрезанного текста.
|
||||||
|
- Первые прогоны уточнили существующее имя поля и дождались штатной анимации Sheet; оба исправленных сценария прошли. Снимок сервера телефона показывает прокрученный контент с кнопками; таблица имеет внутреннюю горизонтальную прокрутку. Это профильная проверка PSK-редактирования, не вся приёмка screen reader/контраста/всех полей.
|
||||||
|
- Production build frontend прошёл (контракты/TypeScript, 34 страницы), lint изменённого компонента/теста чистый. Backend не менялся, последний build — 9.44. Снимки используют HTTP-фикстуры, реальные RouterOS/JWT/БД не проверены.
|
||||||
|
- Далее — оставшиеся ограничения VXLAN: достоверность после общего сбоя, свежесть, режим просмотра и источник; затем ошибки общего GRE-каталога. Этап 9 открыт. Локальный коммит без push.
|
||||||
|
|
||||||
|
| Референс | ПК | Телефон |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [Data Grid Filtering 2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | [Клиенты](ux-ui/screens/ipsec-desktop.png) | [Клиенты](ux-ui/screens/ipsec-mobile.png) |
|
||||||
|
| [Sheet 8](https://reui.io/preview/base/sheet-8?ref=mcp) | [Форма клиента](ux-ui/screens/ipsec-client-desktop.png) | [Форма клиента](ux-ui/screens/ipsec-client-mobile.png) |
|
||||||
|
| [List 9](https://reui.io/preview/base/list-9?ref=mcp) | [Серверы](ux-ui/screens/ipsec-servers-desktop.png) | [Сервер](ux-ui/screens/ipsec-servers-mobile.png) |
|
||||||
|
|
||||||
## Блок 9.51 — свежесть IPsec (2026-09-30)
|
## Блок 9.51 — свежесть IPsec (2026-09-30)
|
||||||
|
|
||||||
- По выбранному устройству показано клиентское время получения успешного ответа; по всем устройствам — время последнего валидного ответа IPsec, которое не означает успешный опрос каждого роутера. Частичный отказ сохраняет прошлое время неответившего устройства, полный отказ не меняет ни одну дату. До первого успешного результата время неизвестно; время отмечается только для устройств, присутствующих в успешных сводках/клиентах.
|
- По выбранному устройству показано клиентское время получения успешного ответа; по всем устройствам — время последнего валидного ответа IPsec, которое не означает успешный опрос каждого роутера. Частичный отказ сохраняет прошлое время неответившего устройства, полный отказ не меняет ни одну дату. До первого успешного результата время неизвестно; время отмечается только для устройств, присутствующих в успешных сводках/клиентах.
|
||||||
|
|||||||
@@ -271,3 +271,16 @@ Live-копирование читает актуальный целевой rul
|
|||||||
- Подтверждённые изменения/восстановление показывают отдельный timestamp API с serverId. Он не заменяет последующий опрос. ISO доступен через time.dateTime, title поясняет клиентские часы; даты не сохраняются между перезагрузками, возраст не обновляется таймером.
|
- Подтверждённые изменения/восстановление показывают отдельный timestamp API с serverId. Он не заменяет последующий опрос. ISO доступен через time.dateTime, title поясняет клиентские часы; даты не сохраняются между перезагрузками, возраст не обновляется таймером.
|
||||||
- 8 E2E прошли: данные/схемы, новый тест управляемых часов с выбором устройства и расширенный замок удаления/ошибка опроса с подтверждением записи. TypeScript и lint чистые. Backend не менялся, build пока не повторялся.
|
- 8 E2E прошли: данные/схемы, новый тест управляемых часов с выбором устройства и расширенный замок удаления/ошибка опроса с подтверждением записи. TypeScript и lint чистые. Backend не менялся, build пока не повторялся.
|
||||||
- Далее — профильная клавиатурная и адаптивная приёмка IPsec, проверенные экраны в плане; затем оставшиеся VXLAN/GRE. Реальные RouterOS/JWT/БД не проверены. Локальный коммит без push.
|
- Далее — профильная клавиатурная и адаптивная приёмка IPsec, проверенные экраны в плане; затем оставшиеся VXLAN/GRE. Реальные RouterOS/JWT/БД не проверены. Локальный коммит без push.
|
||||||
|
|
||||||
|
## Блок 9.52 — клавиатура и экраны IPsec (2026-09-30)
|
||||||
|
|
||||||
|
- Два E2E проверили ПК 1440×1000 и телефон 390×844: выбор устройства на телефоне, открытие клиента клавиатурой, сохранение через Enter с единственным PATCH в правильный serverId, отсутствие горизонтального переполнения документа, границы формы и серверные действия. Шесть PNG сохранены и просмотрены. Имена клиентов получают title для обрезанного текста.
|
||||||
|
- Первые прогоны уточнили существующее имя поля и дождались штатной анимации Sheet; оба исправленных сценария прошли. Снимок сервера телефона показывает прокрученный контент с кнопками; таблица имеет внутреннюю горизонтальную прокрутку. Это профильная проверка PSK-редактирования, не вся приёмка screen reader/контраста/всех полей.
|
||||||
|
- Production build frontend прошёл (контракты/TypeScript, 34 страницы), lint изменённого компонента/теста чистый. Backend не менялся, последний build — 9.44. Снимки используют HTTP-фикстуры, реальные RouterOS/JWT/БД не проверены.
|
||||||
|
- Далее — оставшиеся ограничения VXLAN: достоверность после общего сбоя, свежесть, режим просмотра и источник; затем ошибки общего GRE-каталога. Этап 9 открыт. Локальный коммит без push.
|
||||||
|
|
||||||
|
| Референс | ПК | Телефон |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [Data Grid Filtering 2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | [Клиенты](ux-ui/screens/ipsec-desktop.png) | [Клиенты](ux-ui/screens/ipsec-mobile.png) |
|
||||||
|
| [Sheet 8](https://reui.io/preview/base/sheet-8?ref=mcp) | [Форма клиента](ux-ui/screens/ipsec-client-desktop.png) | [Форма клиента](ux-ui/screens/ipsec-client-mobile.png) |
|
||||||
|
| [List 9](https://reui.io/preview/base/list-9?ref=mcp) | [Серверы](ux-ui/screens/ipsec-servers-desktop.png) | [Сервер](ux-ui/screens/ipsec-servers-mobile.png) |
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 214 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
import { test, expect } from "@playwright/test"
|
||||||
|
import { mkdirSync } from "node:fs"
|
||||||
|
|
||||||
|
for (const device of ["desktop", "mobile"] as const) {
|
||||||
|
test(`IPsec: клавиатура, контекст и layout ${device}`, async ({ page }) => {
|
||||||
|
await page.setViewportSize(device === "desktop" ? { width: 1440, height: 1000 } : { width: 390, height: 844 })
|
||||||
|
await page.clock.install({ time: new Date("2026-09-30T05:00:00Z") })
|
||||||
|
const devices = [1, 2].map(id => ({ id, name: id === 1 ? "Основной VPN роутер" : "Резервный VPN роутер", host: "192.0.2." + id, country: "RU", enabled: true, type: "exit-node", status: "online" }))
|
||||||
|
const clients = ["1", "2"].map(id => ({ id: id + "-*1", rosId: "*1", serverId: id, serverName: devices[Number(id) - 1].name, name: id === "1" ? "Ноутбук инженера" : "Резервный клиент", authMethod: "pre-shared-key", kind: "psk", remoteId: "engineer", peerName: "ikev2-vpn", online: false, disabled: false, managed: true }))
|
||||||
|
const summaries = ["1", "2"].map(id => ({ serverId: id, serverName: devices[Number(id) - 1].name, initialized: true, ike2Ready: true, serverEndpoint: "vpn.example.test", caCert: { name: "vpn-ca", role: "ca", managed: true, trusted: true }, natRuleManaged: true, clientsTotal: 1, clientsOnline: 0, peers: [{ id: id + "-*p1", rosId: "*p1", serverId: id, serverName: devices[Number(id) - 1].name, name: "ikev2-vpn", passive: true, exchangeMode: "ike2", profile: "vpn-profile", address: "0.0.0.0/0", disabled: false, managed: true }], certs: [{ name: "vpn-ca", role: "ca", managed: true, trusted: true }] }))
|
||||||
|
let writes = 0
|
||||||
|
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||||
|
await page.route("**/api/**", r => {
|
||||||
|
const path = new URL(r.request().url()).pathname
|
||||||
|
if (r.request().method() === "PATCH") {
|
||||||
|
writes++
|
||||||
|
expect(path).toContain(device === "mobile" ? "/users/2/" : "/users/1/")
|
||||||
|
expect(r.request().postDataJSON()).toMatchObject({ name: "Проверенный клиент" })
|
||||||
|
clients[device === "mobile" ? 1 : 0].name = "Проверенный клиент"
|
||||||
|
return r.fulfill({ json: { ok: true } })
|
||||||
|
}
|
||||||
|
return r.fulfill({ json: path === "/api/ipsec" ? { servers: summaries, clients } : path === "/api/servers" ? devices : path === "/api/auth/config" ? { required: false } : {} })
|
||||||
|
})
|
||||||
|
await page.goto("/ipsec")
|
||||||
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(2)
|
||||||
|
await expect(page.getByRole("button", { name: "Новый клиент", exact: true })).toBeEnabled()
|
||||||
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||||||
|
await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" })
|
||||||
|
mkdirSync("docs/ux-ui/screens", { recursive: true })
|
||||||
|
await page.screenshot({ path: `docs/ux-ui/screens/ipsec-${device}.png`, animations: "disabled" })
|
||||||
|
if (device === "mobile") {
|
||||||
|
await page.getByRole("button", { name: "Серверы", exact: true }).click()
|
||||||
|
await page.getByRole("dialog").getByRole("option").filter({ hasText: "Резервный VPN роутер" }).click()
|
||||||
|
await expect(page.getByRole("dialog")).toHaveCount(0)
|
||||||
|
await expect(page.locator("tr[data-row-id]")).toHaveCount(1)
|
||||||
|
}
|
||||||
|
const edit = page.getByRole("button", { name: device === "mobile" ? "Резервный клиент" : "Ноутбук инженера", exact: true })
|
||||||
|
await edit.focus()
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
const dialog = page.getByRole("dialog")
|
||||||
|
await dialog.getByRole("textbox", { name: "Имя клиента" }).fill("Проверенный клиент")
|
||||||
|
await expect.poll(() => dialog.evaluate(el => el.getBoundingClientRect().left >= 0 && el.getBoundingClientRect().right <= innerWidth)).toBe(true)
|
||||||
|
await page.screenshot({ path: `docs/ux-ui/screens/ipsec-client-${device}.png`, animations: "disabled" })
|
||||||
|
await dialog.getByRole("button", { name: "Сохранить", exact: true }).focus()
|
||||||
|
await page.keyboard.press("Enter")
|
||||||
|
await expect(dialog).toHaveCount(0)
|
||||||
|
await expect(page.locator("tr[data-row-id]").filter({ hasText: "Проверенный клиент" })).toHaveCount(1)
|
||||||
|
expect(writes).toBe(1)
|
||||||
|
await page.getByRole("tab", { name: "Сервер", exact: true }).click()
|
||||||
|
await expect(page.getByRole("button", { name: "Переинициализировать", exact: true }).first()).toBeEnabled()
|
||||||
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||||||
|
await page.clock.fastForward(6000)
|
||||||
|
await expect(page.getByText("Клиент обновлён", { exact: true })).toHaveCount(0)
|
||||||
|
if (device === "mobile") await page.getByRole("button", { name: "Переинициализировать", exact: true }).scrollIntoViewIfNeeded()
|
||||||
|
await page.screenshot({ path: `docs/ux-ui/screens/ipsec-servers-${device}.png`, animations: "disabled" })
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user