diff --git a/package.json b/package.json index c76d5b8..0c6f1d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.101", + "version": "0.5.102", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/history/2026-06-15-unified-interface.md b/v2/history/2026-06-15-unified-interface.md new file mode 100644 index 0000000..e914fe5 --- /dev/null +++ b/v2/history/2026-06-15-unified-interface.md @@ -0,0 +1,30 @@ +# V2 Unified Interface — v0.5.102 (2026-06-15) + +**Что сделано:** объединение юзерского и админского интерфейсов в один `/v2/app`. + +## Изменённые файлы + +### `v2/src/user/index.js` — единый роутер +- GET `/` — для юзеров и админов +- Админ: dropdown = все компании из БД (`getAllCompanies()`), по умолчанию своя +- Юзер: dropdown = свои профили из IAM, по умолчанию активный +- `?switchTo=W***` — смена компании: админ → любая, юзер → только свои +- CRUD без изменений (add/edit/delete через `req.clientId`) + +### `views/v2/user.ejs` — единый шаблон +- Дизайн из оригинала: лого Nubes, CSS-переменные, card-раскладка +- Хедер: лого | "IP WhiteList v{VERSION}" | email (админ) | Выйти (реальный юзер) +- Dropdown компаний + таблица IP с inline CRUD +- Статистика, форма добавления, поддержка удалённых записей + +### `v2/server.js` +- `/v2/admin` → 302 на `/v2/app` +- `toggle-admin` → всегда редирект на `/v2/app` +- Убран импорт `createAdminRouter` + +### Версии +- `v2/src/config/index.js`: 0.5.98 → 0.5.99 +- `package.json`: 0.5.101 → 0.5.102 + +## Синтаксис +`node -c` — OK на всех изменённых файлах. diff --git a/v2/server.js b/v2/server.js index 3049b67..3b1569e 100644 --- a/v2/server.js +++ b/v2/server.js @@ -28,7 +28,6 @@ const auth = require('./src/auth'); const { resolveContext } = require('./src/router'); const { enhanceImpersonation } = require('./src/impersonation'); const { createUserRouter } = require('./src/user'); -const { createAdminRouter } = require('./src/admin'); const { pool } = require('./src/db'); const { ensureSchema } = require('./src/db/schema'); @@ -53,8 +52,8 @@ function createV2Router() { // ── /v2/app — пользовательский CRUD (только с сессией) ────────────── router.use('/app', enhanceImpersonation, resolveContext, createUserRouter()); - // ── /v2/admin — админка (только для isAdmin=true) ──────────────────── - router.use('/admin', enhanceImpersonation, resolveContext, createAdminRouter()); + // ── /v2/admin — редирект на единый интерфейс ─────────────────── + router.get('/admin', (req, res) => res.redirect('/v2/app')); // ── POST /v2/toggle-admin — переключить режим администратора ──── router.post('/toggle-admin', (req, res) => { @@ -63,10 +62,9 @@ function createV2Router() { const canAdmin = (u.allClientIds || []).includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin; if (!canAdmin) return res.redirect('/v2/app'); u.adminMode = !u.adminMode; - const target = u.adminMode ? '/v2/admin' : '/v2/app'; req.session.save(err => { if (err) console.error('[toggle-admin] session save error:', err.message); - res.redirect(target); + res.redirect('/v2/app'); }); }); diff --git a/v2/src/config/index.js b/v2/src/config/index.js index 4559e21..985c632 100644 --- a/v2/src/config/index.js +++ b/v2/src/config/index.js @@ -9,7 +9,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ module.exports = { - version: '0.5.98', + version: '0.5.99', // ── IAM ────────────────────────────────────────────────────────────────── iamUrl: process.env.V2_IAM_URL || 'https://auth-api.ngcloud.ru/api/v1/auth/user', diff --git a/v2/src/user/index.js b/v2/src/user/index.js index 9b9903e..212712f 100644 --- a/v2/src/user/index.js +++ b/v2/src/user/index.js @@ -1,15 +1,22 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — Фронтенд пользователя (Express + EJS) +// V2 — Единый фронтенд (Express + EJS) — юзеры и админы // -// ЭТО: Express-роутер, рендерит EJS-шаблоны. +// ЭТО: Express-роутер, рендерит EJS-шаблон views/v2/user.ejs. // НЕ: работа с БД (только через crud/). // -// ШАБЛОН: views/v2/user.ejs — данные: entries, limit, used, companies, -// activeClientId, user, includeDeleted, version. +// ОДИН ИНТЕРФЕЙС для юзеров и админов: +// - Юзер: dropdown = свои профили из IAM, CRUD внутри выбранной компании. +// - Админ: dropdown = все компании из БД, CRUD внутри выбранной. +// - По умолчанию: активный профиль (is_active_profile=true). +// +// ПЕРЕКЛЮЧЕНИЕ КОМПАНИИ: ?switchTo=W*** → обновляет activeClientId в сессии. +// - Юзер: только свои профили. +// - Админ: любая компания. // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); const crud = require('../crud'); +const q = require('../db/queries'); const config = require('../config'); function createUserRouter() { @@ -18,29 +25,71 @@ function createUserRouter() { // ── GET / — список записей + форма ────────────────────────────────── router.get('/', async (req, res) => { try { + const isAdmin = req.isAdmin; const clId = req.clientId; - const allCompanies = req.profiles.length > 0 - ? req.profiles - : [{ client_id: clId, company_name: req.companyName || clId }]; + // ── Переключение компании ────────────────────────────────────── + if (req.query.switchTo) { + const targetId = req.query.switchTo; + if (isAdmin) { + // Админ может переключиться на любую компанию + req.session.user.activeClientId = targetId; + req.session.save(() => res.redirect('/v2/app')); + return; + } else { + // Юзер — только свои профили + const allowed = (req.profiles || []).find(p => p.client_id === targetId); + if (allowed) { + req.session.user.activeClientId = targetId; + req.session.save(() => res.redirect('/v2/app')); + return; + } + } + } + + // ── Компании для dropdown ─────────────────────────────────────── + let companies; + if (isAdmin) { + const all = await q.getAllCompanies(); + companies = all.map(c => ({ + client_id: c.client_id, + name: c.name || c.client_id, + active_count: c.active_count, + })); + } else { + companies = (req.profiles && req.profiles.length > 0) + ? req.profiles.map(p => ({ + client_id: p.client_id, + name: p.company_name || p.client_id, + is_active: p.is_active_profile, + })) + : [{ client_id: clId, name: req.companyName || clId, is_active: true }]; + } + + // ── Записи ────────────────────────────────────────────────────── const includeDeleted = req.query.deleted === '1'; const { entries, used, limit } = await crud.list(clId, includeDeleted); - // Переключение компании - if (req.query.switchTo && allCompanies.find(c => c.client_id === req.query.switchTo)) { - req.session.user.activeClientId = req.query.switchTo; - return res.redirect('/v2/app'); - } + // ── Данные пользователя для шаблона ──────────────────────────── + const sessionUser = req.session && req.session.user ? req.session.user : {}; + const templateUser = { + email: req.email, + fio: sessionUser.fio || null, + isAdmin: req.canAdmin || false, + adminMode: isAdmin, + isImpersonated: req.isImpersonated || false, + originalUserEmail: sessionUser.originalUserEmail || '', + originalUserFullName: sessionUser.originalUserFullName || '', + originalUserCompany: sessionUser.originalUserCompany || '', + }; res.render('v2/user', { entries, limit, used, - companies: allCompanies, + companies, activeClientId: clId, - user: req.user || {}, + user: templateUser, includeDeleted, version: config.version, - canAdmin: req.canAdmin || false, - adminMode: req.adminMode || false, }); } catch (e) { res.status(500).send('
' + e.message + 'Назад'); diff --git a/views/v2/user.ejs b/views/v2/user.ejs index fe89354..c171da0 100644 --- a/views/v2/user.ejs +++ b/views/v2/user.ejs @@ -1,77 +1,278 @@ -
Записей: <%= used %> из <%= limit %>
-| CIDR | Комментарий | Кто | Когда | |
|---|---|---|---|---|
| Нет записей | ||||
| <%= e.value_cidr %> | -<%= e.comment || '' %> | -<%= e.created_by %> | -<%= new Date(e.created_at).toLocaleString('ru') %> | -
- <% if (e.deleted_at) { %>
- удалено
- <% } else { %>
-
+
+ |
-
<%= includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые' %>
- + + + + + +| Адрес / Подсеть | +Комментарий | +Добавил | +Добавлена | +Действия | +
|---|---|---|---|---|
<%= e.value_cidr %><% if (e.deleted_at) { %> удалена<% } %> |
+ <%= e.comment || '—' %> | +<%= e.created_by %> | +<%= new Date(e.created_at).toLocaleString('ru') %> | +
+ <% if (!e.deleted_at) { %>
+ |
+