v0.5.166: /export→/v2/export редирект, экспорт только админам
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ui/routes/entries.js — главная страница: список записей + CRUD.
|
||||
*
|
||||
* Данные из /api/v1/entries (Bearer token из сессии).
|
||||
* Admin: ?company=<id> (числовой PK) для управления конкретной компанией.
|
||||
* - Без параметра: ищем компанию admin в списке по clientId.
|
||||
* Если не найдена (первый вход) — один GET /api/v1/entries создаёт её,
|
||||
* потом редирект на числовой ID.
|
||||
* - С параметром: грузим записи этой компании через admin-ветку API.
|
||||
* User: без параметра, API сам определяет компанию по clientId из токена.
|
||||
*/
|
||||
|
||||
const { Router } = require('express');
|
||||
const api = require('../api-client');
|
||||
|
||||
function createRouter() {
|
||||
const router = Router();
|
||||
|
||||
// URL для возврата после CRUD — всегда с числовым company ID
|
||||
function backUrl(req) {
|
||||
const id = req.query.company || (req.body && req.body.company_id);
|
||||
return id ? '/?company=' + id : '/';
|
||||
}
|
||||
|
||||
// Суффикс ?company=<id> для API — только числовой ID или пусто
|
||||
function companyQuery(req) {
|
||||
const id = req.query.company || (req.body && req.body.company_id);
|
||||
if (id) return '?company=' + id;
|
||||
// Мульти-компания: передаём активный client_id
|
||||
if (!req.user.isAdmin && req.user.allClientIds && req.user.allClientIds.length > 1) {
|
||||
return '?client_id=' + encodeURIComponent(req.user.activeClientId || req.user.clientId);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// GET / — список записей
|
||||
router.get('/', async (req, res) => {
|
||||
const token = api.token(req);
|
||||
const imp = api.impHeaders(req);
|
||||
|
||||
// ── Переключатель компании через IAM ────────────────────────────────
|
||||
const switchTo = (req.query.switchTo || '').trim();
|
||||
if (!req.user.isAdmin && switchTo && req.user.profiles && req.user.profiles.length > 1) {
|
||||
const targetProfile = req.user.profiles.find(p => p.client_id === switchTo);
|
||||
if (targetProfile) {
|
||||
try {
|
||||
const { switchProfile } = require('../../src/auth');
|
||||
await switchProfile(token, targetProfile.id);
|
||||
// Обновляем сессию: новый активный профиль
|
||||
req.session.user.activeClientId = switchTo;
|
||||
req.session.user.clientId = switchTo;
|
||||
req.session.user.companyName = targetProfile.company_name;
|
||||
req.session.user.companyId = targetProfile.company_id;
|
||||
req.session.user.activeProfileId = targetProfile.id;
|
||||
// Обновляем is_active_profile в массиве
|
||||
req.session.user.profiles.forEach(p => {
|
||||
p.is_active_profile = (p.client_id === switchTo);
|
||||
});
|
||||
req.user.activeClientId = switchTo;
|
||||
req.user.clientId = switchTo;
|
||||
} catch (e) {
|
||||
console.warn('[entries] switchProfile failed:', e.message);
|
||||
// Локальный fallback без IAM
|
||||
req.session.user.activeClientId = switchTo;
|
||||
if (req.session.user.clientId) req.session.user.clientId = switchTo;
|
||||
req.user.activeClientId = switchTo;
|
||||
}
|
||||
}
|
||||
} else if (!req.user.isAdmin && switchTo && req.user.allClientIds && req.user.allClientIds.includes(switchTo)) {
|
||||
// Fallback: без profiles (mock-режим или IAM не ответил)
|
||||
req.session.user.activeClientId = switchTo;
|
||||
if (req.session.user.clientId) req.session.user.clientId = switchTo;
|
||||
req.user.activeClientId = switchTo;
|
||||
}
|
||||
|
||||
try {
|
||||
let entries = [], limit = 15, selectedCompany = null, companies = [];
|
||||
|
||||
if (req.user.adminMode) {
|
||||
const cr = await api.get('/api/v1/companies', token, imp);
|
||||
companies = cr.data.companies || [];
|
||||
|
||||
const companyId = req.query.company ? parseInt(req.query.company, 10) : null;
|
||||
|
||||
if (companyId) {
|
||||
selectedCompany = companies.find(c => c.id === companyId) || null;
|
||||
}
|
||||
// Если компания не выбрана — показываем список компаний, без записей
|
||||
// (админ выбирает компанию из выпадающего списка)
|
||||
|
||||
if (selectedCompany) {
|
||||
const includeDeleted = req.query.includeDeleted === 'true' ? '&includeDeleted=true' : '';
|
||||
const er = await api.get('/api/v1/entries?company=' + selectedCompany.id + includeDeleted, token, imp);
|
||||
entries = er.data.entries || [];
|
||||
limit = er.data.limit || 15;
|
||||
}
|
||||
} else {
|
||||
// Обычный пользователь: запрашиваем записи для активной компании
|
||||
const activeClientId = req.user.activeClientId || req.user.clientId;
|
||||
const clientIdParam = req.user.allClientIds && req.user.allClientIds.length > 1
|
||||
? '?client_id=' + encodeURIComponent(activeClientId)
|
||||
: '';
|
||||
const er = await api.get('/api/v1/entries' + clientIdParam, token, imp);
|
||||
entries = er.data.entries || [];
|
||||
limit = er.data.limit || 15;
|
||||
}
|
||||
|
||||
const hasMultiple = !req.user.isAdmin && req.user.allClientIds && req.user.allClientIds.length > 1;
|
||||
const profiles = req.user.profiles || [];
|
||||
|
||||
res.render('index', {
|
||||
entries,
|
||||
limit,
|
||||
used: entries.length,
|
||||
user: req.user,
|
||||
isAdmin: req.user.isAdmin,
|
||||
adminMode: req.user.adminMode,
|
||||
companies,
|
||||
selectedCompany,
|
||||
canAdminMode: req.user.canAdminMode,
|
||||
isImpersonated: req.user.isImpersonated,
|
||||
originalUserEmail: req.user.originalUserEmail,
|
||||
allClientIds: hasMultiple ? req.user.allClientIds : null,
|
||||
activeClientId: req.user.activeClientId || req.user.clientId,
|
||||
profiles, // из IAM — для UI переключателя
|
||||
error: req.query.error || null,
|
||||
success: req.query.success || null,
|
||||
lastValue: req.query.lastValue || '',
|
||||
lastComment: req.query.lastComment || '',
|
||||
includeDeleted: req.query.includeDeleted === 'true',
|
||||
csrfToken: '',
|
||||
});
|
||||
} catch (e) {
|
||||
res.render('index', {
|
||||
entries: [], limit: 15, used: 0,
|
||||
user: req.user, isAdmin: req.user.isAdmin, adminMode: req.user.adminMode,
|
||||
canAdminMode: req.user.canAdminMode,
|
||||
isImpersonated: req.user.isImpersonated,
|
||||
originalUserEmail: req.user.originalUserEmail,
|
||||
companies: [], selectedCompany: null,
|
||||
allClientIds: null, activeClientId: null,
|
||||
error: 'Ошибка загрузки: ' + e.message,
|
||||
success: null, lastValue: '', lastComment: '',
|
||||
includeDeleted: false, csrfToken: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// POST /add — создать запись
|
||||
router.post('/add', async (req, res) => {
|
||||
const token = api.token(req);
|
||||
const imp = api.impHeaders(req);
|
||||
const { value, comment } = req.body;
|
||||
const cq = companyQuery(req);
|
||||
const back = backUrl(req);
|
||||
try {
|
||||
const r = await api.post('/api/v1/entries' + cq, token, { value, comment }, imp);
|
||||
if (r.status === 201) {
|
||||
const msg = r.data.wasNormalized
|
||||
? 'Запись добавлена (CIDR нормализован до ' + r.data.entry.value_cidr + ')'
|
||||
: 'Запись добавлена';
|
||||
return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent(msg));
|
||||
}
|
||||
// Ошибка — возвращаем введённые значения чтобы не перепечатывать
|
||||
const err = (r.data && r.data.error) || 'Ошибка';
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') +
|
||||
'error=' + encodeURIComponent(err) +
|
||||
'&lastValue=' + encodeURIComponent(value || '') +
|
||||
'&lastComment=' + encodeURIComponent(comment || ''));
|
||||
} catch (e) {
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message));
|
||||
}
|
||||
});
|
||||
|
||||
// POST /edit/:id — обновить запись
|
||||
router.post('/edit/:id', async (req, res) => {
|
||||
const token = api.token(req);
|
||||
const imp = api.impHeaders(req);
|
||||
const { value, comment } = req.body;
|
||||
const cq = companyQuery(req);
|
||||
const back = backUrl(req);
|
||||
try {
|
||||
const r = await api.patch('/api/v1/entries/' + req.params.id + cq, token, { value, comment }, imp);
|
||||
if (r.status === 200) {
|
||||
return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent('Запись обновлена'));
|
||||
}
|
||||
// Ошибка — возвращаем введённые значения в модалку
|
||||
const err = (r.data && r.data.error) || 'Ошибка';
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') +
|
||||
'error=' + encodeURIComponent(err) +
|
||||
'&lastValue=' + encodeURIComponent(value || '') +
|
||||
'&lastComment=' + encodeURIComponent(comment || ''));
|
||||
} catch (e) {
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message));
|
||||
}
|
||||
});
|
||||
|
||||
// POST /delete/:id — удалить запись
|
||||
router.post('/delete/:id', async (req, res) => {
|
||||
const token = api.token(req);
|
||||
const imp = api.impHeaders(req);
|
||||
const cq = companyQuery(req);
|
||||
const back = backUrl(req);
|
||||
try {
|
||||
const r = await api.delete('/api/v1/entries/' + req.params.id + cq, token, imp);
|
||||
if (r.status === 204) {
|
||||
return res.redirect(back + (back.includes('?') ? '&' : '?') + 'success=' + encodeURIComponent('Запись удалена'));
|
||||
}
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent((r.data && r.data.error) || 'Ошибка'));
|
||||
} catch (e) {
|
||||
res.redirect(back + (back.includes('?') ? '&' : '?') + 'error=' + encodeURIComponent(e.message));
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
module.exports = { createRouter };
|
||||
Reference in New Issue
Block a user