diff --git a/app/(main)/alerts/page.tsx b/app/(main)/alerts/page.tsx index 2c7fc59a..8b28a758 100644 --- a/app/(main)/alerts/page.tsx +++ b/app/(main)/alerts/page.tsx @@ -722,7 +722,7 @@ function AlertRuleRow({ rule, onToggle, onDelete, onEdit, interactionsDisabled,i return (
{/* toggle */} @@ -737,10 +737,10 @@ function AlertRuleRow({ rule, onToggle, onDelete, onEdit, interactionsDisabled,i
{/* name + target */} -
+
{isLive&&rule.type==="bgp-prefix"?

Отслеживание появления и отзыва префиксов пока недоступно. Это правило не проверяется.

:null} -

{rule.name}

-

+

{rule.name}

+

{TYPE_META[rule.type].label} ·{" "} {rule.targets.length > 1 ? `${rule.targets.length} объектов` : rule.target} @@ -774,7 +774,7 @@ function AlertRuleRow({ rule, onToggle, onDelete, onEdit, interactionsDisabled,i {/* last fired */} {rule.lastFired ?? "—"} @@ -786,7 +786,7 @@ function AlertRuleRow({ rule, onToggle, onDelete, onEdit, interactionsDisabled,i

{/* actions (visible on hover) */} -
+
{onEdit && (
:null} - {isLive?:null} - {isLive?:null} {known?<> - - - - Подсказки: формат целей и «восстановился» - - -

- Движок читает последние сэмплы из PostgreSQL (uptime ресурсы, REST-пинг, пробы, GRE/BGP). Условие - срабатывает только если цепочка состояний совпадает с текстом правила. -

-
    -
  • - RTT / потери — в объектах укажите ключ пробы - как в мониторинге: имя пробы → target. -
  • -
  • - Восстановился — нужна история: после падения - в последних сэмплах виден возврат в online (REST и метрики ресурса объединяются). Один текущий - online без прошлого offline часто не даёт hit. -
  • -
  • - Перешёл в offline — только на ребре (предыдущий - сэмпл был online). Пока сервер остаётся offline, повторные уведомления не шлются. -
  • -
  • - Стабильность / cooldown — задержка перед - Telegram и пауза между повторными отправками. В разделе «Сбор данных» в деталях прогона «Оповещения» - есть таблица по каждому правилу (hit, стабильность, cooldown, Telegram, причина блока). -
  • -
-
-
- {/* ── summary chip bar ── */}
@@ -2576,7 +2541,7 @@ function AlertsContent() { { key: "warning", label: `Предупреждений: ${warning}`, cls: SEVERITY_META.warning.chip }, { key: "info", label: `Информационных: ${info}`, cls: SEVERITY_META.info.chip }, ] as { key: SeverityFilter; label: string; cls: string }[]).map(({ key, label, cls }) => ( -
+ {/* ── main layout ── */} +
+ + {/* ── rules card ── */} + + Правила оповещения + {rulesSaveBusy && } + + } + contentClassName="px-0 pb-0 pt-2" + > +
setSearch(e.target.value)} autoComplete="off" name="alert-rules-search" />
+ {filteredRules.length === 0 ? ( +
+ +

Правила не найдены

+
+ ) : ( +
+ {filteredRules.map(r => ( + setPendingDelete({kind:"rule",id})} + onEdit={openRuleSheetEdit} + interactionsDisabled={writesDisabled} + isLive={isLive} + /> + ))} +
+ )} + + {/* footer */} +
+ + {filteredRules.length} из {total} правил + {onlyActive && " · только активные"} + {sevFilter !== "all" && ` · ${SEVERITY_META[sevFilter].label.toLowerCase()}`} + + +
+
+ + {/* ── right sidebar ── */} +
+ {setTg(value);setTokenConfigured(configured)}}/> + +
+
{/* ── presets + groups (свёрнуто по умолчанию) ── */} - {/* ── main layout ── */} -
+ + + + Подсказки: формат целей и «восстановился» + + +

+ Движок читает последние сэмплы из PostgreSQL (uptime ресурсы, REST-пинг, пробы, GRE/BGP). Условие + срабатывает только если цепочка состояний совпадает с текстом правила. +

+
    +
  • + RTT / потери — в объектах укажите ключ пробы + как в мониторинге: имя пробы → target. +
  • +
  • + Восстановился — нужна история: после падения + в последних сэмплах виден возврат в online (REST и метрики ресурса объединяются). Один текущий + online без прошлого offline часто не даёт hit. +
  • +
  • + Перешёл в offline — только на ребре (предыдущий + сэмпл был online). Пока сервер остаётся offline, повторные уведомления не шлются. +
  • +
  • + Стабильность / cooldown — задержка перед + Telegram и пауза между повторными отправками. В разделе «Сбор данных» в деталях прогона «Оповещения» + есть таблица по каждому правилу (hit, стабильность, cooldown, Telegram, причина блока). +
  • +
+
+
- {/* ── rules card ── */} - - Правила оповещения - {rulesSaveBusy && } - - } - headerRight={ - setSearch(e.target.value)} - autoComplete="off" - name="alert-rules-search" - className="h-7 text-xs max-w-[200px]" - /> - } - contentClassName="px-0 pb-0 pt-2" - > - {filteredRules.length === 0 ? ( -
- -

Правила не найдены

-
- ) : ( -
- {filteredRules.map(r => ( - setPendingDelete({kind:"rule",id})} - onEdit={openRuleSheetEdit} - interactionsDisabled={writesDisabled} - isLive={isLive} - /> - ))} -
- )} - - {/* footer */} -
- - {filteredRules.length} из {total} правил - {onlyActive && " · только активные"} - {sevFilter !== "all" && ` · ${SEVERITY_META[sevFilter].label.toLowerCase()}`} - - -
-
- - {/* ── right sidebar ── */} -
- {setTg(value);setTokenConfigured(configured)}}/> - -
-
:null} + {isLive?:null} + {isLive?:null}
diff --git a/docs/ux-ui-plan.md b/docs/ux-ui-plan.md index 20566d5c..23b250cf 100644 --- a/docs/ux-ui-plan.md +++ b/docs/ux-ui-plan.md @@ -4,7 +4,7 @@ ## Актуальная точка продолжения — 2026-10-03 -Этап 11Б в работе; последний проверенный блок 11Б.4.4.3.10; System DB принят локально с внешними ограничениями. Следующий блок 11Б.4.4.3.11: компактность основной Alerts страницы и профильная приёмка ПК/320px; processing recovery проектировать отдельно с доказуемой остановкой всех отправителей. Затем Releases/probe scheduling и приёмка 11Б/12. Внешняя PG/Telegram приёмка обязательна перед выпуском. +Этап 11Б в работе; последний проверенный блок 11Б.4.4.3.11; основной Alerts экран принят локально на ПК/320px. Следующий блок 11Б.5: проверить достоверность release manifest, безопасные ссылки и локальное/неизвестное состояние Releases, затем probe scheduling и приёмка 11Б/12. Processing recovery остаётся отдельной задачей с доказуемой остановкой всех отправителей; внешняя PG/Telegram приёмка обязательна перед выпуском. Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md). diff --git a/docs/ux-ui-progress.md b/docs/ux-ui-progress.md index 3e242d42..b91e3b0a 100644 --- a/docs/ux-ui-progress.md +++ b/docs/ux-ui-progress.md @@ -28,7 +28,7 @@ | 9 | VPN, Firewall и Filters | Завершён в границах локальной приёмки 2026-09-30; блоки 9.1–9.64, ограничения в 9.64 | | 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 | | 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 | -| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.4.4.3.10; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings | +| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | В работе: 11Б.1–11Б.4.4.3.11; scheduler и GeoIP приняты локально; NetFlow принят локально; Evolution принят локально; далее общие Settings | | 12 | Итоговая доступность, адаптивность, производительность и регрессия | Не начат | @@ -36,6 +36,14 @@ +## Блок 11Б.4.4.3.11 — приоритет правил и мобильная компоновка (2026-10-03) + +- Правила и фильтры расположены перед каталогами, очередью, пресетами и подсказками. Независимые readers каталогов/очереди остаются смонтированы и доступны даже без подтверждённых основных данных. Формы, checkpoints, общий lock и API не изменены. +- Поиск получил имя и отдельную строку, фильтры — aria-pressed/type=button. Название и объект правила на телефоне занимают отдельную строку; метки и действия переносятся, desktop columns допускают сжатие. Frame/base-nova primitives не изменялись. +- 35 browser сценариев чтения/прав/каталогов/правил/Telegram/очереди прошли; после исправления сжатого названия повторно 12 layout/prefix/rules. Typecheck/lint прошли; ReUI Frame checklist применён. 320px screenshot просмотрен: название, объект, действия видимы. +- Первый запуск остановился по timeout dev server из-за повреждённых generated .next/dev/types; проверенный локальный cache удалён, повторные проверки прошли. Production build предыдущего блока не выдаётся за сборку этих изменений. Реальные PG/Telegram и processing recovery не проверены. +- Следующий блок 11Б.5: проверить достоверность release manifest, безопасные ссылки и локальное/неизвестное состояние Releases, затем probe scheduling и приёмка 11Б/12. Processing recovery остаётся отдельной задачей с доказуемой остановкой всех отправителей; внешняя PG/Telegram приёмка обязательна перед выпуском. + ## Блок 11Б.4.4.3.10 — честная доступность BGP-prefix и сборка (2026-10-03) - Backend evaluate-rule для bgp-prefix возвращает null; live UI теперь прямо сообщает, что сохранённое правило не проверяется. Выбор этого типа для нового правила недоступен, выключенное legacy правило нельзя включить через switch. Existing enabled запись можно отключить; редактирование/смена типа/удаление сохраняются. Mock демонстрация и backend policies не менялись. diff --git a/docs/ux-ui-stage-11b.md b/docs/ux-ui-stage-11b.md index 8ce181c5..c30f4c5b 100644 --- a/docs/ux-ui-stage-11b.md +++ b/docs/ux-ui-stage-11b.md @@ -320,3 +320,7 @@ Picker keyboard semantics/one parent scroll/initial objects accepted locally; br ### Продолжение 2026-10-03 — 11Б.4.4.3.10 Live BGP-prefix честно помечен unsupported, legacy записи не удалены; browser12/typecheck/lint и production frontend/contracts/backend build прошли. Финальный Alerts/backend sidebar профиль прошёл. Основной Alerts экран/processing recovery/внешняя PG и Telegram приёмка остаются. Следующий блок 11Б.4.4.3.11: компактность основной Alerts страницы и профильная приёмка ПК/320px; processing recovery проектировать отдельно с доказуемой остановкой всех отправителей. Затем Releases/probe scheduling и приёмка 11Б/12. Внешняя PG/Telegram приёмка обязательна перед выпуском. + +### Продолжение 2026-10-03 — 11Б.4.4.3.11 + +Приоритет правил, доступные фильтры/поиск и мобильная строка приняты локально: browser35 + повтор12, typecheck/lint/ReUI audit, screenshot320. Readers/формы/guards сохранены. Следующий блок 11Б.5: проверить достоверность release manifest, безопасные ссылки и локальное/неизвестное состояние Releases, затем probe scheduling и приёмка 11Б/12. Processing recovery остаётся отдельной задачей с доказуемой остановкой всех отправителей; внешняя PG/Telegram приёмка обязательна перед выпуском. diff --git a/tests/e2e/alerts-layout.spec.ts b/tests/e2e/alerts-layout.spec.ts new file mode 100644 index 00000000..b0139673 --- /dev/null +++ b/tests/e2e/alerts-layout.spec.ts @@ -0,0 +1,21 @@ +import {test,expect} from "@playwright/test" +import {alertsWithRule,emptyOutbox} from "./fixtures/alerts" + +for(const width of [320,1440])test("Alerts rules priority, accessible filters and no overflow/"+width,async({page})=>{ + await page.setViewportSize({width,height:900}) + await page.route("**/health",r=>r.fulfill({json:{status:"ok"}})) + await page.route("**/api/**",r=>{const path=new URL(r.request().url()).pathname;return r.fulfill({json:path==="/api/auth/config"?{required:false}:path==="/api/alerts"?alertsWithRule:path==="/api/alerts/outbox"?emptyOutbox:path==="/api/filters/gre-tunnels"?{tunnels:[],failures:[]}:[]})}) + await page.goto("/alerts") + const search=page.getByRole("textbox",{name:"Поиск правил",exact:true}) + await expect(search).toBeVisible() + const critical=page.getByRole("button",{name:"Критических: 1",exact:true}) + await critical.focus();await page.keyboard.press("Space");await expect(critical).toHaveAttribute("aria-pressed","true") + await search.fill("missing fixture");await expect(page.getByText("Правила не найдены",{exact:true})).toBeVisible() + await search.fill("Fixture rule");await expect(page.getByRole("button",{name:"Изменить правило Fixture rule"})).toBeVisible() + const name=page.getByText("Fixture rule",{exact:true});await expect(name).toBeVisible();expect((await name.boundingBox())!.width).toBeGreaterThan(100) + const position=await search.boundingBox();const catalogs=await page.getByText("Каталоги целей",{exact:true}).boundingBox();const queue=await page.getByRole("region",{name:"Очередь Telegram"}).boundingBox() + expect(position!.y).toBeLessThan(catalogs!.y);expect(position!.y).toBeLessThan(queue!.y) + await search.scrollIntoViewIfNeeded();await expect(search).toBeInViewport() + expect(await page.evaluate(()=>document.documentElement.scrollWidth<=window.innerWidth)).toBe(true) + await page.screenshot({path:"test-results/alerts-layout-"+width+".png",fullPage:true}) +})