fix(ipsec): сохранять общий сертификат при удалении identity
This commit is contained in:
1 parent
58f5c48a97
commit
d52c48e5ed
14 files changed
+506
-14
No files matched your search
@@ -36,6 +36,7 @@ import {
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import { ApiClientError, requestJson } from "@/shared/api/http-client"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
createIpsecUser,
|
||||
deleteIpsecCert,
|
||||
@@ -214,6 +215,7 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
const [peerOpen, setPeerOpen] = useState(false)
|
||||
const [editingPeer, setEditingPeer] = useState<{ serverId: string; peer: IpsecPeerDto } | null>(null)
|
||||
const [pendingDelete, setPendingDelete] = useState<PendingDelete | null>(null)
|
||||
const [removeClientCertificate, setRemoveClientCertificate] = useState(true)
|
||||
const deleteLock = useRef(false)
|
||||
const [deleteError, setDeleteError] = useState<string | null>(null)
|
||||
const [forceAllowed, setForceAllowed] = useState(false)
|
||||
@@ -530,6 +532,10 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
: exportIpsecUserCert(backendUrl, certClient!.serverId, certClient!.rosId, passphrase))
|
||||
}
|
||||
|
||||
const deletingCertificateIdentity = pendingDelete?.kind === "user" && pendingDelete.client.kind === "cert" && pendingDelete.client.rosId.startsWith("identity:")
|
||||
const deletingSharedCertificate = deletingCertificateIdentity && pendingDelete?.kind === "user" && Boolean(pendingDelete.client.certName) && displayClients.some(c => c.serverId === pendingDelete.client.serverId && c.certName === pendingDelete.client.certName && c.rosId !== pendingDelete.client.rosId)
|
||||
const shouldRemoveClientCertificate = !deletingCertificateIdentity || (removeClientCertificate && !deletingSharedCertificate)
|
||||
|
||||
const confirmDelete = async (force = false) => {
|
||||
if (!pendingDelete || deleteLock.current || (force && !forceAllowed)) return
|
||||
const target = pendingDelete
|
||||
@@ -540,7 +546,7 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
setDeleteError(null)
|
||||
try {
|
||||
if (target.kind === "user") {
|
||||
await deleteIpsecUser(backendUrl, target.client.serverId, target.client.rosId)
|
||||
await deleteIpsecUser(backendUrl, target.client.serverId, target.client.rosId, { removeCertificate: shouldRemoveClientCertificate })
|
||||
} else if (target.kind === "server") {
|
||||
await deleteIpsecServer(backendUrl, target.summary.serverId)
|
||||
} else if (target.kind === "peer") {
|
||||
@@ -608,6 +614,7 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
case "server":
|
||||
return "Удалит managed-объекты (identity, mode-config, peer, пул, NAT) и сертификаты IKEv2 на этом роутере. Действие можно откатить через «Историю» (кроме сертификатов)."
|
||||
case "user":
|
||||
if (deletingCertificateIdentity && !shouldRemoveClientCertificate) return "Удалит только выбранную identity и её неиспользуемый персональный mode-config. Сертификат останется на роутере; другие identity сохранятся."
|
||||
return "Удалит identity, персональный mode-config и клиентский сертификат на роутере."
|
||||
case "peer":
|
||||
return "Удалит peer на роутере. Если на него ссылаются identity — потребуется принудительное удаление."
|
||||
@@ -792,7 +799,7 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
compactServer={compactServer}
|
||||
onDownloadCert={isLive ? (row) => void openCert(row) : undefined}
|
||||
onEdit={isLive ? (row) => { setEditing(row); setUserOpen(true) } : undefined}
|
||||
onDelete={isLive ? (row) => setPendingDelete({ kind: "user", client: row }) : undefined}
|
||||
onDelete={isLive ? (row) => { setRemoveClientCertificate(true); setPendingDelete({ kind: "user", client: row }) } : undefined}
|
||||
/>}
|
||||
</DataPageCard>
|
||||
</TabsContent>
|
||||
@@ -924,6 +931,15 @@ function IpsecPage({ selectedServerId, setSelectedServerId, workspaceTab, setWor
|
||||
</AlertDialogMedia>
|
||||
<AlertDialogTitle>{deleteDialogTitle}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{deleteDialogDescription}</AlertDialogDescription>
|
||||
{deletingCertificateIdentity && (
|
||||
<div className="space-y-2 text-left text-sm">
|
||||
<label htmlFor="ipsec-delete-certificate" className="flex items-center gap-2">
|
||||
<Checkbox id="ipsec-delete-certificate" checked={shouldRemoveClientCertificate} disabled={busy || deletingSharedCertificate} onCheckedChange={setRemoveClientCertificate} />
|
||||
Удалить также клиентский сертификат
|
||||
</label>
|
||||
{deletingSharedCertificate && <p className="text-muted-foreground">Сертификат используется другими identity и будет сохранён.</p>}
|
||||
</div>
|
||||
)}
|
||||
{busy && <p role="status" className="text-sm text-muted-foreground">Ожидание результата удаления…</p>}
|
||||
{deleteError && <p role="alert" className="text-sm text-destructive">{deleteError}</p>}
|
||||
</AlertDialogHeader>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: протокол внешней приёмки 12.5
|
||||
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.114 — один certificate больше не скрывает identity: отдельные IDs/имена/mode-config/static IP, общая сессия «Не определено». Certificate target с несколькими refs и удаление используемого сертификата дают409 до записи. State114/114, 8 профильных скриптов/build/ESLint, E2E18/18, live UI4/4. Backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Native matching conflict записан; PATCH200 после удаления собственного дубля, cleanup13×2 passed. Следующий115 — понятное удаление только identity с сохранением общего сертификата в UI;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.115 — UI удаления identity позволяет сохранить сертификат; при общих refs он unchecked/disabled и результат объяснён. Source TypeScript/ESLint/production frontend build, E2E26 unique accepted (22+4 corrected), live dialogs4/4 и один mobile DELETE с removeCertificate=false. Native cert ID/другая identity сохранены, original online восстановлен, cleanup13×2 passed. Backend ux-refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Следующий116 — исключить удаление сертификата, угаданного по имени клиента; затем итоговая регрессия.12.5 открыт.
|
||||
|
||||
IPsec78: реальный PSK responder established, mode-config198.18.78.10 и online=true подтверждены API; точные собственные объекты удалены. Сертификатный/DN online, late failure и history restore остаются открытыми. [Отчёт](ux-ui/acceptance/ipsec-mode-config-2026-10-09.json), [журнал](ux-ui-progress.md).
|
||||
|
||||
@@ -1153,3 +1153,21 @@ Backend только: ux-refs114/version1.0.0-test.refs114, base60b2e09/context
|
||||
| --- | --- |
|
||||
| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Сертификат → все identity → отдельные строки/настройки → выбор по identity ID |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → проверка ссылок → API409 без записи либо подтверждённая операция |
|
||||
|
||||
## 12.5.115 — IPsec: удалить identity и сохранить общий сертификат через UI (2026-10-10)
|
||||
|
||||
В существующий AlertDialog добавлен Checkbox «Удалить также клиентский сертификат» только для certificate identity с явным identity:<id>. По умолчанию выбран для единственной привязки; при других rows того же certName на том же serverId выключен/disabled. Сертификаты с одинаковым именем на другом сервере не считаются общими. Подтверждение объясняет сохранение сертификата и остальных identity; запрос передаёт removeCertificate=false. При API ошибке диалог/выбор/данные сохраняются, блокировки повторного нажатия прежние. Для cert-only row без identity нет ложной возможности «удалить клиента, сохранив единственный объект».
|
||||
|
||||
Изменены app/(main)/ipsec/page.tsx и tests/e2e/ipsec-delete.spec.ts. ReUI/base-nova, установленный AlertDialog и Checkbox переиспользованы; vendor/theme не менялись. Scoped ESLint и source TypeScript passed; production frontend build passed. Приняты26 unique E2E: первая профильная серия22 passed/4 failed из-за проверки getByRole фона при открытом модальном диалоге; corrected includeHidden проверка сохранности DOM rows дала4/4 rerun9.8s. Это26 уникальных, не30 сценариев.
|
||||
|
||||
Frontend ux-keepcert115/version1.0.0-test.keepcert115 развёрнут; backend ux-refs114/version1.0.0-test.refs114 сохранён. Source base58f5c48/context build-alias112-front плюс один page override, bytehash совпал. Label keepcert115 — метка этапа, неGitSHA; compiled frontend reproducibility не заявлена. Backend/PG healthy, frontend HTTP/browser passed, healthcheck у frontend отсутствует. Private rollback pre-keepcert115.env и прежний образ сохранены.
|
||||
|
||||
На собственном signed-certificate IKE fixture проверены4 live диалога (dark/light1280/320) без mocks; первые3 отменены, в light320 выполнен ровно один DELETE identity с removeCertificate=false. Native readback подтвердил отсутствие выбранной identity и сохранение точного certificate ID и другой identity. Исходный клиент восстановил online/198.18.115.250, unknown alert исчез. Последующие PATCH имени/возврат200 подтвердили восстановление. Чужой для приложения mode-config API не удалял: helper очистил только собственный fixture объект. История вызывается API с dedupe; точное число вставленных revisions не заявляется.
|
||||
|
||||
Собственные native объекты и private exports/imports очищены, исходные13 коллекций×2 совпали; маршруты/default/logging сохранены. Оба CHR/SSH доступны, пароли прежние. Screens4, просмотрен light320. Протоколы: [native/deployment](ux-ui/acceptance/ipsec-preserve-certificate-2026-10-10.json), [live UI](ux-ui/acceptance/browser-ipsec-keepcert-2026-10-10.json).
|
||||
|
||||
Следующий116: default user DELETE ещё угадывает имя сертификата для identity без remote-certificate; проверить/исключить случай PSK + отдельный cert с совпавшим именем. После последнего исправления — единая общая регрессия. Внешние Portal/Telegram/client OS/multi-WAN/scale и множественные native IKE sessions остаются в матрице.
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → прямой объём удаления → сохранить общий сертификат → подтверждённый API результат |
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.114 — один certificate больше не скрывает identity: отдельные IDs/имена/mode-config/static IP, общая сессия «Не определено». Certificate target с несколькими refs и удаление используемого сертификата дают409 до записи. State114/114, 8 профильных скриптов/build/ESLint, E2E18/18, live UI4/4. Backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Native matching conflict записан; PATCH200 после удаления собственного дубля, cleanup13×2 passed. Следующий115 — понятное удаление только identity с сохранением общего сертификата в UI;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.115 — UI удаления identity позволяет сохранить сертификат; при общих refs он unchecked/disabled и результат объяснён. Source TypeScript/ESLint/production frontend build, E2E26 unique accepted (22+4 corrected), live dialogs4/4 и один mobile DELETE с removeCertificate=false. Native cert ID/другая identity сохранены, original online восстановлен, cleanup13×2 passed. Backend ux-refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Следующий116 — исключить удаление сертификата, угаданного по имени клиента; затем итоговая регрессия.12.5 открыт.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-10. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-10
|
||||
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.114 — один certificate больше не скрывает identity: отдельные IDs/имена/mode-config/static IP, общая сессия «Не определено». Certificate target с несколькими refs и удаление используемого сертификата дают409 до записи. State114/114, 8 профильных скриптов/build/ESLint, E2E18/18, live UI4/4. Backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Native matching conflict записан; PATCH200 после удаления собственного дубля, cleanup13×2 passed. Следующий115 — понятное удаление только identity с сохранением общего сертификата в UI;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.115 — UI удаления identity позволяет сохранить сертификат; при общих refs он unchecked/disabled и результат объяснён. Source TypeScript/ESLint/production frontend build, E2E26 unique accepted (22+4 corrected), live dialogs4/4 и один mobile DELETE с removeCertificate=false. Native cert ID/другая identity сохранены, original online восстановлен, cleanup13×2 passed. Backend ux-refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Следующий116 — исключить удаление сертификата, угаданного по имени клиента; затем итоговая регрессия.12.5 открыт.
|
||||
|
||||
Этапы0–11 завершены в локальных границах журнала. История выполненных внешних блоков и доказательства сохранены в [журнале](ux-ui-stage-12.md) и [протоколе](ux-ui-external-acceptance.md). Реальная Telegram доставка, Portal SSO, multi-instance и оставшиеся профильные сетевые ограничения открыты.
|
||||
|
||||
|
||||
+20
-2
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.114 — один certificate больше не скрывает identity: отдельные IDs/имена/mode-config/static IP, общая сессия «Не определено». Certificate target с несколькими refs и удаление используемого сертификата дают409 до записи. State114/114, 8 профильных скриптов/build/ESLint, E2E18/18, live UI4/4. Backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Native matching conflict записан; PATCH200 после удаления собственного дубля, cleanup13×2 passed. Следующий115 — понятное удаление только identity с сохранением общего сертификата в UI;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.115 — UI удаления identity позволяет сохранить сертификат; при общих refs он unchecked/disabled и результат объяснён. Source TypeScript/ESLint/production frontend build, E2E26 unique accepted (22+4 corrected), live dialogs4/4 и один mobile DELETE с removeCertificate=false. Native cert ID/другая identity сохранены, original online восстановлен, cleanup13×2 passed. Backend ux-refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Следующий116 — исключить удаление сертификата, угаданного по имени клиента; затем итоговая регрессия.12.5 открыт.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
|
||||
|
||||
@@ -36,7 +36,25 @@
|
||||
| 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–114 и ограничения ниже. Runtime backend ux-refs114/frontend ux-alias112-version. |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–115 и ограничения ниже. Runtime backend ux-refs114/frontend ux-keepcert115. |
|
||||
|
||||
## 12.5.115 — IPsec: удалить identity и сохранить общий сертификат через UI (2026-10-10)
|
||||
|
||||
В существующий AlertDialog добавлен Checkbox «Удалить также клиентский сертификат» только для certificate identity с явным identity:<id>. По умолчанию выбран для единственной привязки; при других rows того же certName на том же serverId выключен/disabled. Сертификаты с одинаковым именем на другом сервере не считаются общими. Подтверждение объясняет сохранение сертификата и остальных identity; запрос передаёт removeCertificate=false. При API ошибке диалог/выбор/данные сохраняются, блокировки повторного нажатия прежние. Для cert-only row без identity нет ложной возможности «удалить клиента, сохранив единственный объект».
|
||||
|
||||
Изменены app/(main)/ipsec/page.tsx и tests/e2e/ipsec-delete.spec.ts. ReUI/base-nova, установленный AlertDialog и Checkbox переиспользованы; vendor/theme не менялись. Scoped ESLint и source TypeScript passed; production frontend build passed. Приняты26 unique E2E: первая профильная серия22 passed/4 failed из-за проверки getByRole фона при открытом модальном диалоге; corrected includeHidden проверка сохранности DOM rows дала4/4 rerun9.8s. Это26 уникальных, не30 сценариев.
|
||||
|
||||
Frontend ux-keepcert115/version1.0.0-test.keepcert115 развёрнут; backend ux-refs114/version1.0.0-test.refs114 сохранён. Source base58f5c48/context build-alias112-front плюс один page override, bytehash совпал. Label keepcert115 — метка этапа, неGitSHA; compiled frontend reproducibility не заявлена. Backend/PG healthy, frontend HTTP/browser passed, healthcheck у frontend отсутствует. Private rollback pre-keepcert115.env и прежний образ сохранены.
|
||||
|
||||
На собственном signed-certificate IKE fixture проверены4 live диалога (dark/light1280/320) без mocks; первые3 отменены, в light320 выполнен ровно один DELETE identity с removeCertificate=false. Native readback подтвердил отсутствие выбранной identity и сохранение точного certificate ID и другой identity. Исходный клиент восстановил online/198.18.115.250, unknown alert исчез. Последующие PATCH имени/возврат200 подтвердили восстановление. Чужой для приложения mode-config API не удалял: helper очистил только собственный fixture объект. История вызывается API с dedupe; точное число вставленных revisions не заявляется.
|
||||
|
||||
Собственные native объекты и private exports/imports очищены, исходные13 коллекций×2 совпали; маршруты/default/logging сохранены. Оба CHR/SSH доступны, пароли прежние. Screens4, просмотрен light320. Протоколы: [native/deployment](ux-ui/acceptance/ipsec-preserve-certificate-2026-10-10.json), [live UI](ux-ui/acceptance/browser-ipsec-keepcert-2026-10-10.json).
|
||||
|
||||
Следующий116: default user DELETE ещё угадывает имя сертификата для identity без remote-certificate; проверить/исключить случай PSK + отдельный cert с совпавшим именем. После последнего исправления — единая общая регрессия. Внешние Portal/Telegram/client OS/multi-WAN/scale и множественные native IKE sessions остаются в матрице.
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → прямой объём удаления → сохранить общий сертификат → подтверждённый API результат |
|
||||
|
||||
## 12.5.114 — IPsec: общий сертификат, разные identity и безопасный target (2026-10-10)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Оставшаяся внешняя приёмка — 2026-10-10
|
||||
|
||||
Это актуальная матрица после 12.5.114. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
Это актуальная матрица после 12.5.115. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
|
||||
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-refs114`, frontend `ux-alias112-version`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-refs114`, frontend `ux-keepcert115`; production lab использует AUTH_REQUIRED=false. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
|
||||
|
||||
| Раздел | Подтверждено | Осталось / условие завершения |
|
||||
| --- | --- | --- |
|
||||
@@ -12,7 +12,7 @@
|
||||
| GRE | Native lifecycle, metadata, live формы/меню/URL/Ping53–57; GRE используется в OSPF/BGP/IPsec последующих блоков; native IPv6 ULA inner109 | IPv6 outer и application IPv6 interface address lifecycle, независимые WAN, масштаб и длительное восстановление; принятие GRE не означает приёмку всех сценариев автоматического расписания |
|
||||
| WireGuard | Native handshake/data plane58, deletion60, live create/peer/disable/enable/delete62, mobile64, export/import67–74; native IPv6 ULA inner/allowedIps/handshake109 | Клиентские ОС и произвольные конфигурации, IPv6 outer/scale и application IPv6 interface address lifecycle, физический lost ACK и устойчивое восстановление; `.conf` с placeholders — шаблон, не принятый клиентский tunnel |
|
||||
| VXLAN | Собственная пара/private Ping/catalog/URL/export75–76; bridge/FDB/port loss/recovery107; IPv6 ULA inner по IPv4 outer108 | API приложения read-only: create/delete проверены native. IPv6 outer, масштаб/производительность, профильные mutation RBAC/concurrency отдельно не приняты |
|
||||
| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113; shared certificate rows/target guards114 | Реальные одновременные sessions с одним ID, UI удаления identity с сохранением общего сертификата; native edit identical matching rules rejected; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput |
|
||||
| IPsec | PSK и сертификатное шифрование77–82, active peer/online/mode-config, scoped NAT/group, partial/unknown/applied receipts, restore conflicts, JWT; full DN/специальные символы/online110; literal CN namespace guards и explicit FQDN111; certificate/PSK shared ID unknown/recovery112; multiple sessions controlled snapshots113; shared certificate rows/target guards114; preserve-certificate UI/native DELETE115 | Реальные одновременные sessions с одним ID, Связь удаляемого certificate с identity без name-based guessing; native edit identical matching rules rejected; клиентские Windows/macOS/iOS/Android, прочие DN форматы/custom OIDs/IKE ID namespaces, полная PKI и policy/profile/proposal restore, физический disconnect, durable retry/multi-instance, IPv6/maximum throughput |
|
||||
| OSPF/BFD | Real Full/own routes84–85, раздельные наблюдения BFD86, effective disabled/optimizer cost/partial/RBAC87, alternate path90 и CIDR91 | Sustained BFD-only gate не принят: в86 сосед мог вновь статьFull при доступныхHello. В90 переходный Ping18/40 с55% loss: бесшовность не принята. REST timing — наблюдаемая граница, не SLA; IPv6/scale/independent WAN открыты |
|
||||
| BGP | Established83, exact /32 exchange/API/UI/administrative withdrawal92, BFD primary GRE loss/alternate/recovery93 | Hold timer/TCP loss безBFD принят104; packet loss в переходе, разные внешние peers/WAN policy, IPv6/scale. Два логических пути используют тот же public WAN; physical redundancy не принята |
|
||||
| Recursive routes | Production PUT/change/own history restore88, disabled guards, partial/unknown/applied outcomes, live формы/контекст | Автоматический rollback/retry, durable receipts и несколько экземпляров отсутствуют; PUT не является idempotent noop. Полная произвольная legacy catalog миграция не принята |
|
||||
@@ -25,7 +25,7 @@
|
||||
## Следующая последовательность
|
||||
|
||||
1. **12.5.95 завершён:** [source/context/compiled audit](ux-ui/acceptance/deployment-source-audit-2026-10-10.json), production source drift не выявлен; нормализация и исключения явно указаны.
|
||||
2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — repeated native ID groups исправлены на controlled snapshots; 114 — multiple refs/target guards/native conflict приняты в описанных границах. Следующий115 — UI сохранения общего сертификата при удалении identity. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение.
|
||||
2. **12.5.96 timezone завершён:** [6 live UI cases](ux-ui/acceptance/browser-speed-timezone-2026-10-10.json). 97 HTTP receipt disconnect принят: [протокол](ux-ui/acceptance/speed-disconnect-2026-10-10.json). 98 сообщение неизвестного native результата исправлено/развёрнуто. 99 main/context завершён с точной записью одного failure/delta;100 transport и101 accessibility исправлены/развёрнуты.102 malformed PATCH исправлен/принят.103 packet rows/public/GRE приняты в границах коротких samples; initial loss/root cause остаётся открытым.104 hold/TCP loss/recovery принят.105 source/runtime/evidence сверка passed.106 read-only readiness: auth disabled/Portal localhost/bot and chat unconfigured.107 — native bridge/FDB/loss/recovery принят.108 — IPv6 inner VXLAN принят, initial0/3 и1/3 остаются в протоколе;109 — native GRE/WireGuard IPv6 inner принят; readers /ip/address не показывают native IPv6 interface addresses.110 — полный native DN/online исправлен и принят;111 — literal CN guards/real explicit FQDN accepted;112 — certificate/PSK alias ambiguity marked unknown/recovery accepted;113 — repeated native ID groups исправлены на controlled snapshots; 114 — multiple refs/target guards/native conflict приняты в описанных границах. 115 — UI сохранения сертификата/native DELETE принят; следующий116 — запрет name-based guessing сертификата. Реальные интеграции требуют тестовых доступов и отдельного разрешения на сообщение.
|
||||
3. **Заключительный профильный контроль:** после последнего исправления собрать только затронутую часть, проверить её live и обновить матрицу. Общую регрессию выполнить один раз на согласованном runtime.
|
||||
4. **Внешние зависимости:** Telegram/Portal/client OS/multi-WAN/scale проходят отдельную приёмку при наличии нужных аккаунтов, устройств и разрешений. Лабораторный результат двух CHR не закрывает их автоматически.
|
||||
|
||||
@@ -68,3 +68,21 @@ Backend только: ux-refs114/version1.0.0-test.refs114, base60b2e09/context
|
||||
| --- | --- |
|
||||
| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Сертификат → все identity → отдельные строки/настройки → выбор по identity ID |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → проверка ссылок → API409 без записи либо подтверждённая операция |
|
||||
|
||||
## 12.5.115 — IPsec: удалить identity и сохранить общий сертификат через UI (2026-10-10)
|
||||
|
||||
В существующий AlertDialog добавлен Checkbox «Удалить также клиентский сертификат» только для certificate identity с явным identity:<id>. По умолчанию выбран для единственной привязки; при других rows того же certName на том же serverId выключен/disabled. Сертификаты с одинаковым именем на другом сервере не считаются общими. Подтверждение объясняет сохранение сертификата и остальных identity; запрос передаёт removeCertificate=false. При API ошибке диалог/выбор/данные сохраняются, блокировки повторного нажатия прежние. Для cert-only row без identity нет ложной возможности «удалить клиента, сохранив единственный объект».
|
||||
|
||||
Изменены app/(main)/ipsec/page.tsx и tests/e2e/ipsec-delete.spec.ts. ReUI/base-nova, установленный AlertDialog и Checkbox переиспользованы; vendor/theme не менялись. Scoped ESLint и source TypeScript passed; production frontend build passed. Приняты26 unique E2E: первая профильная серия22 passed/4 failed из-за проверки getByRole фона при открытом модальном диалоге; corrected includeHidden проверка сохранности DOM rows дала4/4 rerun9.8s. Это26 уникальных, не30 сценариев.
|
||||
|
||||
Frontend ux-keepcert115/version1.0.0-test.keepcert115 развёрнут; backend ux-refs114/version1.0.0-test.refs114 сохранён. Source base58f5c48/context build-alias112-front плюс один page override, bytehash совпал. Label keepcert115 — метка этапа, неGitSHA; compiled frontend reproducibility не заявлена. Backend/PG healthy, frontend HTTP/browser passed, healthcheck у frontend отсутствует. Private rollback pre-keepcert115.env и прежний образ сохранены.
|
||||
|
||||
На собственном signed-certificate IKE fixture проверены4 live диалога (dark/light1280/320) без mocks; первые3 отменены, в light320 выполнен ровно один DELETE identity с removeCertificate=false. Native readback подтвердил отсутствие выбранной identity и сохранение точного certificate ID и другой identity. Исходный клиент восстановил online/198.18.115.250, unknown alert исчез. Последующие PATCH имени/возврат200 подтвердили восстановление. Чужой для приложения mode-config API не удалял: helper очистил только собственный fixture объект. История вызывается API с dedupe; точное число вставленных revisions не заявляется.
|
||||
|
||||
Собственные native объекты и private exports/imports очищены, исходные13 коллекций×2 совпали; маршруты/default/logging сохранены. Оба CHR/SSH доступны, пароли прежние. Screens4, просмотрен light320. Протоколы: [native/deployment](ux-ui/acceptance/ipsec-preserve-certificate-2026-10-10.json), [live UI](ux-ui/acceptance/browser-ipsec-keepcert-2026-10-10.json).
|
||||
|
||||
Следующий116: default user DELETE ещё угадывает имя сертификата для identity без remote-certificate; проверить/исключить случай PSK + отдельный cert с совпавшим именем. После последнего исправления — единая общая регрессия. Внешние Portal/Telegram/client OS/multi-WAN/scale и множественные native IKE sessions остаются в матрице.
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → прямой объём удаления → сохранить общий сертификат → подтверждённый API результат |
|
||||
+19
-1
@@ -1,6 +1,6 @@
|
||||
# Этап 12: итоговая приёмка UX/UI
|
||||
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.114 — один certificate больше не скрывает identity: отдельные IDs/имена/mode-config/static IP, общая сессия «Не определено». Certificate target с несколькими refs и удаление используемого сертификата дают409 до записи. State114/114, 8 профильных скриптов/build/ESLint, E2E18/18, live UI4/4. Backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Native matching conflict записан; PATCH200 после удаления собственного дубля, cleanup13×2 passed. Следующий115 — понятное удаление только identity с сохранением общего сертификата в UI;12.5 открыт.
|
||||
Актуальное продолжение 2026-10-10: завершён12.5.115 — UI удаления identity позволяет сохранить сертификат; при общих refs он unchecked/disabled и результат объяснён. Source TypeScript/ESLint/production frontend build, E2E26 unique accepted (22+4 corrected), live dialogs4/4 и один mobile DELETE с removeCertificate=false. Native cert ID/другая identity сохранены, original online восстановлен, cleanup13×2 passed. Backend ux-refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Следующий116 — исключить удаление сертификата, угаданного по имени клиента; затем итоговая регрессия.12.5 открыт.
|
||||
|
||||
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
|
||||
|
||||
@@ -2458,3 +2458,21 @@ Backend только: ux-refs114/version1.0.0-test.refs114, base60b2e09/context
|
||||
| --- | --- |
|
||||
| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Сертификат → все identity → отдельные строки/настройки → выбор по identity ID |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → проверка ссылок → API409 без записи либо подтверждённая операция |
|
||||
|
||||
## 12.5.115 — IPsec: удалить identity и сохранить общий сертификат через UI (2026-10-10)
|
||||
|
||||
В существующий AlertDialog добавлен Checkbox «Удалить также клиентский сертификат» только для certificate identity с явным identity:<id>. По умолчанию выбран для единственной привязки; при других rows того же certName на том же serverId выключен/disabled. Сертификаты с одинаковым именем на другом сервере не считаются общими. Подтверждение объясняет сохранение сертификата и остальных identity; запрос передаёт removeCertificate=false. При API ошибке диалог/выбор/данные сохраняются, блокировки повторного нажатия прежние. Для cert-only row без identity нет ложной возможности «удалить клиента, сохранив единственный объект».
|
||||
|
||||
Изменены app/(main)/ipsec/page.tsx и tests/e2e/ipsec-delete.spec.ts. ReUI/base-nova, установленный AlertDialog и Checkbox переиспользованы; vendor/theme не менялись. Scoped ESLint и source TypeScript passed; production frontend build passed. Приняты26 unique E2E: первая профильная серия22 passed/4 failed из-за проверки getByRole фона при открытом модальном диалоге; corrected includeHidden проверка сохранности DOM rows дала4/4 rerun9.8s. Это26 уникальных, не30 сценариев.
|
||||
|
||||
Frontend ux-keepcert115/version1.0.0-test.keepcert115 развёрнут; backend ux-refs114/version1.0.0-test.refs114 сохранён. Source base58f5c48/context build-alias112-front плюс один page override, bytehash совпал. Label keepcert115 — метка этапа, неGitSHA; compiled frontend reproducibility не заявлена. Backend/PG healthy, frontend HTTP/browser passed, healthcheck у frontend отсутствует. Private rollback pre-keepcert115.env и прежний образ сохранены.
|
||||
|
||||
На собственном signed-certificate IKE fixture проверены4 live диалога (dark/light1280/320) без mocks; первые3 отменены, в light320 выполнен ровно один DELETE identity с removeCertificate=false. Native readback подтвердил отсутствие выбранной identity и сохранение точного certificate ID и другой identity. Исходный клиент восстановил online/198.18.115.250, unknown alert исчез. Последующие PATCH имени/возврат200 подтвердили восстановление. Чужой для приложения mode-config API не удалял: helper очистил только собственный fixture объект. История вызывается API с dedupe; точное число вставленных revisions не заявляется.
|
||||
|
||||
Собственные native объекты и private exports/imports очищены, исходные13 коллекций×2 совпали; маршруты/default/logging сохранены. Оба CHR/SSH доступны, пароли прежние. Screens4, просмотрен light320. Протоколы: [native/deployment](ux-ui/acceptance/ipsec-preserve-certificate-2026-10-10.json), [live UI](ux-ui/acceptance/browser-ipsec-keepcert-2026-10-10.json).
|
||||
|
||||
Следующий116: default user DELETE ещё угадывает имя сертификата для identity без remote-certificate; проверить/исключить случай PSK + отдельный cert с совпавшим именем. После последнего исправления — единая общая регрессия. Внешние Portal/Telegram/client OS/multi-WAN/scale и множественные native IKE sessions остаются в матрице.
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → прямой объём удаления → сохранить общий сертификат → подтверждённый API результат |
|
||||
@@ -1,6 +1,6 @@
|
||||
# Тестовый сервер UX/UI — 2026-10-10
|
||||
|
||||
Сервер161.104.46.222: backend ux-refs114/version1.0.0-test.refs114, frontend ux-alias112-version/version1.0.0-test.alias112. Backend/PG healthy; native shared-certificate guard и UI4/4 passed. RouterOS matching conflict зафиксирован, PATCH200 после удаления собственного дубля. Cleanup13×2 passed; оба CHR доступны, пароли прежние. Следующий115 — UI удаления identity с сохранением сертификата.
|
||||
Сервер161.104.46.222: backend ux-refs114/version1.0.0-test.refs114, frontend ux-keepcert115/version1.0.0-test.keepcert115. Backend/PG healthy, четыре live подтверждения и один mobile DELETE identity с сохранением общего certificate ID passed; cleanup13×2. Оба CHR доступны, пароли прежние. Следующий116 — исключить name-based удаление несвязанного сертификата.
|
||||
|
||||
| Параметр | Проверенное состояние |
|
||||
| --- | --- |
|
||||
@@ -14,7 +14,7 @@
|
||||
| PostgreSQL | postgres:18-alpine,18.6;mmapp-test-postgres healthy;SELECT version() passed |
|
||||
| Данные | Отдельный Compose project mmapp-test,том mmapp-test_mmapp-pgdata и каталог ./data |
|
||||
| Доступ | PostgreSQL не публикуется;frontend3000/backend8000 привязаны к127.0.0.1 после запуска |
|
||||
| Образы приложения | mmapp-test/backend:ux-refs114 и mmapp-test/frontend:ux-alias112-version; запущены |
|
||||
| Образы приложения | mmapp-test/backend:ux-refs114 и mmapp-test/frontend:ux-keepcert115; запущены |
|
||||
|
||||
Compose SHA256:3052bd6ab5d9865f97605cebfb5ff27a4410cd3357b81153a65a87fb7c92582f. OptionalBACKEND_IMAGE_TAG позволяетобновлятьbackendотдельно. Локальный и удалённый файлы совпали. Шаблоны: [Compose](../deploy/docker-compose.test.yml), [env без секретов](../deploy/env.test.example).
|
||||
|
||||
@@ -884,3 +884,21 @@ Backend только: ux-refs114/version1.0.0-test.refs114, base60b2e09/context
|
||||
| --- | --- |
|
||||
| [ReUI Filtering2](https://reui.io/preview/base/data-grid-filtering-2?ref=mcp) | Сертификат → все identity → отдельные строки/настройки → выбор по identity ID |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → проверка ссылок → API409 без записи либо подтверждённая операция |
|
||||
|
||||
## 12.5.115 — IPsec: удалить identity и сохранить общий сертификат через UI (2026-10-10)
|
||||
|
||||
В существующий AlertDialog добавлен Checkbox «Удалить также клиентский сертификат» только для certificate identity с явным identity:<id>. По умолчанию выбран для единственной привязки; при других rows того же certName на том же serverId выключен/disabled. Сертификаты с одинаковым именем на другом сервере не считаются общими. Подтверждение объясняет сохранение сертификата и остальных identity; запрос передаёт removeCertificate=false. При API ошибке диалог/выбор/данные сохраняются, блокировки повторного нажатия прежние. Для cert-only row без identity нет ложной возможности «удалить клиента, сохранив единственный объект».
|
||||
|
||||
Изменены app/(main)/ipsec/page.tsx и tests/e2e/ipsec-delete.spec.ts. ReUI/base-nova, установленный AlertDialog и Checkbox переиспользованы; vendor/theme не менялись. Scoped ESLint и source TypeScript passed; production frontend build passed. Приняты26 unique E2E: первая профильная серия22 passed/4 failed из-за проверки getByRole фона при открытом модальном диалоге; corrected includeHidden проверка сохранности DOM rows дала4/4 rerun9.8s. Это26 уникальных, не30 сценариев.
|
||||
|
||||
Frontend ux-keepcert115/version1.0.0-test.keepcert115 развёрнут; backend ux-refs114/version1.0.0-test.refs114 сохранён. Source base58f5c48/context build-alias112-front плюс один page override, bytehash совпал. Label keepcert115 — метка этапа, неGitSHA; compiled frontend reproducibility не заявлена. Backend/PG healthy, frontend HTTP/browser passed, healthcheck у frontend отсутствует. Private rollback pre-keepcert115.env и прежний образ сохранены.
|
||||
|
||||
На собственном signed-certificate IKE fixture проверены4 live диалога (dark/light1280/320) без mocks; первые3 отменены, в light320 выполнен ровно один DELETE identity с removeCertificate=false. Native readback подтвердил отсутствие выбранной identity и сохранение точного certificate ID и другой identity. Исходный клиент восстановил online/198.18.115.250, unknown alert исчез. Последующие PATCH имени/возврат200 подтвердили восстановление. Чужой для приложения mode-config API не удалял: helper очистил только собственный fixture объект. История вызывается API с dedupe; точное число вставленных revisions не заявляется.
|
||||
|
||||
Собственные native объекты и private exports/imports очищены, исходные13 коллекций×2 совпали; маршруты/default/logging сохранены. Оба CHR/SSH доступны, пароли прежние. Screens4, просмотрен light320. Протоколы: [native/deployment](ux-ui/acceptance/ipsec-preserve-certificate-2026-10-10.json), [live UI](ux-ui/acceptance/browser-ipsec-keepcert-2026-10-10.json).
|
||||
|
||||
Следующий116: default user DELETE ещё угадывает имя сертификата для identity без remote-certificate; проверить/исключить случай PSK + отдельный cert с совпавшим именем. После последнего исправления — единая общая регрессия. Внешние Portal/Telegram/client OS/multi-WAN/scale и множественные native IKE sessions остаются в матрице.
|
||||
|
||||
| Референс | Схема |
|
||||
| --- | --- |
|
||||
| [ReUI Sheet8](https://reui.io/preview/base/sheet-8?ref=mcp) | Выбранная identity → прямой объём удаления → сохранить общий сертификат → подтверждённый API результат |
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"date": "2026-10-10",
|
||||
"runtime": {
|
||||
"backend": "ux-refs114",
|
||||
"frontend": "ux-keepcert115"
|
||||
},
|
||||
"httpMocks": false,
|
||||
"cases": [
|
||||
{
|
||||
"theme": "dark",
|
||||
"width": 1280,
|
||||
"sharedCertificateUncheckedAndDisabled": true,
|
||||
"scopeExplained": true,
|
||||
"noDialogOverflow": true,
|
||||
"confirmedDelete": false
|
||||
},
|
||||
{
|
||||
"theme": "dark",
|
||||
"width": 320,
|
||||
"sharedCertificateUncheckedAndDisabled": true,
|
||||
"scopeExplained": true,
|
||||
"noDialogOverflow": true,
|
||||
"confirmedDelete": false
|
||||
},
|
||||
{
|
||||
"theme": "light",
|
||||
"width": 1280,
|
||||
"sharedCertificateUncheckedAndDisabled": true,
|
||||
"scopeExplained": true,
|
||||
"noDialogOverflow": true,
|
||||
"confirmedDelete": false
|
||||
},
|
||||
{
|
||||
"theme": "light",
|
||||
"width": 320,
|
||||
"sharedCertificateUncheckedAndDisabled": true,
|
||||
"scopeExplained": true,
|
||||
"noDialogOverflow": true,
|
||||
"confirmedDelete": true
|
||||
}
|
||||
],
|
||||
"pageErrors": [],
|
||||
"applicationWrites": [
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/ipsec/users/1/identity%3A*B",
|
||||
"query": "?removeCertificate=false"
|
||||
}
|
||||
],
|
||||
"scope": "Four real confirmation dialogs, one mobile UI DELETE preserving shared certificate"
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
{
|
||||
"date": "2026-10-10",
|
||||
"block": "12.5.115",
|
||||
"deployment": {
|
||||
"date": "2026-10-10",
|
||||
"baseCommit": "58f5c48",
|
||||
"frontendBaseContext": "build-alias112-front",
|
||||
"frontendContext": "build-keepcert115-front",
|
||||
"override": "app/(main)/ipsec/page.tsx",
|
||||
"sourceSha256": "9e2fb7457410a837e0d9f2b0cbdb4201a0874b039f0a2f0202cc68bab6ebfd5c",
|
||||
"containers": {
|
||||
"backend": {
|
||||
"imageTag": "mmapp-test/backend:ux-refs114",
|
||||
"imageId": "sha256:df345a34f4e178b8a15fe5c537173f33a79eb15681a5e62b76a4aa7dab0017a6",
|
||||
"state": "running",
|
||||
"health": "healthy"
|
||||
},
|
||||
"frontend": {
|
||||
"imageTag": "mmapp-test/frontend:ux-keepcert115",
|
||||
"imageId": "sha256:98cd215a1c944f0883b0676d58a2df93ac689f488343b70b8360594848df81cf",
|
||||
"state": "running",
|
||||
"health": null
|
||||
},
|
||||
"postgres": {
|
||||
"imageTag": "postgres:18-alpine",
|
||||
"imageId": "sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873",
|
||||
"state": "running",
|
||||
"health": "healthy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceByteHashMatches": true,
|
||||
"native": {
|
||||
"date": "2026-10-10",
|
||||
"block": "12.5.115",
|
||||
"name": "mm-ipsec115-1791641218646",
|
||||
"cn": "mm-ipsec115-1791641218646",
|
||||
"observations": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"active": [
|
||||
{
|
||||
".id": "*1",
|
||||
"id": "CN=mm-ipsec115-1791641218646",
|
||||
"state": "established",
|
||||
"dynamic-address": "198.18.115.250",
|
||||
"remote-address": "104.165.244.7",
|
||||
"uptime": "1s"
|
||||
}
|
||||
],
|
||||
"catalog": [
|
||||
{
|
||||
"name": "CN=mm-ipsec115-1791641218646",
|
||||
"commonName": "CN=mm-ipsec115-1791641218646",
|
||||
"certName": "ipsec-user-mm-ipsec115-1791641218646-decoy",
|
||||
"online": false
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646",
|
||||
"commonName": "mm-ipsec115-1791641218646",
|
||||
"certName": "ipsec-user-mm-ipsec115-1791641218646",
|
||||
"online": true,
|
||||
"activeAddress": "198.18.115.250"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"active": [
|
||||
{
|
||||
".id": "*1",
|
||||
"id": "194.226.121.150",
|
||||
"state": "established",
|
||||
"remote-address": "194.226.121.150",
|
||||
"uptime": "1s"
|
||||
}
|
||||
],
|
||||
"catalog": []
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
{
|
||||
"serverId": 1,
|
||||
"/system/logging": true,
|
||||
"/ip/ipsec/identity": true,
|
||||
"/ip/ipsec/peer": true,
|
||||
"/ip/ipsec/policy": true,
|
||||
"/ip/ipsec/policy/group": true,
|
||||
"/ip/ipsec/mode-config": true,
|
||||
"/ip/ipsec/proposal": true,
|
||||
"/ip/ipsec/profile": true,
|
||||
"/certificate": true,
|
||||
"/file": true,
|
||||
"/ip/address": true,
|
||||
"/ip/route": true,
|
||||
"/ip/firewall/nat": true
|
||||
},
|
||||
{
|
||||
"serverId": 2,
|
||||
"/system/logging": true,
|
||||
"/ip/ipsec/identity": true,
|
||||
"/ip/ipsec/peer": true,
|
||||
"/ip/ipsec/policy": true,
|
||||
"/ip/ipsec/policy/group": true,
|
||||
"/ip/ipsec/mode-config": true,
|
||||
"/ip/ipsec/proposal": true,
|
||||
"/ip/ipsec/profile": true,
|
||||
"/certificate": true,
|
||||
"/file": true,
|
||||
"/ip/address": true,
|
||||
"/ip/route": true,
|
||||
"/ip/firewall/nat": true
|
||||
}
|
||||
],
|
||||
"databaseWrites": "Two successful API mutations invoke history recording; deduplication may avoid new rows",
|
||||
"certificate": {
|
||||
"name": "ipsec-user-mm-ipsec115-1791641218646",
|
||||
"common-name": "mm-ipsec115-1791641218646",
|
||||
"ca": "mm-ipsec115-1791641218646-ca",
|
||||
"key-usage": "digital-signature,key-encipherment,tls-client"
|
||||
},
|
||||
"certificates": [
|
||||
[
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646-ca",
|
||||
"trusted": "true",
|
||||
"trust-store": "all",
|
||||
"invalid-before": "2026-10-10 17:06:59",
|
||||
"invalid-after": "2026-10-12 17:06:59",
|
||||
"common-name": "mm-ipsec115-1791641218646-CA"
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646-server",
|
||||
"trusted": "false",
|
||||
"trust-store": "all",
|
||||
"ca": "mm-ipsec115-1791641218646-ca",
|
||||
"invalid-before": "2026-10-10 17:07:00",
|
||||
"invalid-after": "2026-10-12 17:07:00",
|
||||
"common-name": "194.226.121.150"
|
||||
},
|
||||
{
|
||||
"name": "ipsec-user-mm-ipsec115-1791641218646",
|
||||
"trusted": "false",
|
||||
"trust-store": "all",
|
||||
"ca": "mm-ipsec115-1791641218646-ca",
|
||||
"invalid-before": "2026-10-10 17:07:01",
|
||||
"invalid-after": "2026-10-12 17:07:01",
|
||||
"common-name": "mm-ipsec115-1791641218646"
|
||||
},
|
||||
{
|
||||
"name": "ipsec-user-mm-ipsec115-1791641218646-decoy",
|
||||
"trusted": "false",
|
||||
"trust-store": "all",
|
||||
"ca": "mm-ipsec115-1791641218646-ca",
|
||||
"invalid-before": "2026-10-10 17:07:01",
|
||||
"invalid-after": "2026-10-12 17:07:01",
|
||||
"common-name": "CN=mm-ipsec115-1791641218646"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646-client-imported",
|
||||
"trusted": "true",
|
||||
"trust-store": "ipsec",
|
||||
"invalid-before": "2026-10-10 16:07:01",
|
||||
"invalid-after": "2026-10-12 16:07:01",
|
||||
"common-name": "mm-ipsec115-1791641218646"
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646-ca-imported",
|
||||
"trusted": "true",
|
||||
"trust-store": "ipsec",
|
||||
"invalid-before": "2026-10-10 16:06:59",
|
||||
"invalid-after": "2026-10-12 16:06:59",
|
||||
"common-name": "mm-ipsec115-1791641218646-CA"
|
||||
}
|
||||
]
|
||||
],
|
||||
"catalogCorrect": true,
|
||||
"dnPhase": {
|
||||
"active": [
|
||||
{
|
||||
".id": "*1",
|
||||
"id": "CN=mm-ipsec115-1791641218646",
|
||||
"state": "established",
|
||||
"dynamic-address": "198.18.115.250",
|
||||
"remote-address": "104.165.244.7",
|
||||
"uptime": "1s"
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"name": "CN=mm-ipsec115-1791641218646",
|
||||
"commonName": "CN=mm-ipsec115-1791641218646",
|
||||
"certName": "ipsec-user-mm-ipsec115-1791641218646-decoy",
|
||||
"online": false
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646",
|
||||
"commonName": "mm-ipsec115-1791641218646",
|
||||
"certName": "ipsec-user-mm-ipsec115-1791641218646",
|
||||
"online": true,
|
||||
"activeAddress": "198.18.115.250"
|
||||
}
|
||||
],
|
||||
"decoyOnline": false
|
||||
},
|
||||
"fqdnPhase": {
|
||||
"configuredRemoteId": "fqdn:mm-ipsec115-1791641218646.example",
|
||||
"nativeId": "mm-ipsec115-1791641218646.example",
|
||||
"assignedAddress": "198.18.115.250",
|
||||
"originalOnline": true,
|
||||
"decoyOnline": false
|
||||
},
|
||||
"aliasName": "mm-ipsec115-1791641218646-extra",
|
||||
"aliasPhase": {
|
||||
"native": [
|
||||
{
|
||||
"id": "mm-ipsec115-1791641218646.example",
|
||||
"state": "established",
|
||||
"dynamic-address": "198.18.115.250"
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"name": "CN=mm-ipsec115-1791641218646",
|
||||
"kind": "cert",
|
||||
"online": false
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646",
|
||||
"kind": "cert",
|
||||
"online": false,
|
||||
"sessionAmbiguous": true
|
||||
},
|
||||
{
|
||||
"name": "mm-ipsec115-1791641218646-extra",
|
||||
"kind": "cert",
|
||||
"online": false,
|
||||
"sessionAmbiguous": true
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"clientsTotal": 3,
|
||||
"clientsOnline": 0,
|
||||
"clientsSessionAmbiguous": 2
|
||||
}
|
||||
},
|
||||
"renameReply": {
|
||||
"error": "RouterOS: RouterOS API error 400 on /ip/ipsec/identity/*B: {\"detail\":\"failure: a matching identity already exists\",\"error\":400,\"message\":\"Bad Request\"}"
|
||||
},
|
||||
"targetGuards": {
|
||||
"certificatePatch409": true,
|
||||
"certificateDelete409": true,
|
||||
"sharedCertificateIdentityDelete409": true,
|
||||
"selectedIdentityPatchNativeMatchingConflict": true,
|
||||
"bothCommentsUnchanged": true
|
||||
},
|
||||
"uiDelete": {
|
||||
"selectedIdentityAbsent": true,
|
||||
"sharedCertificateNativeIdPreserved": true,
|
||||
"otherIdentityPreserved": true
|
||||
},
|
||||
"recovered": {
|
||||
"originalOnline": true,
|
||||
"sessionAmbiguous": false,
|
||||
"activeAddress": "198.18.115.250",
|
||||
"selectedIdentityPatch200AfterRemovingDuplicate": true,
|
||||
"originalCommentRestored": true
|
||||
}
|
||||
},
|
||||
"browserEvidence": "browser-ipsec-keepcert-2026-10-10.json",
|
||||
"local": {
|
||||
"eslintPassed": true,
|
||||
"sourceTypeScriptPassed": true,
|
||||
"productionBuildPassed": true,
|
||||
"uniqueE2eAccepted": 26,
|
||||
"firstRun": {
|
||||
"passed": 22,
|
||||
"failed": 4,
|
||||
"reason": "Modal hides background buttons from accessibility tree; use includeHidden to check retained DOM rows"
|
||||
},
|
||||
"correctedRerun": {
|
||||
"passed": 4,
|
||||
"seconds": 9.8
|
||||
}
|
||||
},
|
||||
"bounds": [
|
||||
"Backend remains refs114; only frontend rebuilt",
|
||||
"One real mobile UI DELETE with removeCertificate=false; four dialogs, not four deletes",
|
||||
"Foreign mode-config retained by API guard and removed only by owned fixture cleanup",
|
||||
"History recording invoked by DELETE and subsequent PATCH; inserted revision count not claimed",
|
||||
"No full regression or new encryption/data-plane acceptance"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -1,5 +1,45 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
|
||||
for (const width of [1280, 320]) for (const shared of [false, true]) {
|
||||
test(`IPsec: сохранить сертификат при удалении identity shared=${shared} ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
const client = (n: number, serverId = "1") => ({ id: serverId + ":identity:*" + n, rosId: "identity:*" + n, serverId, serverName: "Роутер", name: "Клиент " + n, kind: "cert", certName: "shared-cert", authMethod: "certificate", online: false, disabled: false, managed: true })
|
||||
let attempts = 0, deleted = false
|
||||
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||
await page.route("**/api/**", r => {
|
||||
const url = new URL(r.request().url())
|
||||
if (r.request().method() === "DELETE") {
|
||||
attempts++
|
||||
expect(decodeURIComponent(url.pathname)).toBe("/api/ipsec/users/1/identity:*0")
|
||||
expect(url.searchParams.get("removeCertificate")).toBe("false")
|
||||
if (attempts === 1) return r.fulfill({ status: 403, json: { error: "Нет доступа тест" } })
|
||||
deleted = true
|
||||
return r.fulfill({ json: { ok: true } })
|
||||
}
|
||||
const rows = [...(deleted ? [] : [client(0)]), client(1, shared ? "1" : "2")]
|
||||
return r.fulfill({ json: url.pathname === "/api/ipsec" ? { servers: [{ serverId: "1", serverName: "Роутер", initialized: true, ike2Ready: true, natRuleManaged: false, clientsTotal: rows.filter(c => c.serverId === "1").length, clientsOnline: 0 }, { serverId: "2", serverName: "Другой", initialized: false, ike2Ready: false, natRuleManaged: false, clientsTotal: shared ? 0 : 1, clientsOnline: 0 }], clients: rows }
|
||||
: url.pathname === "/api/servers" ? [1,2].map(id => ({ id, name: "Роутер " + id, host: "192.0.2." + id, enabled: true, type: "exit-node", country: "RU", status: "online" })) : url.pathname === "/api/auth/config" ? { required: false } : {} })
|
||||
})
|
||||
await page.goto("/ipsec?server=1")
|
||||
const row = page.locator("tr[data-row-id]").filter({ has: page.getByRole("button", { name: "Клиент 0", exact: true, includeHidden: true }) })
|
||||
await row.getByRole("button", { name: "Удалить клиента", exact: true }).click()
|
||||
const dialog = page.getByRole("alertdialog"), checkbox = dialog.getByRole("checkbox", { name: "Удалить также клиентский сертификат" })
|
||||
if (shared) { await expect(checkbox).not.toBeChecked(); await expect(checkbox).toBeDisabled(); await expect(dialog).toContainText("используется другими identity") }
|
||||
else { await expect(checkbox).toBeChecked(); await expect(checkbox).toBeEnabled(); await checkbox.uncheck() }
|
||||
await expect(dialog).toContainText("Сертификат останется на роутере")
|
||||
expect(await dialog.evaluate(e => e.scrollWidth <= e.clientWidth)).toBe(true)
|
||||
await dialog.getByRole("button", { name: "Удалить", exact: true }).click()
|
||||
await expect(dialog.getByRole("alert")).toContainText("Нет доступа тест")
|
||||
await expect(checkbox).not.toBeChecked()
|
||||
await expect(row).toHaveCount(1)
|
||||
await dialog.getByRole("button", { name: "Удалить", exact: true }).click()
|
||||
await expect(dialog).toHaveCount(0)
|
||||
await expect(row).toHaveCount(0)
|
||||
expect(attempts).toBe(2)
|
||||
if (shared) await expect(page.getByRole("button", { name: "Клиент 1", exact: true })).toBeVisible()
|
||||
})
|
||||
}
|
||||
|
||||
for (const kind of ["user", "server", "peer", "cert"] as const) {
|
||||
test(`IPsec: удаление ${kind} сохраняет подтверждение и защищает повтор`, async ({ page }) => {
|
||||
const peer = { id: "peer", rosId: "*P/1", serverId: "1", serverName: "Роутер", name: "peer-test", managed: true, disabled: false }
|
||||
|
||||
Reference in new issue
Block a user