diff --git a/components/backups/backup-confirm-dialogs.tsx b/components/backups/backup-confirm-dialogs.tsx index 93fb52e7..8579dd1b 100644 --- a/components/backups/backup-confirm-dialogs.tsx +++ b/components/backups/backup-confirm-dialogs.tsx @@ -36,7 +36,7 @@ export function BackupDeleteDialog({ Удалить бэкап? - + Файл {backup?.filename} будет удалён {backup?.storage === "s3" || backup?.storage === "both" ? " локально; объект S3 — через текущее подключение к хранилищу" : " с диска"}. @@ -77,7 +77,7 @@ export function BackupRestoreDialog({ Восстановить конфигурацию? - + Файл {backup?.filename} будет загружен на {backup?.server} и импортирован. @@ -85,7 +85,7 @@ export function BackupRestoreDialog({ - Это изменит рабочую конфигурацию роутера + Это изменит рабочую конфигурацию роутера Сессия может оборваться. Убедитесь, что выбран именно этот сервер и этот снимок. diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 6dc3fd9e..5c11ab44 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -226,7 +226,7 @@ REST запрос → ожидание → подтверждённый output / | Пользователи | [Профиль](ux-ui/screens/users-profile-desktop.png), [ошибка](ux-ui/screens/users-error-desktop.png) | [390](ux-ui/screens/users-profile-mobile.png), [320](ux-ui/screens/users-profile-narrow.png) | | Контейнеры | [Обзор](ux-ui/screens/containers-overview-desktop.png), [снимок](ux-ui/screens/containers-export-desktop.png) | [390](ux-ui/screens/containers-export-mobile.png), [320](ux-ui/screens/containers-export-narrow.png) | -Актуальная точка продолжения: 11А.31.2 — S3/ошибки/диалоги и визуальная приёмка резервных копий. Frame/первые 9 экранов приняты в 11А.31.1. URL/Filters контекст принят в 11А.30. Клиентский checkpoint restore принят в 11А.29.5. Backend outcome неизвестного импорта принят в 11А.29.4. Идентичность receipt принята в 11А.29.3. Backend cleanup удаления принят в 11А.29.2. Frontend delete/restore/download принят в 11А.29.1, затем URL/Frame/визуальная приёмка и scheduler cleanup. Frontend checkpoint/создание/serial polling приняты локально в 11А.28.2. API/основа наблюдения реализованы в 11А.28.1. Настройки расписания/S3 реализованы в 11А.27.1–11А.27.2. Каталоги/source/права реализованы в 11А.25, backend результат ручных операций исправлен в 11А.26. Сертификаты приняты локально в 11А.24, backups API стал строгим. State API, reader, snapshot merge и nullable представление подключены в 11А.14–11А.17.3; таблица требований — [этап 11А](ux-ui-stage-11a.md). Резервные копии и весь 11А ещё не приняты. +Актуальная точка продолжения: 11А.31.2.2 — S3/partial job/initial errors и визуальная приёмка резервных копий. Диалоги ошибок приняты в 11А.31.2.1. Frame/первые 9 экранов приняты в 11А.31.1. URL/Filters контекст принят в 11А.30. Клиентский checkpoint restore принят в 11А.29.5. Backend outcome неизвестного импорта принят в 11А.29.4. Идентичность receipt принята в 11А.29.3. Backend cleanup удаления принят в 11А.29.2. Frontend delete/restore/download принят в 11А.29.1, затем URL/Frame/визуальная приёмка и scheduler cleanup. Frontend checkpoint/создание/serial polling приняты локально в 11А.28.2. API/основа наблюдения реализованы в 11А.28.1. Настройки расписания/S3 реализованы в 11А.27.1–11А.27.2. Каталоги/source/права реализованы в 11А.25, backend результат ручных операций исправлен в 11А.26. Сертификаты приняты локально в 11А.24, backups API стал строгим. State API, reader, snapshot merge и nullable представление подключены в 11А.14–11А.17.3; таблица требований — [этап 11А](ux-ui-stage-11a.md). Резервные копии и весь 11А ещё не приняты. | Экран | ПК | Телефон | | --- | --- | --- | @@ -236,3 +236,9 @@ REST запрос → ожидание → подтверждённый output / Референсы: [Settings 16](https://reui.io/preview/base/settings-16?ref=mcp), [Data Grid Filtering 2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet 8](https://reui.io/preview/base/sheet-8?ref=mcp). Схема: источник → сводка → история/настройки → immutable цель → подтверждение API/неизвестный результат. + +| Экран | ПК | Телефон | +| --- | --- | --- | +| Ошибка удаления | [PNG](ux-ui/screens/backups-delete-error-desktop.png) | [390](ux-ui/screens/backups-delete-error-mobile.png), [320](ux-ui/screens/backups-delete-error-narrow.png) | +| Неизвестный импорт | [PNG](ux-ui/screens/backups-restore-unknown-desktop.png) | [390](ux-ui/screens/backups-restore-unknown-mobile.png), [320](ux-ui/screens/backups-restore-unknown-narrow.png) | + diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 3803e606..a0166eeb 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -27,7 +27,7 @@ | 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 | | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | -| 11А | Пользователи, контейнеры, сертификаты и резервные копии | В работе: 11А.1–11А.31.1; пользователи/контейнеры/сертификаты приняты локально; резервные копии: Frame/первые экраны, далее 11А.31.2 | +| 11А | Пользователи, контейнеры, сертификаты и резервные копии | В работе: 11А.1–11А.31.2.1; пользователи/контейнеры/сертификаты приняты локально; резервные копии: Frame/диалоги, далее 11А.31.2.2 | | 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | @@ -36,6 +36,17 @@ +## Блок 11А.31.2.1 — ошибки и подтверждения в узких диалогах (2026-10-01) + +- Delete/restore описания переносят непрерывное имя файла. Предупреждение изменения рабочей конфигурации не обрезается line-clamp на 320 px. Ошибка удаления сохраняет цель; unknown restore оставляет disabled import/«Закрыть», Escape не объявляет отмену RouterOS. +- 3 keyboard/dialog layout E2E прошли повторно после снятия clamp; профильный ESLint прошёл. 6 PNG ПК/390/320 сохранены, узкий restore просмотрен. Проверены границы диалога по ширине/высоте и отсутствие его внутреннего горизонтального overflow. Production frontend прошёл в 11А.31.1; в этом блоке только CSS отображения и layout тесты, полная сборка не повторялась. +- **Следующий блок 11А.31.2.2:** S3/partial job/initial resource error визуальные состояния, затем backup scheduler cleanup (lock при setup/event сбоях, отделение результата от event/prune) и профильная приёмка. Backup scheduler обнаруженные дефекты пока не исправлены, весь backups/11А не принят. Реальные RouterOS/PostgreSQL/S3 остаются внешней приёмкой. + +| Экран | ПК | Телефон | +| --- | --- | --- | +| Ошибка удаления | [PNG](ux-ui/screens/backups-delete-error-desktop.png) | [390](ux-ui/screens/backups-delete-error-mobile.png), [320](ux-ui/screens/backups-delete-error-narrow.png) | +| Неизвестный импорт | [PNG](ux-ui/screens/backups-restore-unknown-desktop.png) | [390](ux-ui/screens/backups-restore-unknown-mobile.png), [320](ux-ui/screens/backups-restore-unknown-narrow.png) | + ## Блок 11А.31.1 — Frame и первые адаптивные экраны резервных копий (2026-10-01) - История/настройки объединены существующим ReUI Frame/FramePanel, добавлены h1/краткая подпись, min-width и адаптивные отступы. Refresh и элементы настроек переносятся. Null status API становится «Неизвестно», а не offline/«недоступен». diff --git a/docs/ux-ui-stage-11a.md b/docs/ux-ui-stage-11a.md index e78e16c8..2667b289 100644 --- a/docs/ux-ui-stage-11a.md +++ b/docs/ux-ui-stage-11a.md @@ -435,3 +435,15 @@ Референсы: [Settings 16](https://reui.io/preview/base/settings-16?ref=mcp), [Data Grid Filtering 2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp), [Sheet 8](https://reui.io/preview/base/sheet-8?ref=mcp). Схема: источник → сводка → история/настройки → immutable цель → подтверждение API/неизвестный результат. + +## Блок 11А.31.2.1 — ошибки и подтверждения в узких диалогах (2026-10-01) + +- Delete/restore описания переносят непрерывное имя файла. Предупреждение изменения рабочей конфигурации не обрезается line-clamp на 320 px. Ошибка удаления сохраняет цель; unknown restore оставляет disabled import/«Закрыть», Escape не объявляет отмену RouterOS. +- 3 keyboard/dialog layout E2E прошли повторно после снятия clamp; профильный ESLint прошёл. 6 PNG ПК/390/320 сохранены, узкий restore просмотрен. Проверены границы диалога по ширине/высоте и отсутствие его внутреннего горизонтального overflow. Production frontend прошёл в 11А.31.1; в этом блоке только CSS отображения и layout тесты, полная сборка не повторялась. +- **Следующий блок 11А.31.2.2:** S3/partial job/initial resource error визуальные состояния, затем backup scheduler cleanup (lock при setup/event сбоях, отделение результата от event/prune) и профильная приёмка. Backup scheduler обнаруженные дефекты пока не исправлены, весь backups/11А не принят. Реальные RouterOS/PostgreSQL/S3 остаются внешней приёмкой. + +| Экран | ПК | Телефон | +| --- | --- | --- | +| Ошибка удаления | [PNG](ux-ui/screens/backups-delete-error-desktop.png) | [390](ux-ui/screens/backups-delete-error-mobile.png), [320](ux-ui/screens/backups-delete-error-narrow.png) | +| Неизвестный импорт | [PNG](ux-ui/screens/backups-restore-unknown-desktop.png) | [390](ux-ui/screens/backups-restore-unknown-mobile.png), [320](ux-ui/screens/backups-restore-unknown-narrow.png) | + diff --git a/docs/ux-ui/screens/backups-delete-error-desktop.png b/docs/ux-ui/screens/backups-delete-error-desktop.png new file mode 100644 index 00000000..af79f8c1 Binary files /dev/null and b/docs/ux-ui/screens/backups-delete-error-desktop.png differ diff --git a/docs/ux-ui/screens/backups-delete-error-mobile.png b/docs/ux-ui/screens/backups-delete-error-mobile.png new file mode 100644 index 00000000..e897210d Binary files /dev/null and b/docs/ux-ui/screens/backups-delete-error-mobile.png differ diff --git a/docs/ux-ui/screens/backups-delete-error-narrow.png b/docs/ux-ui/screens/backups-delete-error-narrow.png new file mode 100644 index 00000000..5efd8898 Binary files /dev/null and b/docs/ux-ui/screens/backups-delete-error-narrow.png differ diff --git a/docs/ux-ui/screens/backups-restore-unknown-desktop.png b/docs/ux-ui/screens/backups-restore-unknown-desktop.png new file mode 100644 index 00000000..22b1bcd8 Binary files /dev/null and b/docs/ux-ui/screens/backups-restore-unknown-desktop.png differ diff --git a/docs/ux-ui/screens/backups-restore-unknown-mobile.png b/docs/ux-ui/screens/backups-restore-unknown-mobile.png new file mode 100644 index 00000000..78cf2a7d Binary files /dev/null and b/docs/ux-ui/screens/backups-restore-unknown-mobile.png differ diff --git a/docs/ux-ui/screens/backups-restore-unknown-narrow.png b/docs/ux-ui/screens/backups-restore-unknown-narrow.png new file mode 100644 index 00000000..d2e39cf1 Binary files /dev/null and b/docs/ux-ui/screens/backups-restore-unknown-narrow.png differ diff --git a/tests/e2e/backups-dialog-layout.spec.ts b/tests/e2e/backups-dialog-layout.spec.ts new file mode 100644 index 00000000..0127296d --- /dev/null +++ b/tests/e2e/backups-dialog-layout.spec.ts @@ -0,0 +1,19 @@ +import { test, expect } from "@playwright/test" +import { backup, setupBackups } from "./fixtures/backups" +for (const [name, width] of [["desktop", 1440], ["mobile", 390], ["narrow", 320]] as const) test("Бэкапы: длинный файл и ошибки диалогов/" + name, async ({ page }) => { + await page.setViewportSize({ width, height: 900 }); await setupBackups(page) + const filename = "a".repeat(130) + ".rsc" + await page.route("**/api/backups", r => r.fulfill({ json: [{ ...backup, filename }] })) + await page.route("**/api/backups/backup-1", r => r.fulfill({ status: 503, json: { error: "Хранилище не подтвердило удаление. Запись резервной копии сохранена." } })) + await page.route("**/api/backups/backup-1/restore", r => r.fulfill({ status: 502, json: { outcome: "unknown", error: "Связь с роутером оборвалась после отправки импорта." } })) + await page.goto("/backups"); await page.getByRole("button", { name: "Удалить " + filename, exact: true }).click() + const dialog = page.getByRole("alertdialog"); await dialog.getByRole("button", { name: "Удалить", exact: true }).click(); await expect(dialog).toContainText("Запись резервной копии сохранена") + expect(await dialog.evaluate(element => element.scrollWidth <= element.clientWidth + 1)).toBe(true) + await page.screenshot({ path: "docs/ux-ui/screens/backups-delete-error-" + name + ".png", fullPage: true }) + await dialog.getByRole("button", { name: "Отмена", exact: true }).click(); await page.getByRole("button", { name: "Восстановить " + filename, exact: true }).click() + await dialog.getByRole("button", { name: "Восстановить", exact: true }).click(); await expect(dialog).toContainText("Результат импорта неизвестен"); await expect(dialog.getByRole("button", { name: "Восстановить", exact: true })).toBeDisabled() + expect(await dialog.evaluate(element => element.scrollWidth <= element.clientWidth + 1)).toBe(true) + const box = await dialog.boundingBox(); expect(box && box.x >= 0 && box.y >= 0 && box.x + box.width <= width + 1 && box.y + box.height <= 901).toBe(true) + await page.screenshot({ path: "docs/ux-ui/screens/backups-restore-unknown-" + name + ".png", fullPage: true }) + await page.keyboard.press("Escape"); await expect(dialog).toHaveCount(0) +})