diff --git a/package.json b/package.json index 5309b12..d5699d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.96", + "version": "0.5.97", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/src/admin/index.js b/v2/src/admin/index.js index 284e381..6f26e07 100644 --- a/v2/src/admin/index.js +++ b/v2/src/admin/index.js @@ -1,65 +1,51 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — Админка +// V2 — Админка (Express + EJS) // -// ЭТО: Express-роутер для администратора (isAdmin=true). -// НЕ: доступно обычным юзерам (middleware проверяет req.isAdmin). +// ЭТО: Express-роутер для админа, рендерит EJS-шаблоны. +// НЕ: доступно обычным юзерам (middleware: req.isAdmin). // -// ЗАЧЕМ: -// 1. Видеть ВСЕ компании и их статистику (дашборд). -// 2. Смотреть аудит любой компании. -// 3. Устанавливать персональные лимиты. -// 4. Просматривать записи любой компании. -// -// ДАННЫЕ: -// crud.list(clientId) — записи компании (через API-слой) -// q.getAllCompanies() — все компании со счётчиками -// q.getAudit(companyId) — аудит (null = все компании) -// q.setLimit(id, limit) — персональный лимит -// -// МАРШРУТЫ: -// GET /admin — дашборд: таблица всех компаний -// GET /admin/audit — аудит (?companyId=X — фильтр) -// POST /admin/limit — установить лимит (body: companyId, limit) -// GET /admin/entries — записи компании (?companyId=X) +// ШАБЛОНЫ: +// views/v2/admin.ejs — дашборд (companies, user, version) +// views/v2/admin-audit.ejs — аудит (audit, company, version) +// views/v2/admin-entries.ejs — записи (entries, company, used, limit, ...) // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); -const q = require('../db/queries'); // admin-specific: аудит, лимиты, все компании -const crud = require('../crud'); // CRUD для просмотра записей +const q = require('../db/queries'); +const crud = require('../crud'); +const config = require('../config'); function createAdminRouter() { const router = express.Router(); - // ── middleware: только админы ─────────────────────────────────────── router.use((req, res, next) => { if (!req.isAdmin) return res.redirect('/v2/app'); next(); }); - // ── GET / — дашборд: все компании ────────────────────────────────── + // ── GET / — дашборд ───────────────────────────────────────────────── router.get('/', async (req, res) => { try { const companies = await q.getAllCompanies(); - const version = require('../config').version; - res.send(renderDashboard({ companies, user: req.user || {}, version })); + res.render('v2/admin', { companies, user: req.user || {}, version: config.version }); } catch (e) { res.status(500).send('
' + e.message + ''); } }); - // ── GET /audit?companyId=X — журнал действий ─────────────────────── + // ── GET /audit ────────────────────────────────────────────────────── router.get('/audit', async (req, res) => { try { const companyId = req.query.companyId ? parseInt(req.query.companyId) : null; const audit = await q.getAudit(companyId); const company = companyId ? await q.getCompanyById(companyId) : null; - res.send(renderAudit({ audit, company, version: require('../config').version })); + res.render('v2/admin-audit', { audit, company, version: config.version }); } catch (e) { res.status(500).send('
' + e.message + ''); } }); - // ── POST /limit — установить персональный лимит ──────────────────── + // ── POST /limit ───────────────────────────────────────────────────── router.post('/limit', async (req, res) => { try { const companyId = parseInt(req.body.companyId); @@ -72,16 +58,15 @@ function createAdminRouter() { } }); - // ── GET /entries?companyId=X — записи любой компании ─────────────── + // ── GET /entries ──────────────────────────────────────────────────── router.get('/entries', async (req, res) => { try { const companyId = parseInt(req.query.companyId); const company = companyId ? await q.getCompanyById(companyId) : null; if (!company) return res.redirect('/v2/admin'); - // crud.list принимает clientId (W-номер), не внутренний id - const { entries, used, limit } = await crud.list(company.client_id, req.query.deleted === '1'); - const version = require('../config').version; - res.send(renderEntries({ entries, company, used, limit, includeDeleted: req.query.deleted === '1', version })); + const includeDeleted = req.query.deleted === '1'; + const { entries, used, limit } = await crud.list(company.client_id, includeDeleted); + res.render('v2/admin-entries', { entries, company, used, limit, includeDeleted, version: config.version }); } catch (e) { res.status(500).send('
' + e.message + ''); } @@ -90,126 +75,4 @@ function createAdminRouter() { return router; } -// ═══════════════════════════════════════════════════════════════════════════ -// HTML-рендеринг (временный, будет заменён на EJS) -// ═══════════════════════════════════════════════════════════════════════════ - -function renderDashboard({ companies, user, version }) { - const rows = companies.map(c => ` -
${c.client_id}| Компания | Client ID | Записей | Лимит | Новый лимит | |
|---|---|---|---|---|---|
| Нет компаний | |||||
| Дата | Действие | Кто | От имени | Компания | Значения |
|---|---|---|---|---|---|
| Нет записей | |||||
Показано: ${audit.length} (макс 500)
-`; -} - -function renderEntries({ entries, company, used, limit, includeDeleted, version }) { - const rows = entries.map(e => { - const del = e.deleted_at; - const style = del ? 'text-decoration:line-through;opacity:0.5' : ''; - return `Записей: ${used} из ${limit} ${includeDeleted ? '(с удалёнными: ' + entries.length + ')' : ''}
-| CIDR | Комментарий | Кто | Когда | Статус |
|---|---|---|---|---|
| Нет записей | ||||
${includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые'}
-`; -} - -// ── msgHtml() — скрипт для показа зелёных/красных плашек ────────────── -function msgHtml() { - return ``; -} - module.exports = { createAdminRouter }; diff --git a/v2/src/config/index.js b/v2/src/config/index.js index a6b320d..8d3e552 100644 --- a/v2/src/config/index.js +++ b/v2/src/config/index.js @@ -9,7 +9,7 @@ // ═══════════════════════════════════════════════════════════════════════════════ module.exports = { - version: '0.5.96', + version: '0.5.97', // ── 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 86c59f3..676c283 100644 --- a/v2/src/user/index.js +++ b/v2/src/user/index.js @@ -1,41 +1,24 @@ // ═══════════════════════════════════════════════════════════════════════════════ -// V2 — Фронтенд обычного пользователя +// V2 — Фронтенд пользователя (Express + EJS) // -// ЭТО: Express-роутер, который рендерит HTML и принимает POST-формы. -// НЕ: работа с БД (только через crud/), валидация (в crud/). +// ЭТО: Express-роутер, рендерит EJS-шаблоны. +// НЕ: работа с БД (только через crud/). // -// ЗАЧЕМ: -// 1. Единственное место где генерится HTML для юзера. -// 2. Все действия юзера (add/edit/delete) — через POST-формы. -// 3. Выбор компании — всегда выпадающий список (даже если одна компания). -// 4. Сообщения об ошибках/успехе — через query-параметры (?msg=, ?error=). -// -// КОНТЕКСТ (из resolveContext middleware): -// req.clientId — W-номер текущей компании -// req.email — почта юзера -// req.impersonatedBy — null или почта админа -// req.profiles — список компаний для выпадающего списка -// -// МАРШРУТЫ: -// GET / — список записей + форма добавления -// POST /add — добавить CIDR (→ crud.add → 302 с ?msg=) -// POST /edit/:id — изменить запись -// POST /delete/:id — удалить запись (soft delete) +// ШАБЛОН: views/v2/user.ejs — данные: entries, limit, used, companies, +// activeClientId, user, includeDeleted, version. // ═══════════════════════════════════════════════════════════════════════════════ const express = require('express'); -const crud = require('../crud'); // ← единственная зависимость от данных +const crud = require('../crud'); +const config = require('../config'); function createUserRouter() { const router = express.Router(); - // ── GET / — главная страница ────────────────────────────────────────── + // ── GET / — список записей + форма ────────────────────────────────── router.get('/', async (req, res) => { try { const clId = req.clientId; - - // Список компаний для выпадающего списка - // Если profiles пуст — создаём fallback из clientId (одна компания) const allCompanies = req.profiles.length > 0 ? req.profiles : [{ client_id: clId, company_name: req.companyName || clId }]; @@ -43,31 +26,37 @@ function createUserRouter() { 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'); } - res.send(renderPage({ entries, limit, used, allCompanies, clId, user: req.user, includeDeleted })); + res.render('v2/user', { + entries, limit, used, + companies: allCompanies, + activeClientId: clId, + user: req.user || {}, + includeDeleted, + version: config.version, + }); } catch (e) { res.status(500).send('' + e.message + 'Назад'); } }); - // ── POST /add — добавить запись ─────────────────────────────────────── + // ── POST /add ─────────────────────────────────────────────────────── router.post('/add', async (req, res) => { try { const result = await crud.add(req.clientId, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); const msg = result.wasNormalized ? 'Добавлено (адрес нормализован)' : 'Добавлено'; res.redirect('/v2/app?msg=' + encodeURIComponent(msg)); } catch (e) { - // Ошибка валидации/лимита/дубликата — показываем юзеру res.redirect('/v2/app?error=' + encodeURIComponent(e.message)); } }); - // ── POST /edit/:id — изменить запись ────────────────────────────────── + // ── POST /edit/:id ────────────────────────────────────────────────── router.post('/edit/:id', async (req, res) => { try { const result = await crud.edit(parseInt(req.params.id), req.clientId, req.body.cidr || '', req.body.comment || '', req.email, req.impersonatedBy); @@ -78,7 +67,7 @@ function createUserRouter() { } }); - // ── POST /delete/:id — удалить запись (soft delete) ─────────────────── + // ── POST /delete/:id ──────────────────────────────────────────────── router.post('/delete/:id', async (req, res) => { try { await crud.remove(parseInt(req.params.id), req.clientId, req.email, req.impersonatedBy); @@ -91,69 +80,4 @@ function createUserRouter() { return router; } -// ── renderPage() → HTML ───────────────────────────────────────────────────── -// Временный inline HTML (будет заменён на EJS-шаблоны). -// Данные: entries, limit, used, allCompanies, clId, user, includeDeleted. -function renderPage({ entries, limit, used, allCompanies, clId, user, includeDeleted }) { - const companyOptions = allCompanies.map(c => - `` - ).join(''); - - const rows = entries.map(e => { - const del = e.deleted_at; - const style = del ? 'text-decoration:line-through;opacity:0.5' : ''; - const actions = del ? 'удалено' : ` - - `; - return `
Записей: ${used} из ${limit}
-| CIDR | Комментарий | Кто | Когда | |
|---|---|---|---|---|
| Нет записей | ||||
${includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые'}
-`; -} - module.exports = { createUserRouter }; diff --git a/views/v2/admin-audit.ejs b/views/v2/admin-audit.ejs new file mode 100644 index 0000000..547cdbf --- /dev/null +++ b/views/v2/admin-audit.ejs @@ -0,0 +1,30 @@ + +| Дата | Действие | Кто | От имени | Компания | Значения |
|---|---|---|---|---|---|
| Нет записей | |||||
| <%= new Date(a.created_at).toLocaleString('ru') %> | +<%= a.action %> | +<%= a.user_email %> | +<%= a.impersonated_by || '—' %> | +<%= a.company_name || '—' %> | +<%= a.old_value || '—' %> → <%= a.new_value || '—' %> | +
Показано: <%= audit.length %> (макс 500)
+ diff --git a/views/v2/admin-entries.ejs b/views/v2/admin-entries.ejs new file mode 100644 index 0000000..e277802 --- /dev/null +++ b/views/v2/admin-entries.ejs @@ -0,0 +1,30 @@ + +Записей: <%= used %> из <%= limit %> <%= includeDeleted ? '(с удалёнными: ' + entries.length + ')' : '' %>
+| CIDR | Комментарий | Кто | Когда | Статус |
|---|---|---|---|---|
| Нет записей | ||||
| <%= e.value_cidr %> | +<%= e.comment || '' %> | +<%= e.created_by %> | +<%= new Date(e.created_at).toLocaleString('ru') %> | +<%= e.deleted_at ? 'удалено' : 'активна' %> | +
<%= includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые' %>
+ diff --git a/views/v2/admin.ejs b/views/v2/admin.ejs new file mode 100644 index 0000000..3afdb8a --- /dev/null +++ b/views/v2/admin.ejs @@ -0,0 +1,47 @@ + +| Компания | Client ID | Записей | Лимит | Новый лимит | |
|---|---|---|---|---|---|
| Нет компаний | |||||
| <%= c.name || c.client_id %> | +<%= c.client_id %> |
+ <%= c.active_count %> / <%= c.custom_limit != null ? c.custom_limit : 15 %> | +<%= c.custom_limit != null ? c.custom_limit : '—' %> | ++ аудит | + записи + | ++ + | +
Записей: <%= used %> из <%= limit %>
+| CIDR | Комментарий | Кто | Когда | |
|---|---|---|---|---|
| Нет записей | ||||
| <%= e.value_cidr %> | +<%= e.comment || '' %> | +<%= e.created_by %> | +<%= new Date(e.created_at).toLocaleString('ru') %> | ++ <% if (e.deleted_at) { %> + удалено + <% } else { %> + + + <% } %> + | +
<%= includeDeleted ? 'Скрыть удалённые' : 'Показать удалённые' %>
+