From d1c9d450f26911efdbbdfb64f54c68cbcd545da7 Mon Sep 17 00:00:00 2001
From: byrsapty
Date: Tue, 25 Aug 2026 14:52:09 +0300
Subject: [PATCH] =?UTF-8?q?=D0=86=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5?=
=?UTF-8?q?=D0=B9=D1=81:=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D0=B0=20?=
=?UTF-8?q?=D1=83=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D1=86=D1=96,=20=D0=BA?=
=?UTF-8?q?=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=BD=D0=B5=20=D0=B2=D0=B8?=
=?UTF-8?q?=D0=B4=D1=96=D0=BB=D1=8F=D1=8E=D1=82=D1=8C=D1=81=D1=8F,=20?=
=?UTF-8?q?=D0=BF=D0=B0=D0=BD=D0=B5=D0=BB=D1=8C=20=D0=B7=D0=B3=D0=BE=D1=80?=
=?UTF-8?q?=D1=82=D0=B0=D1=94=D1=82=D1=8C=D1=81=D1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
«Правила» пішли з бічної панелі у вкладку поруч з «Алертами»: тригери
переїхали в шаблони, а окремий пункт меню й далі пропонував заводити
правила там, звідки їх прибрали.
Кнопки перестали бути виділюваним текстом — у них типово
user-select: auto, і в підпис можна було поставити блимаючий курсор.
Бічна панель згортається у значки, стан у localStorage.
Дорогою закрито дірку: вбудований шаблон нікого не сповіщав. Тригери
стають правилами лише при збереженні шаблону, а вбудований нікому не
належить і не зберігається — хост із icmp-basic збирав пінг і мовчав
про недоступність. Плюс індекс правил з 0024 не враховував тенанта,
через що другий кабінет із тим самим вбудованим шаблоном отримав би
помилку унікальності.
Co-Authored-By: Claude Opus 5
---
HISTORY.md | 79 +++++++++++++++++++
server/internal/store/template_triggers.go | 9 ++-
server/internal/store/templates.go | 17 +++-
.../0025_template_rules_per_tenant.sql | 17 ++++
web/src/components/AppShell.tsx | 54 +++++++++++--
web/src/components/ui.tsx | 33 ++++++++
web/src/index.css | 25 ++++++
web/src/pages/AlertsPage.tsx | 8 +-
web/src/pages/RulesPage.tsx | 10 ++-
9 files changed, 239 insertions(+), 13 deletions(-)
create mode 100644 server/migrations/0025_template_rules_per_tenant.sql
diff --git a/HISTORY.md b/HISTORY.md
index 9100fb3..d9dfd2b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2345,3 +2345,82 @@ JSON лишився запасним виходом для джерел, яки
валідація невідоме джерело → 400 з поясненням
тести server (з базою) і web build — зелені
```
+
+## Три правки інтерфейсу
+
+### «Правила» пішли з бічної панелі
+
+Тригери переїхали в шаблони, а окремий пункт меню лишився й далі
+пропонував заводити правила там, звідки їх щойно прибрали.
+
+Тепер це вкладка поруч з «Алертами»: `Алерти · Правила`. Розділ
+лишився — разові правила на конкретний хост нікуди не діваються, — але
+не займає рядок у меню як рівноправна тема.
+
+Вкладки маршрутні, а не станові: посилання лишається посиланням, із
+середньою кнопкою, «відкрити в новій вкладці» й адресою в рядку
+браузера. Тому це окремий компонент `SectionTabs`, а не той самий
+`Tabs`, що перемикає вміст форми.
+
+Підзаголовок сторінки правил тепер прямо каже, де правити тригери
+шаблонів.
+
+### Кнопки перестали бути текстом
+
+У кнопок типово `user-select: auto` — підпис лишається виділюваним
+текстом, і в нього можна поставити текстовий курсор. Він блимає
+всередині кнопки, ніби туди щось вводять. На дотик гірше: довге
+натискання виділяє слово замість натиснути.
+
+Правило на елемент, а не на компонент — кнопки трапляються й поза
+`
)}
+
+ {/* Кнопка внизу, а не в шапці: згортання стосується самої
+ панелі, і шукати її перемикач в іншому кутку екрана —
+ зайвий крок. На телефоні панель і так шухляда. */}
+
diff --git a/web/src/components/ui.tsx b/web/src/components/ui.tsx
index 702dee5..4701bf9 100644
--- a/web/src/components/ui.tsx
+++ b/web/src/components/ui.tsx
@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react'
import type { ReactNode } from 'react'
+import { NavLink } from 'react-router-dom'
/**
* Примітиви інтерфейсу.
@@ -29,6 +30,38 @@ export function PageHeader({
)
}
+/**
+ * Смуга розділів під шапкою сторінки.
+ *
+ * Не те саме, що Tabs: ті перемикають вміст форми, а ці — маршрут, і
+ * посилання лишається посиланням (середня кнопка, «відкрити в новій
+ * вкладці», адреса в рядку браузера).
+ *
+ * Потрібна там, де два екрани — це одна тема, і обидва в бічній панелі
+ * лише подовжували б її: алерти й правила, за якими вони виникають.
+ */
+export function SectionTabs({ tabs }: { tabs: { to: string; label: string }[] }) {
+ return (
+
+ )
+}
+
type ButtonKind = 'primary' | 'default' | 'danger' | 'ghost'
const buttonKind: Record = {
diff --git a/web/src/index.css b/web/src/index.css
index b1be2b0..0368055 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -27,6 +27,31 @@ body {
color: #334155 !important;
}
+/* ---------------------------------------------------------------------
+ Кнопки — це кнопки, а не текст
+ ------------------------------------------------------------------ */
+
+/* Типово підпис кнопки лишається виділюваним текстом (user-select: auto),
+ і в браузері на ньому можна поставити текстовий курсор — він блимає
+ всередині кнопки, ніби туди щось вводять. На дотик те саме виглядає
+ гірше: довге натискання виділяє слово замість натиснути.
+
+ Правило на елемент, а не на компонент: кнопки трапляються й поза
+