fix(ui): сохранять ошибку загрузки истории IPsec
This commit is contained in:
1 parent
3dd4ad34ee
commit
46d62c98b8
5 files changed
+75
-9
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { servers as mockServers } from "@/lib/data"
|
||||
import type { Server } from "@/lib/data"
|
||||
@@ -190,6 +190,8 @@ export default function IpsecPage() {
|
||||
const [historyOpen, setHistoryOpen] = useState(false)
|
||||
const [revisions, setRevisions] = useState<ConfigRevisionDto[]>([])
|
||||
const [historyLoading, setHistoryLoading] = useState(false)
|
||||
const [historyError, setHistoryError] = useState<string | null>(null)
|
||||
const historyEpoch = useRef(0)
|
||||
const [historyRestoring, setHistoryRestoring] = useState(false)
|
||||
|
||||
const loadLive = useCallback(async () => {
|
||||
@@ -245,20 +247,26 @@ export default function IpsecPage() {
|
||||
|
||||
const historyServerId = effectiveServerId === ALL_SERVERS_ID ? null : effectiveServerId
|
||||
|
||||
useEffect(() => () => { historyEpoch.current += 1 }, [historyServerId, backendUrl, isLive])
|
||||
|
||||
const loadRevisions = useCallback(async () => {
|
||||
if (!isLive || !historyServerId) return
|
||||
const epoch = ++historyEpoch.current
|
||||
setHistoryLoading(true)
|
||||
setHistoryError(null)
|
||||
try {
|
||||
const res = await requestJson<{ revisions: ConfigRevisionDto[] }>(
|
||||
backendUrl,
|
||||
`/api/ipsec/revisions?serverId=${encodeURIComponent(historyServerId)}`,
|
||||
)
|
||||
if (epoch !== historyEpoch.current) return
|
||||
if (!Array.isArray(res?.revisions)) throw new Error("Некорректный ответ истории IPsec")
|
||||
setRevisions(res.revisions)
|
||||
} catch (err) {
|
||||
toast.error("Не удалось загрузить историю", { description: String(err) })
|
||||
setRevisions([])
|
||||
if (epoch !== historyEpoch.current) return
|
||||
setHistoryError(err instanceof Error ? err.message : "Не удалось загрузить историю")
|
||||
} finally {
|
||||
setHistoryLoading(false)
|
||||
if (epoch === historyEpoch.current) setHistoryLoading(false)
|
||||
}
|
||||
}, [isLive, historyServerId, backendUrl])
|
||||
|
||||
@@ -884,7 +892,9 @@ export default function IpsecPage() {
|
||||
|
||||
<ConfigHistorySheet
|
||||
open={historyOpen}
|
||||
onOpenChange={setHistoryOpen}
|
||||
onOpenChange={v => { if (!v) historyEpoch.current += 1; setHistoryOpen(v) }}
|
||||
loadError={historyError}
|
||||
onRetry={() => void loadRevisions()}
|
||||
revisions={revisions}
|
||||
loading={historyLoading}
|
||||
restoring={historyRestoring}
|
||||
|
||||
+2
-2
@@ -80,7 +80,7 @@ flowchart LR
|
||||
|
||||
## Текущая точка продолжения
|
||||
|
||||
По журналу этапы 0–8 завершены в границах локальной приёмки; внешние ограничения сохраняются. Этап 9 начат (блоки 9.1–9.11 — история и подтверждение восстановления); этапы 10–12 не начаты. Этап 11 разделён: 11А — пользователи, контейнеры, сертификаты, резервирование; 11Б — оповещения, сбор данных, интеграции, релизы, настройки.
|
||||
По журналу этапы 0–8 завершены в границах локальной приёмки; внешние ограничения сохраняются. Этап 9 начат (блоки 9.1–9.12 — история WireGuard/IPsec и восстановление); этапы 10–12 не начаты. Этап 11 разделён: 11А — пользователи, контейнеры, сертификаты, резервирование; 11Б — оповещения, сбор данных, интеграции, релизы, настройки.
|
||||
|
||||
Подробные планы: [этап 6](ux-ui-stage-6.md), [этап 7](ux-ui-stage-7.md), [этап 8 и итоговая матрица](ux-ui-stage-8.md), [этап 9](ux-ui-stage-9.md).
|
||||
|
||||
@@ -88,7 +88,7 @@ flowchart LR
|
||||
|
||||
Ближайший оставшийся объём:
|
||||
|
||||
1. Этап 9: карта разделов и схема сохранены; удаление, импорт, создание, переключение, контекст экспорта и выбор пира WireGuard исправлены в 9.1–9.7; в 9.8 проверено подтверждение восстановления. В 9.9 исправлены разметка и сохранение подтверждения при ошибке у всех шести потребителей. В 9.10 исправлены ошибки и поздние ответы загрузки истории WireGuard. В 9.11 подтверждение восстановления проверено у остальных пяти потребителей. Далее — их состояния загрузки истории: ошибка, повтор и поздние ответы; затем IPsec/GRE/VXLAN/Firewall/Filters.
|
||||
1. Этап 9: карта разделов и схема сохранены; удаление, импорт, создание, переключение, контекст экспорта и выбор пира WireGuard исправлены в 9.1–9.7; в 9.8 проверено подтверждение восстановления. В 9.9 исправлены разметка и сохранение подтверждения при ошибке у всех шести потребителей. В 9.10 исправлены ошибки и поздние ответы загрузки истории WireGuard. В 9.11 подтверждение восстановления проверено у остальных пяти потребителей. В 9.12 обработаны ошибка/повтор/поздние ответы истории IPsec. Далее — история GRE/Firewall/Filters/рекурсивных маршрутов и общая валидация ревизий; затем IPsec/GRE/VXLAN/Firewall/Filters.
|
||||
2. Сохранять контекст устройства, введённые значения при ошибке и подтверждать результат операций по API; локальные моки проверяют клиентские сценарии, реальные сетевые операции требуют тестового окружения.
|
||||
3. В этапе 12 выполнить полный аудит доступности (скринридер, контраст), производительности и сквозную регрессию. Ограничения этапа 8 — удалённые серверы месячного каталога, отсутствие экспорта и автоматического переподключения SSE — остаются явно зафиксированными, без заявления о реализации.
|
||||
|
||||
|
||||
@@ -25,12 +25,19 @@
|
||||
| 6 | Навигация и дашборд | Завершён 2026-09-20 в границах локальной приёмки; ограничения ниже |
|
||||
| 7 | Серверы и мониторинг: детали, формы, URL-контекст, права | Завершён в границах локальной приёмки 2026-09-23; внешние ограничения ниже |
|
||||
| 8 | Трафик и статистика | Завершён 2026-09-28 в границах локальной приёмки; ограничения в блоке 8.13 |
|
||||
| 9 | VPN, Firewall и Filters | В работе: блоки 9.1–9.11 — история и подтверждение восстановления |
|
||||
| 9 | VPN, Firewall и Filters | В работе: блоки 9.1–9.12 — история WireGuard/IPsec и восстановление |
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Не начат |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Не начат |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Не начат |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат |
|
||||
|
||||
## Блок 9.12 — ошибка и повтор истории IPsec (2026-09-29)
|
||||
|
||||
- IPsec подключён к loadError/onRetry общего диалога. Ошибка не обнуляет историю и не показывает пустое состояние; прежние ревизии скрыты до успешного повтора. Ответ без массива revisions отклоняется.
|
||||
- Поколение запроса и инвалидирование при закрытии, смене устройства/backend/режима или размонтировании блокируют устаревшие результат/ошибку/finally. Сетевые запросы физически не отменяются.
|
||||
- Все 7 E2E истории прошли: прежние 6 сценариев восстановления и новый IPsec (403, повтор с задержкой, закрытие/открытие, повреждённый ответ, успешный пустой список, поздняя ошибка). Lint/TypeScript проверены перед коммитом; diff-check чистый. Build не повторялся; последний — 9.9.
|
||||
- Ограничение: здесь проверен только контейнер revisions, полная схема отдельных ревизий и принадлежность серверу ещё не валидируются, в отличие от WireGuard. Реальные RouterOS/JWT не проверены. Далее — история GRE/Firewall/Filters/рекурсивных маршрутов и общая валидация ревизий. Этап 9 открыт. Блок начат при 87% расхода окна по прямому разрешению работать до конца лимита; локальный коммит без push.
|
||||
|
||||
## Блок 9.11 — подтверждение восстановления остальных разделов (2026-09-29)
|
||||
|
||||
- IPsec, GRE, Firewall, Filters и рекурсивные маршруты проверяют ok=true до изменения таблиц, обновления данных и сообщения успеха. Контракты всех пяти backend-маршрутов проверены по коду: они возвращают такое подтверждение. WireGuard уже использовал эту проверку.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Этап 9: VPN и сетевые операции
|
||||
|
||||
Статус: начат 2026-09-28, выполнены блоки 9.1–9.11. Основа — существующие ReUI PRO/base-nova, Frame, ServerTileRail, DataGrid и формы. Правила применения сетевой конфигурации не меняем.
|
||||
Статус: начат 2026-09-28, выполнены блоки 9.1–9.12. Основа — существующие ReUI PRO/base-nova, Frame, ServerTileRail, DataGrid и формы. Правила применения сетевой конфигурации не меняем.
|
||||
|
||||
## Схема и референсы принятого плана
|
||||
|
||||
@@ -85,3 +85,7 @@
|
||||
## Блок 9.11 — подтверждение восстановления остальных разделов (2026-09-29)
|
||||
|
||||
IPsec/GRE/Firewall/Filters/рекурсивные маршруты требуют ok=true до применения ответа в UI; WireGuard уже проверял подтверждение. 6 расширенных сценариев истории прошли (403, ok=false, пустой ответ, успех), как и TypeScript/lint теста/diff-check. Исходные ошибки lint страниц не изменились. Далее — ошибка/повтор/поздние ответы загрузки истории этих пяти разделов. Полная валидация rules/routes и реальная операция RouterOS остаются вне этого блока.
|
||||
|
||||
## Блок 9.12 — загрузка истории IPsec (2026-09-29)
|
||||
|
||||
Ошибка и повтор подключены к общей панели; поздние ответы закрытого/сменившего контекст запроса игнорируются. Проверяется наличие массива revisions, без полной валидации записей. Все 7 E2E истории прошли. Далее — GRE/Firewall/Filters/рекурсивные маршруты и общая схема ревизий. Реальные устройства не проверены.
|
||||
@@ -67,3 +67,48 @@ for (const section of ["wireguard", "ipsec", "gre", "firewall", "filters", "recu
|
||||
expect(errors.filter(error => !error.includes("403 (Forbidden)"))).toEqual([])
|
||||
})
|
||||
}
|
||||
|
||||
test("История IPsec: ошибка, повтор и поздний ответ закрытой панели", async ({ page }) => {
|
||||
let attempts = 0
|
||||
let release: (() => void) | undefined
|
||||
const gate = new Promise<void>(resolve => { release = resolve })
|
||||
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 (path === "/api/ipsec/revisions") {
|
||||
attempts++
|
||||
if (attempts === 1) return route.fulfill({ status: 403, json: { error: "История недоступна тест" } })
|
||||
if (attempts === 2) {
|
||||
await gate
|
||||
return route.fulfill({ status: 500, json: { error: "Устаревшая ошибка тест" } })
|
||||
}
|
||||
if (attempts === 3) return route.fulfill({ json: {} })
|
||||
return route.fulfill({ json: { revisions: [] } })
|
||||
}
|
||||
return route.fulfill({ json: path === "/api/auth/config" ? { required: false }
|
||||
: path === "/api/ipsec" ? { servers: [], clients: [] }
|
||||
: path === "/api/servers" ? [{ id: 1, name: "История роутер", host: "192.0.2.1", enabled: true, type: "exit-node", country: "RU", status: "online" }] : {} })
|
||||
})
|
||||
await page.goto("/ipsec")
|
||||
await page.getByRole("option").filter({ hasText: "История роутер" }).click()
|
||||
const open = page.getByRole("button", { name: "История", exact: true })
|
||||
const sheet = page.getByRole("dialog", { name: "История конфигураций IPsec" })
|
||||
await open.click()
|
||||
await expect(sheet.getByRole("alert")).toContainText("История недоступна тест")
|
||||
await expect(sheet.getByText("Истории пока нет", { exact: true })).toHaveCount(0)
|
||||
await sheet.getByRole("button", { name: "Повторить загрузку" }).click()
|
||||
await expect.poll(() => attempts).toBe(2)
|
||||
await expect(sheet.getByRole("status")).toContainText("Загрузка истории")
|
||||
await page.keyboard.press("Escape")
|
||||
await expect(sheet).toHaveCount(0)
|
||||
await open.click()
|
||||
await expect(sheet.getByRole("alert")).toContainText("Некорректный ответ истории IPsec")
|
||||
await sheet.getByRole("button", { name: "Повторить загрузку" }).click()
|
||||
await expect(sheet.getByText("Истории пока нет", { exact: true })).toBeVisible()
|
||||
const response = page.waitForResponse(r => r.url().includes("/api/ipsec/revisions?"))
|
||||
release!()
|
||||
await (await response).finished()
|
||||
await page.waitForTimeout(150)
|
||||
await expect(sheet.getByRole("alert")).toHaveCount(0)
|
||||
await expect(sheet.getByText("Истории пока нет", { exact: true })).toBeVisible()
|
||||
})
|
||||
Reference in new issue
Block a user