Задача 036: меню пользователя в шапке
Логин в шапке — триггер выпадающего списка «Сменить пароль» / «Выйти» в общем стиле списков (popMenu, row-pop) вместо отдельных кнопок; шрифт и место прежней кнопки «Выйти». Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8384c2c311
commit
c852f47f09
5 files changed
+71
-2
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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`:
|
||||
```
|
||||
<span class="rel user-menu"><button type="button" class="btn ghost" data-action="menu" data-menu="user" title="<роль>">
|
||||
<логин>${I.chevD(14)}</button>${popMenu("user", [{label:"Сменить пароль", value:"pw"}, {label:"Выйти", value:"logout"}], "row-pop")}</span>
|
||||
```
|
||||
- `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`, если он подключён).
|
||||
@@ -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.
|
||||
+7
-1
@@ -132,9 +132,11 @@ const header = (title, sub, actions = "") => `<div class="head"><div><h1>${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 = `<span class="rel user-menu"><button type="button" class="btn ghost" data-action="menu" data-menu="user" title="${esc(ROLE_RU[user?.role] ?? "")}">${esc(user?.username ?? "")}${I.chevD(14)}</button>${popMenu("user", [{ label: "Сменить пароль", value: "pw" }, { label: "Выйти", value: "logout" }], "row-pop")}</span>`;
|
||||
return `<div class="appbar"><div class="brand"><span class="brand-logo">${I.globe()}</span>ipam_manager</div>
|
||||
<nav class="nav">${nav.map(([k, l]) => `<a href="#/${k}" class="${k === active ? "active" : ""}">${l}</a>`).join("")}</nav>
|
||||
<div class="grow"></div><span class="user" title="${esc(ROLE_RU[user?.role] ?? "")}">${esc(user?.username ?? "")}</span>${btn("Сменить пароль", "pw-change", { cls: "ghost" })}${btn("Выйти", "logout", { cls: "ghost" })}</div>
|
||||
<div class="grow"></div>${userMenu}</div>
|
||||
<div class="page">${content}</div>`;
|
||||
}
|
||||
|
||||
@@ -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(":");
|
||||
|
||||
+1
-1
@@ -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)}
|
||||
|
||||
Reference in new issue
Block a user