From c852f47f09066bb69050927657cbe4de7fc106ac Mon Sep 17 00:00:00 2001 From: ayurishchev Date: Sun, 27 Sep 2026 16:14:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B4=D0=B0=D1=87=D0=B0=20036:=20?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8E=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D0=B2=20=D1=88=D0=B0?= =?UTF-8?q?=D0=BF=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Логин в шапке — триггер выпадающего списка «Сменить пароль» / «Выйти» в общем стиле списков (popMenu, row-pop) вместо отдельных кнопок; шрифт и место прежней кнопки «Выйти». Co-Authored-By: Claude Opus 5.5 --- README.md | 2 ++ docs/changes/036-user-menu/PLAN.md | 42 +++++++++++++++++++++++++++ docs/changes/036-user-menu/SUMMARY.md | 19 ++++++++++++ web/app.js | 8 ++++- web/styles.css | 2 +- 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 docs/changes/036-user-menu/PLAN.md create mode 100644 docs/changes/036-user-menu/SUMMARY.md diff --git a/README.md b/README.md index 97e3d7c..92fff52 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ docs/changes/ планы и итоги доработок docs/reviews/ ## Интерфейс - Экраны: «Обзор», «Префиксы» (дерево по VRF), «Адреса» подсети, «Организации», «Операторы», «Устройства», «Журнал», «Пользователи» (только `superadmin`). - В «Пользователях» видна дата и IP последнего входа (изменение 035); «—», если пользователь ещё не входил. +- Логин в шапке раскрывает меню «Сменить пароль» / «Выйти» (изменение 036). - Переключатель организации — только у `superadmin`; `admin`/`viewer` работают в своей организации. - Строка реестра кликабельна целиком. Действия над строкой — в меню «⋯». - Групповые операции через чекбоксы (кроме «Журнала»): удаление, смена типа устройств, статус префиксов и адресов, доступ пользователей. @@ -191,6 +192,7 @@ docker compose -p ipam_control_006 up -d --build && venv/bin/python -m pytest -q | 033 | Исправление находок ревью 032 | [план](docs/changes/033-review-fixes-032/PLAN.md) · [итог](docs/changes/033-review-fixes-032/SUMMARY.md) | | 034 | Публикация через Caddy: rxipam.rxmsk.ru | [план](docs/changes/034-caddy-publication/PLAN.md) · [итог](docs/changes/034-caddy-publication/SUMMARY.md) | | 035 | Последний вход пользователя: дата и IP | [план](docs/changes/035-user-last-login/PLAN.md) · [итог](docs/changes/035-user-last-login/SUMMARY.md) | +| 036 | Меню пользователя в шапке: логин с выпадающим списком | [план](docs/changes/036-user-menu/PLAN.md) · [итог](docs/changes/036-user-menu/SUMMARY.md) | ## Отчёты ревью - [Ревью кодовой базы](docs/reviews/2026-09-26-codebase-review.md) (находки → изменения 011–023) diff --git a/docs/changes/036-user-menu/PLAN.md b/docs/changes/036-user-menu/PLAN.md new file mode 100644 index 0000000..d956866 --- /dev/null +++ b/docs/changes/036-user-menu/PLAN.md @@ -0,0 +1,42 @@ +# Меню пользователя в шапке: логин с выпадающим списком (изменение 036) + +## Context +Сейчас справа в шапке (`web/app.js::shell`) три элемента: серый логин (`span.user`) и две кнопки `btn ghost` — «Сменить пароль» и «Выйти». +Их нужно свернуть в один элемент. В шапке остаётся только логин; по нажатию раскрывается список «Сменить пароль» / «Выйти» +в том же стиле, что все выпадающие списки сайта (`popMenu` + `.pop`). Логин стоит на месте нынешней кнопки «Выйти», +шрифт тот же, что у неё: `.btn.ghost` — `500 14px var(--sans)`, цвет `--muted`. + +## Изменения — `web/app.js` +- `shell()`: вместо `span.user` и двух `btn(...)` — один триггер по образцу `bulkMenu`/`filterBtn`: + ``` + ${popMenu("user", [{label:"Сменить пароль", value:"pw"}, {label:"Выйти", value:"logout"}], "row-pop")} + ``` + - `btn ghost` даёт шрифт и цвет нынешней «Выйти»; шеврон — как у остальных триггеров списков. + - `row-pop` (`top:40px; right:0`) выравнивает список по правому краю, чтобы он не уходил за край окна. + - Роль остаётся во всплывающей подсказке (`title`), как сейчас у `span.user`. +- `actions.pick`: ветка `id === "user"` — снять открытый `.pop` (как для `rowActions`), затем `value === "logout"` → `actions.logout()`, + иначе → `passwordDialog()`. Существующие действия `logout` и `pw-change` не меняются. +- Открытие и закрытие по клику вне списка, навигация стрелками — уже есть в общих обработчиках `menu`/`.pop` (≈стр. 1036, 1059), новых не нужно. +- Комментарии — «изменение 036». + +## Изменения — `web/styles.css` +- Удалить неиспользуемое правило `.appbar .user`. +- При необходимости `.user-menu .pop{min-width:180px}`, если `row-pop` уже задаёт ширину — не добавлять. + +## Документация (правила проекта) +- `docs/changes/036-user-menu/PLAN.md` — этот план; по завершении — `SUMMARY.md`. +- `README.md`, «Интерфейс»: логин в шапке раскрывает меню «Сменить пароль» / «Выйти»; строка 036 в истории. + +## Исполнение +По принятой схеме: правки делает агент на Sonnet (только `node --check`, без стенда); ревью, пересборка стенда, проверки и SUMMARY — моя часть. +Тесты не нужны: API не меняется, UI автотестами не покрыт. + +## Проверка +- `node --check web/app.js`; пересборка `docker compose -p ipam_control_006 up -d --build app`; стенд отдаёт новый `app.js`. +- В браузере на `https://rxipam.rxmsk.ru` (или `:8088`): + - в шапке справа только логин с шевроном, шрифт как у прежней «Выйти»; + - клик раскрывает список в стиле остальных, выровненный вправо; + - «Сменить пароль» открывает диалог, «Выйти» — экран входа; + - клик вне списка и Esc закрывают его, стрелки перемещают фокус. +- Визуальную проверку выполняет пользователь (либо через `claude-in-chrome`, если он подключён). diff --git a/docs/changes/036-user-menu/SUMMARY.md b/docs/changes/036-user-menu/SUMMARY.md new file mode 100644 index 0000000..36d102a --- /dev/null +++ b/docs/changes/036-user-menu/SUMMARY.md @@ -0,0 +1,19 @@ +# Итог: меню пользователя в шапке (изменение 036) + +План: `PLAN.md`. Справа в шапке вместо логина и кнопок «Сменить пароль» / «Выйти» — один элемент: логин с шевроном, +по нажатию раскрывается список «Сменить пароль» / «Выйти» в общем стиле выпадающих списков. + +## Что сделано +| Файл | Изменение | +|---|---| +| `web/app.js` | `shell()`: триггер `btn ghost` с логином (шрифт прежней «Выйти») и ролью в `title`; список через общий `popMenu("user", …, "row-pop")`, выровнен вправо. `actions.pick`: ветка `user` закрывает список и вызывает `actions.logout()` или `passwordDialog()` | +| `web/styles.css` | Удалено неиспользуемое `.appbar .user` | +| `README.md` | «Интерфейс» — меню логина; строка 036 в истории | + +## Проверки +- `node --check web/app.js` — чисто. Стенд пересобран: контейнер healthy, новый `app.js` отдаётся напрямую (`:8088`) и через Caddy (`rxipam.rxmsk.ru`). +- Ручная проверка в браузере (пользователь): вид шапки, раскрытие списка, «Сменить пароль», «Выйти», закрытие — подтверждено. + +## Замечание по стенду +`docker compose -p ipam_control_006 up -d --build app` пересобрал образ, но не пересоздал контейнер — понадобился `--force-recreate`. +После пересборки проверять, что стенд отдаёт новый код, а не только статус healthy. diff --git a/web/app.js b/web/app.js index 35b18dd..0f2637c 100644 --- a/web/app.js +++ b/web/app.js @@ -132,9 +132,11 @@ const header = (title, sub, actions = "") => `

${esc(t const NAV = [["overview", "Обзор"], ["prefixes", "Префиксы"], ["orgs", "Организации"], ["isps", "Операторы"], ["devices", "Устройства"], ["journal", "Журнал"], ["users", "Пользователи", "superadmin"]]; // изменение 032: "superadmin" function shell(active, content) { const nav = NAV.filter(([, , role]) => role !== "superadmin" || isSuperadmin()); // изменение 032: раздел «Пользователи» виден только суперадминистратору + // изменение 036: логин в шапке — триггер выпадающего меню («Сменить пароль» / «Выйти») вместо отдельных кнопок + const userMenu = `${popMenu("user", [{ label: "Сменить пароль", value: "pw" }, { label: "Выйти", value: "logout" }], "row-pop")}`; return `
ipam_manager
-
${esc(user?.username ?? "")}${btn("Сменить пароль", "pw-change", { cls: "ghost" })}${btn("Выйти", "logout", { cls: "ghost" })}
+
${userMenu}

${content}
`; } @@ -924,6 +926,10 @@ const actions = { else if (id === "jactor") { S.jactor = value; S.limit = 100; } else if (id === "jentity") { S.jentity = value; S.limit = 100; } else if (id === "bulk-status" || id === "bulk-type") return bulkPick(id, value); + else if (id === "user") { // изменение 036: меню логина в шапке + document.querySelectorAll(".pop").forEach((el) => el.remove()); + return value === "logout" ? actions.logout() : passwordDialog(); + } else { const key = Object.keys(rowActions).find((k) => id.startsWith(k + "-")); const [act, rid] = value.split(":"); diff --git a/web/styles.css b/web/styles.css index c050691..2108412 100644 --- a/web/styles.css +++ b/web/styles.css @@ -14,7 +14,7 @@ button{font-family:var(--sans)} .nav a{display:flex;align-items:center;height:100%;padding:0 14px;font:500 14px/1 var(--sans);color:var(--muted);border-bottom:2px solid transparent} .nav a.active{color:var(--text);border-bottom-color:var(--primary)} .grow{flex:1} -.appbar .user{color:var(--muted);font:400 14px/1 var(--sans)} +/* изменение 036: .appbar .user удалено — логин заменён на триггер меню (.user-menu .btn.ghost) */ /* buttons */ .btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;height:36px;margin:0;border-radius:8px;font:500 14px/1 var(--sans);white-space:nowrap;cursor:pointer;padding:0 14px;background:#fff;color:var(--text);border:1px solid var(--line-strong)}