diff --git a/components/data-grids/gre-tunnels-data-grid.tsx b/components/data-grids/gre-tunnels-data-grid.tsx
index 04ddb8f8..9d05efeb 100644
--- a/components/data-grids/gre-tunnels-data-grid.tsx
+++ b/components/data-grids/gre-tunnels-data-grid.tsx
@@ -20,6 +20,7 @@ import type {
import { Flag } from "@/components/flag"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
+import { Badge } from "@/components/reui/badge"
import {
DropdownMenu,
DropdownMenuContent,
@@ -86,13 +87,13 @@ function TunnelStatus({ status }: { status: GreStatus }) {
function IpsecBadge({ secured }: { secured: boolean }) {
return secured ? (
-
+
IPsec
-
+
) : (
-
+
Открытый
-
+
)
}
diff --git a/docs/ux-ui-external-acceptance.md b/docs/ux-ui-external-acceptance.md
index 102d4809..5e3bb79a 100644
--- a/docs/ux-ui-external-acceptance.md
+++ b/docs/ux-ui-external-acceptance.md
@@ -1,6 +1,6 @@
# UX/UI: протокол внешней приёмки 12.5
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап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).
@@ -2579,3 +2579,7 @@ Production compiled SQL/API29 на собственной ограниченно
## 12.5.213 — GRE IPv6 export (2026-10-13)
Статический внутренний ULA и IPv6 fragment проверены на двух CHR и четырёх live UI. Backend ux-gre212, frontend ux-gre213; собственные интерфейсы/адреса/user удалены. Подробности и ограничения: [блок213 в журнале](ux-ui-progress.md). Public IPv6 и произвольный RSC import не приняты.
+
+## 12.5.214 — GRE badge contrast (2026-10-14)
+
+Подробности: [блок214 в журнале](ux-ui-progress.md). Scoped axe4 и live UI8 прошли; cleanup подтверждён. Backend ux-gre212, frontend ux-gre214.
diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md
index ba1f6ded..499235f1 100644
--- a/docs/ux-ui-plan.md
+++ b/docs/ux-ui-plan.md
@@ -1,6 +1,6 @@
# План реализации UX/UI по пятичасовым окнам
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап12.5 открыт.
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-13. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md
index cb67efe5..c50c404c 100644
--- a/docs/ux-ui-progress.md
+++ b/docs/ux-ui-progress.md
@@ -1,6 +1,6 @@
# UX/UI: журнал реализации
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап12.5 открыт.
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения. Записи расположены от новых к старым; подробная история этапа12 также сохранена в [ux-ui-stage-12.md](ux-ui-stage-12.md).
@@ -38,6 +38,16 @@
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | Локальная приёмка завершена в границах12.3.83; внешняя12.5 в работе, выполненные блоки1–196 и ограничения ниже. Runtime backend/frontend ux-peak196. |
+## 12.5.214 — контраст метки IPsec на заполненной GRE-таблице (2026-10-13 — 2026-10-14)
+
+После213 взят небольшой дополнительный блок доступности перед большим legacy OSPF/BFD. Baseline axe подтвердил color-contrast1,76:1 в светлой теме. В первоначальном mobile baseline метка не прокручивалась явно; отсутствие там violation не считается доказательством контраста. Ручные spans заменены штатным ReUI Badge invert-light, без изменения vendor components/theme. Конфигурация IPsec различается текстом и значком; метка не утверждает успешный handshake.
+
+Четыре fixed fixtures light/dark ×1440/320 прокручивали метку в область видимости: contrast violations0. TypeScript и ESLint прошли. Frontend ux-gre214/source527 matched; backend ux-gre212 остался healthy. Восемь live UI reader/admin ×light/dark ×1440/320 проверили scoped badge contrast на собственных отключённых GRE с ULA/PSK. Интерфейсы, адреса и Portal reader удалены, отсутствие проверено. Пароли не менялись. Это scoped color-contrast audit, не полная WCAG/IPv6/data-plane/RSC приёмка.
+
+Протоколы: [baseline/fixed](ux-ui/acceptance/gre214-badge-2026-10-13.json), [live/cleanup](ux-ui/acceptance/gre214-native-live-2026-10-14.json), [source](ux-ui/acceptance/source214-result.json), [deployment](ux-ui/acceptance/gre214-deployment-2026-10-14.json).
+
+Следующий215: /ospf/neighbors, /ospf/interfaces, /ospf/instances, /ospf/all и /bfd/sessions — сохранить success envelopes, перестать скрывать partial/all-failed native reads как200/empty; собственные SQL/HTTP fixtures, без отключения CHR. Оставшиеся внешние ограничения сохраняются в матрице.
+
## 12.5.213 — IPv6 в фрагменте экспорта GRE (2026-10-13)
Генератор всегда использовал /ip/address/add, хотя приложение уже поддерживает статический внутренний IPv6. Теперь family выбирается по адресу: IPv4 сохраняет прежнюю команду; IPv6 получает /ipv6/address/add и advertise=no. Комментарий уточняет: это новый статический адрес без объявления префикса; pool/EUI-64 и остальные адреса не включены. Это фрагмент, не полное восстановление параметров произвольного native адреса. Escaping и шаблоны213/212 сохранены; ReUI primitives не менялись.
diff --git a/docs/ux-ui-remaining-acceptance.md b/docs/ux-ui-remaining-acceptance.md
index b111face..202eae48 100644
--- a/docs/ux-ui-remaining-acceptance.md
+++ b/docs/ux-ui-remaining-acceptance.md
@@ -1,10 +1,10 @@
# Оставшаяся внешняя приёмка — 2026-10-13
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап12.5 открыт.
-Это актуальная матрица после 12.5.213. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
+Это актуальная матрица после 12.5.214. Исторические записи сохраняют версии и ограничения на момент выполнения; поздние проверки ниже уточняют их. Этап 12.5 открыт. Успех в двух CHR и одном backend не означает приёмку всех конфигураций, интеграций или полной регрессии.
-Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-gre212`, frontend `ux-gre213`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
+Стенд: Debian13/Compose, PostgreSQL18.6, два CHR RouterOS7.24.5. Backend `ux-gre212`, frontend `ux-gre214`; production lab с195 использует AUTH_REQUIRED=true и Portal1.3.1. Подписанные JWT/RBAC проверялись в изолированных процессах. Все собственные объекты112 удалены, исходные13 коллекций×2 (включая logging/certificates/files/routes) проверены; пароли не менялись.
| Раздел | Подтверждено | Осталось / условие завершения |
| --- | --- | --- |
@@ -26,7 +26,7 @@
## Следующая последовательность — 2026-10-13
-1. **214 — legacy OSPF/BFD read errors:** пять GET endpoints скрывают native failures; сохранить success envelopes, различить empty/partial/all-failed/disabled contexts в own SQL/HTTP fixtures.
+1. **215 — legacy OSPF/BFD read errors:** пять GET endpoints скрывают native failures; сохранить success envelopes, различить empty/partial/all-failed/disabled contexts в own SQL/HTTP fixtures.
2. **Профильный контроль:** для новых изменений выполнять затронутые SQL/API/UI проверки. Общая регрессия192 выполнена; не повторять её без изменений общей основы или нового основания.
3. **Внешние зависимости:** Portal issuer/login/revoke, Telegram bot/chat/разрешённое сообщение, клиентские ОС, независимые WAN/public IPv6 и scale/multi-instance — отдельные группы по таблице выше. Ограничения текущего стенда не превращать в обещание приёмки всех конфигураций.
@@ -1497,3 +1497,7 @@ Production compiled SQL/API29 на собственной ограниченно
## 12.5.213 — GRE IPv6 export (2026-10-13)
Статический внутренний ULA и IPv6 fragment проверены на двух CHR и четырёх live UI. Backend ux-gre212, frontend ux-gre213; собственные интерфейсы/адреса/user удалены. Подробности и ограничения: [блок213 в журнале](ux-ui-progress.md). Public IPv6 и произвольный RSC import не приняты.
+
+## 12.5.214 — GRE badge contrast (2026-10-14)
+
+Подробности: [блок214 в журнале](ux-ui-progress.md). Scoped axe4 и live UI8 прошли; cleanup подтверждён. Backend ux-gre212, frontend ux-gre214.
diff --git a/docs/ux-ui-stage-12.md b/docs/ux-ui-stage-12.md
index c5f01de9..aefbddd1 100644
--- a/docs/ux-ui-stage-12.md
+++ b/docs/ux-ui-stage-12.md
@@ -1,6 +1,6 @@
# Этап 12: итоговая приёмка UX/UI
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап12.5 открыт.
План: [ux-ui-plan.md](ux-ui-plan.md). Фактические результаты: [ux-ui-progress.md](ux-ui-progress.md). Подготовлен после сводных проверок 11Б; завершение каждого блока требует локального коммита без push.
@@ -3910,3 +3910,14 @@ Live: приложение создало по собственному откл
Следующий214: legacy /ospf/neighbors, /ospf/interfaces, /ospf/instances, /ospf/all и /bfd/sessions сейчас скрывают rejected native reads как200/empty. Поиск не нашёл активных frontend-потребителей этих путей; основной OSPF catalog уже сообщает failures. Сохранить успешные legacy envelopes и явно сообщать частичный/полный отказ; проверить собственные SQL/HTTP fixtures без искусственного отключения CHR.
+
+## 12.5.214 — контраст метки IPsec на заполненной GRE-таблице (2026-10-13 — 2026-10-14)
+
+После213 взят небольшой дополнительный блок доступности перед большим legacy OSPF/BFD. Baseline axe подтвердил color-contrast1,76:1 в светлой теме. В первоначальном mobile baseline метка не прокручивалась явно; отсутствие там violation не считается доказательством контраста. Ручные spans заменены штатным ReUI Badge invert-light, без изменения vendor components/theme. Конфигурация IPsec различается текстом и значком; метка не утверждает успешный handshake.
+
+Четыре fixed fixtures light/dark ×1440/320 прокручивали метку в область видимости: contrast violations0. TypeScript и ESLint прошли. Frontend ux-gre214/source527 matched; backend ux-gre212 остался healthy. Восемь live UI reader/admin ×light/dark ×1440/320 проверили scoped badge contrast на собственных отключённых GRE с ULA/PSK. Интерфейсы, адреса и Portal reader удалены, отсутствие проверено. Пароли не менялись. Это scoped color-contrast audit, не полная WCAG/IPv6/data-plane/RSC приёмка.
+
+Протоколы: [baseline/fixed](ux-ui/acceptance/gre214-badge-2026-10-13.json), [live/cleanup](ux-ui/acceptance/gre214-native-live-2026-10-14.json), [source](ux-ui/acceptance/source214-result.json), [deployment](ux-ui/acceptance/gre214-deployment-2026-10-14.json).
+
+Следующий215: /ospf/neighbors, /ospf/interfaces, /ospf/instances, /ospf/all и /bfd/sessions — сохранить success envelopes, перестать скрывать partial/all-failed native reads как200/empty; собственные SQL/HTTP fixtures, без отключения CHR. Оставшиеся внешние ограничения сохраняются в матрице.
+
diff --git a/docs/ux-ui-test-server.md b/docs/ux-ui-test-server.md
index e71edc7d..b5287630 100644
--- a/docs/ux-ui-test-server.md
+++ b/docs/ux-ui-test-server.md
@@ -1,6 +1,6 @@
# Тестовый сервер UX/UI — 2026-10-10
-Актуальное продолжение 2026-10-13: блок 213 завершён. GRE экспорт выбирает IPv4/IPv6 family; статический IPv6 fragment содержит advertise=no и явные границы. Unit/backend IPv6, E2E6 и live UI4 прошли; два собственных GRE/ULA и Portal reader удалены. Backend ux-gre212, frontend ux-gre213/source527 matched. Оба CHR доступны; пароли совпадают с предоставленными. Следующий214 — ошибки legacy OSPF/BFD API вместо успешных пустых данных. Этап12.5 открыт.
+Актуальное продолжение 2026-10-14: блок214 завершён. Контраст заполненной метки GRE IPsec исправлен штатным ReUI Badge. Baseline1,76:1; четыре scoped axe fixtures и восемь live UI light/dark × reader/admin ×1440/320 прошли. Own GRE/ULA/PSK и Portal reader удалены. Backend ux-gre212, frontend ux-gre214/source527 совпал. Следующий215 — ошибки пяти legacy OSPF/BFD GET endpoints. Этап12.5 открыт.
| Параметр | Проверенное состояние |
| --- | --- |
@@ -2310,3 +2310,7 @@ Production compiled SQL/API29 на собственной ограниченно
## 12.5.213 — GRE IPv6 export (2026-10-13)
Статический внутренний ULA и IPv6 fragment проверены на двух CHR и четырёх live UI. Backend ux-gre212, frontend ux-gre213; собственные интерфейсы/адреса/user удалены. Подробности и ограничения: [блок213 в журнале](ux-ui-progress.md). Public IPv6 и произвольный RSC import не приняты.
+
+## 12.5.214 — GRE badge contrast (2026-10-14)
+
+Подробности: [блок214 в журнале](ux-ui-progress.md). Scoped axe4 и live UI8 прошли; cleanup подтверждён. Backend ux-gre212, frontend ux-gre214.
diff --git a/docs/ux-ui/acceptance/gre214-badge-2026-10-13.json b/docs/ux-ui/acceptance/gre214-badge-2026-10-13.json
new file mode 100644
index 00000000..ffec0871
--- /dev/null
+++ b/docs/ux-ui/acceptance/gre214-badge-2026-10-13.json
@@ -0,0 +1,27 @@
+{
+ "date": "2026-10-13",
+ "block": "12.5.214",
+ "baseline": {
+ "startTime": "2026-10-13T15:06:47.037Z",
+ "duration": 13430.022,
+ "expected": 3,
+ "skipped": 0,
+ "unexpected": 1,
+ "flaky": 0
+ },
+ "baselineKnownContrastRatio": 1.76,
+ "baselineScope": "1440 light confirmed; initial mobile badge was not explicitly scrolled",
+ "fixed": {
+ "startTime": "2026-10-13T15:08:00.429Z",
+ "duration": 12526.091,
+ "expected": 4,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "uniqueCases": 4,
+ "fixedBadgeScrolledIntoView": true,
+ "apiMocked": true,
+ "rule": "color-contrast",
+ "scope": "Configured-IPsec badge, light/dark ×1440/320; not full WCAG"
+}
diff --git a/docs/ux-ui/acceptance/gre214-deployment-2026-10-14.json b/docs/ux-ui/acceptance/gre214-deployment-2026-10-14.json
new file mode 100644
index 00000000..be64967c
--- /dev/null
+++ b/docs/ux-ui/acceptance/gre214-deployment-2026-10-14.json
@@ -0,0 +1,20 @@
+{
+ "date": "2026-10-14",
+ "block": "12.5.214",
+ "frontendSourceMatched": 527,
+ "backendChanged": false,
+ "services": {
+ "backend": {
+ "image": "mmapp-test/backend:ux-gre212",
+ "imageId": "sha256:15b4c11614737ff12d4a4d40ed585324dabcace1f96342f0e966266125b78a1b",
+ "running": true,
+ "health": "healthy"
+ },
+ "frontend": {
+ "image": "mmapp-test/frontend:ux-gre214",
+ "imageId": "sha256:7035e5ba628276a9927cce4814739dadd920dba0606e3b9fc316cc0262f77d5f",
+ "running": true,
+ "health": null
+ }
+ }
+}
diff --git a/docs/ux-ui/acceptance/gre214-e2e-2026-10-13.json b/docs/ux-ui/acceptance/gre214-e2e-2026-10-13.json
new file mode 100644
index 00000000..34103180
--- /dev/null
+++ b/docs/ux-ui/acceptance/gre214-e2e-2026-10-13.json
@@ -0,0 +1,15 @@
+{
+ "date": "2026-10-13",
+ "block": "12.5.214",
+ "uniqueCases": 6,
+ "stats": {
+ "startTime": "2026-10-13T15:08:00.429Z",
+ "duration": 12526.091,
+ "expected": 4,
+ "skipped": 0,
+ "unexpected": 0,
+ "flaky": 0
+ },
+ "apiMocked": true,
+ "scope": "IPv6 family and existing export templates on 1440/320"
+}
diff --git a/docs/ux-ui/acceptance/gre214-native-live-2026-10-14.json b/docs/ux-ui/acceptance/gre214-native-live-2026-10-14.json
new file mode 100644
index 00000000..a7177041
--- /dev/null
+++ b/docs/ux-ui/acceptance/gre214-native-live-2026-10-14.json
@@ -0,0 +1,124 @@
+{
+ "date": "2026-10-14",
+ "block": "12.5.214",
+ "backend": "ux-gre212",
+ "frontend": "ux-gre214",
+ "cases": [
+ {
+ "host": "194.226.121.150",
+ "applicationCreateStatus": 201,
+ "ownedDisabledGre": true,
+ "localInnerIp": "fd00:214:f753::1/64",
+ "nativeFamily": "ipv6/address",
+ "advertise": false,
+ "publicOuterIpv6": false,
+ "dataPlaneTest": false
+ },
+ {
+ "host": "104.165.244.7",
+ "applicationCreateStatus": 201,
+ "ownedDisabledGre": true,
+ "localInnerIp": "fd00:214:f753::2/64",
+ "nativeFamily": "ipv6/address",
+ "advertise": false,
+ "publicOuterIpv6": false,
+ "dataPlaneTest": false
+ }
+ ],
+ "uiCases": [
+ {
+ "theme": "light",
+ "width": 1440,
+ "role": "reader",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "light",
+ "width": 1440,
+ "role": "admin",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "light",
+ "width": 320,
+ "role": "reader",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "light",
+ "width": 320,
+ "role": "admin",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "dark",
+ "width": 1440,
+ "role": "reader",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "dark",
+ "width": 1440,
+ "role": "admin",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "dark",
+ "width": 320,
+ "role": "reader",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ },
+ {
+ "theme": "dark",
+ "width": 320,
+ "role": "admin",
+ "badgeContrastViolations": 0,
+ "actualNativeData": true,
+ "ipv6ExportFamily": true,
+ "staticFragmentScopeVisible": true,
+ "mutationRequests": 0,
+ "pageErrors": 0
+ }
+ ],
+ "apiMocks": false,
+ "realPortalJWT": true,
+ "encodedAnonymousAuthStatus": 401,
+ "cleanupCompleted": true,
+ "scope": "Static inner ULA GRE projection/export fragment matches native IPv6 family; no RSC execution, outer/public IPv6 or data-plane claim",
+ "ownedPortalUserRemoved": true
+}
diff --git a/docs/ux-ui/acceptance/source214-result.json b/docs/ux-ui/acceptance/source214-result.json
new file mode 100644
index 00000000..e50e8ecf
--- /dev/null
+++ b/docs/ux-ui/acceptance/source214-result.json
@@ -0,0 +1,11 @@
+{
+ "date": "2026-10-13",
+ "block": "12.5.214",
+ "kinds": [
+ {
+ "kind": "frontend",
+ "matched": 527
+ }
+ ],
+ "verifiedOnClientDate": "2026-10-14"
+}
diff --git a/tests/e2e/gre-badge-accessibility.spec.ts b/tests/e2e/gre-badge-accessibility.spec.ts
new file mode 100644
index 00000000..c99eec5b
--- /dev/null
+++ b/tests/e2e/gre-badge-accessibility.spec.ts
@@ -0,0 +1,14 @@
+import {test,expect} from '@playwright/test'
+import axe from 'axe-core'
+for(const theme of ['light','dark'])for(const width of [1440,320])test(`GRE: скрытый PSK сохраняет состояние IPsec и помечает экспорт ${theme} ${width}`,async({page})=>{
+ let writes=0;await page.addInitScript(t=>localStorage.setItem('rl-theme',t),theme)
+ const token=[Buffer.from('{}').toString('base64url'),Buffer.from(JSON.stringify({sub:'own209',apps:['mm'],permissions:['mm:dashboard:read','mm:servers:read','mm:network:read'],exp:Math.floor(Date.now()/1000)+600})).toString('base64url'),'fixture'].join('.')
+ await page.addInitScript(t=>localStorage.setItem('mmapp_token',t),token)
+ const tunnel={id:'*1',serverId:'1',name:'own209-gre',localAddress:'192.0.2.1',remoteAddress:'192.0.2.2',localInnerIp:'10.209.0.1/30',remoteInnerIp:'10.209.0.2/30',poolId:'live',ipsec:{secret:''},mtu:1476,keepaliveInterval:10,keepaliveRetries:3,dscp:'inherit',clampTcpMss:true,allowFastPath:false,comment:'',enabled:true,status:'up'}
+ await page.route('**/health',r=>r.fulfill({json:{ok:true}}))
+ await page.route('**/api/**',r=>{if(r.request().method()!=='GET')writes++;const p=new URL(r.request().url()).pathname;return r.fulfill({json:p==='/api/auth/config'?{required:true}:p==='/api/servers'?[{id:1,name:'Own209 router',host:'192.0.2.1',enabled:true,country:'RU'}]:p==='/api/gre/tunnels'?{tunnels:[tunnel]}:{}})})
+ await page.setViewportSize({width,height:1000});await page.goto('/gre?server=1')
+ const row=page.getByRole('row').filter({hasText:'own209-gre'});await expect(row).toContainText('IPsec');await expect(row).not.toContainText('Открытый')
+ await expect(page.getByRole('button',{name:'Добавить туннель',exact:true})).toBeDisabled()
+ const badge=row.getByText('IPsec',{exact:true});await badge.scrollIntoViewIfNeeded();await badge.evaluate(el=>el.id='own214-ipsec-badge');await page.addScriptTag({content:axe.source});const result=await page.evaluate(async()=>(window as unknown as {axe:typeof axe}).axe.run({include:['#own214-ipsec-badge']},{runOnly:{type:'rule',values:['color-contrast']}}));expect(result.violations.map(v=>({id:v.id,nodes:v.nodes.map(n=>n.failureSummary)}))).toEqual([]);expect(writes).toBe(0);expect(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1)).toBe(true)
+})