diff --git a/views/admin.ejs b/views/admin.ejs new file mode 100644 index 0000000..05f42be --- /dev/null +++ b/views/admin.ejs @@ -0,0 +1,204 @@ + + + + + Управление лимитами — IP WhiteList + + + + + +
+ + + + + + + + + + | + Управление лимитами + + ← Список адресов + Журнал аудита + Выйти (<%= user.email %>) + +
+ +
+ + <% if (message) { %>
<%= message %>
<% } %> + <% if (error) { %>
<%= error %>
<% } %> + +
+
+ Компании и лимиты + + Глобальный дефолт: <%= defaultLimit %> записей. + Пустое поле = сброс на дефолт. + +
+ + <% if (companies.length === 0) { %> +
Нет компаний в системе
+ <% } else { %> +
+ + + + + + + + + + + + <% companies.forEach(c => { %> + <% + // Вычисляем эффективный лимит (кастомный или дефолтный) + const effectiveLimit = c.custom_limit != null ? c.custom_limit : defaultLimit; + const pct = effectiveLimit > 0 ? Math.min(c.active_count / effectiveLimit, 1) : 1; + const barClass = pct >= 1 ? 'full' : pct >= 0.8 ? 'warn' : ''; + %> + + + + + + + + + + + <% }) %> + +
КомпанияClientIDИспользованоЛимитДействие
<%= c.name %><%= c.client_id %> +
+
+
+
+ + <%= c.active_count %> / <%= effectiveLimit %> + <% if (c.custom_limit != null) { %> + + <% } %> + +
+
+ <% if (c.custom_limit != null) { %> + <%= c.custom_limit %> + (индивидуальный) + <% } else { %> + дефолт (<%= defaultLimit %>) + <% } %> + +
+ + + <% if (c.custom_limit != null) { %> + + + <% } %> +
+
+
+ <% } %> +
+ +
+ + + + diff --git a/views/audit.ejs b/views/audit.ejs new file mode 100644 index 0000000..9e4ccb2 --- /dev/null +++ b/views/audit.ejs @@ -0,0 +1,173 @@ + + + + + Журнал аудита — IP WhiteList + + + + + + +
+ + + + + + + + + + | + Журнал аудита + + ← Список адресов + Управление лимитами + Выйти (<%= user.email %>) + +
+ +
+ + <% if (error) { %>
<%= error %>
<% } %> + + +
+
+ Фильтр +
+
+
+ + + <% if (selectedCompanyId) { %> + Сбросить + <% } %> + + Показано: <%= rows.length %> записей (макс 500) + +
+
+
+ + +
+
Записи журнала
+ <% if (rows.length === 0) { %> +
Нет записей в журнале аудита
+ <% } else { %> +
+ + + + + + + + + + + + <% rows.forEach(r => { %> + + + + + + + + + + + + <% }) %> + +
КогдаКтоКомпанияДействиеЗначение
+ <%= new Date(r.created_at).toLocaleString('ru', { + day: '2-digit', month: '2-digit', year: 'numeric', + hour: '2-digit', minute: '2-digit' + }) %> + <%= r.user_email %> + <%= r.company_name %>
+ <%= r.client_id %> +
+ <%= r.action %> + + <% if (r.old_value && r.new_value) { %> + <%= r.old_value %> + + <%= r.new_value %> + <% } else if (r.new_value) { %> + <%= r.new_value %> + <% } else if (r.old_value) { %> + <%= r.old_value %> + <% } else { %> + + <% } %> +
+
+ <% } %> +
+ +
+ + diff --git a/views/index.ejs b/views/index.ejs index f7f3b7c..be4d8e1 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -157,6 +157,14 @@ | Белые списки IP + + + <% if (isAdmin) { %> + Аудит + Лимиты + <% } %> + Выйти (<%= user.email %>) +
@@ -164,6 +172,40 @@ <% if (message) { %>
<%= message %>
<% } %> <% if (error) { %>
<%= error %>
<% } %> + <% + // Для admin: selectedCompany — выбранная компания (объект из getAllCompanies). + // Для обычного пользователя: selectedCompany — их компания (из getOrCreateCompany). + // company_id — числовой PK для передачи в скрытые поля форм. + const companyPk = selectedCompany ? selectedCompany.id : null; + %> + + + <% if (isAdmin && companies) { %> +
+
+
+ Компания: + + + → Аудит этой компании + → Лимиты + + Режим: Администратор + +
+
+
+ <% } %> +
@@ -173,7 +215,8 @@
записей
-
<%= user.clientId %>
+ +
<%= selectedCompany ? selectedCompany.client_id : user.clientId %>
компания
@@ -189,6 +232,13 @@
Добавить адрес
+ <% + // Передаём числовой PK компании в скрытом поле. + // Для admin: server.js возьмёт его из req.body.company_id. + // Для обычного пользователя: server.js игнорирует это поле + // и использует clientId из токена — защита от подмены. + %> +
@@ -220,7 +270,8 @@ Комментарий Добавил Дата - Действие + Изменён + Действия @@ -230,8 +281,33 @@ <%= e.comment || '—' %> <%= e.created_by %> <%= new Date(e.created_at).toLocaleDateString('ru', {day:'numeric',month:'short',year:'numeric',hour:'2-digit',minute:'2-digit'}) %> - - + + + <%= e.updated_at + ? new Date(e.updated_at).toLocaleDateString('ru', {day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'}) + : '—' %> + + + <% + // data-* атрибуты для JS-заполнения modal без inline-onclick с параметрами. + // Так как комментарий может содержать кавычки — EJS экранирует их в HTML. + // Но в data-атрибуте JSON::stringify безопаснее. + %> + + + + + @@ -244,5 +320,83 @@
+ + + + + + +