fix: remove inline JS handlers (onclick/onchange/onsubmit) -> event delegation + bump 0.4.4

This commit is contained in:
2026-05-31 06:26:47 +03:00
parent a6177c4cef
commit c19480336d
2 changed files with 52 additions and 37 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ipwhitelist", "name": "ipwhitelist",
"version": "0.4.3", "version": "0.4.4",
"description": "IP WhiteList microservice for cloud provider", "description": "IP WhiteList microservice for cloud provider",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+46 -31
View File
@@ -185,7 +185,7 @@
<div class="card-body" style="padding:.75rem 1rem;"> <div class="card-body" style="padding:.75rem 1rem;">
<form method="GET" action="/" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;"> <form method="GET" action="/" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;">
<span style="font-size:.85rem;color:var(--muted);font-weight:500;">Компания:</span> <span style="font-size:.85rem;color:var(--muted);font-weight:500;">Компания:</span>
<select name="company" onchange="this.form.submit()" <select name="company" id="company-select"
style="padding:.4rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.85rem;background:#fff;"> style="padding:.4rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.85rem;background:#fff;">
<% companies.forEach(c => { %> <% companies.forEach(c => { %>
<option value="<%= c.id %>" <%= selectedCompany && c.id === selectedCompany.id ? 'selected' : '' %>> <option value="<%= c.id %>" <%= selectedCompany && c.id === selectedCompany.id ? 'selected' : '' %>>
@@ -194,6 +194,7 @@
<% }) %> <% }) %>
</select> </select>
<!-- Быстрый доступ к admin-страницам прямо из главной --> <!-- Быстрый доступ к admin-страницам прямо из главной -->
<button type="submit" class="btn btn-sm">Выбрать</button>
<a href="/audit<%= selectedCompany ? '?company=' + selectedCompany.id : '' %>" <a href="/audit<%= selectedCompany ? '?company=' + selectedCompany.id : '' %>"
style="font-size:.82rem;color:var(--muted);text-decoration:none;">→ Аудит этой компании</a> style="font-size:.82rem;color:var(--muted);text-decoration:none;">→ Аудит этой компании</a>
<a href="/admin" <a href="/admin"
@@ -296,19 +297,19 @@
// Но в data-атрибуте JSON::stringify безопаснее. // Но в data-атрибуте JSON::stringify безопаснее.
%> %>
<!-- Кнопка редактирования: открывает modal и заполняет его данными из data-* --> <!-- Кнопка редактирования: открывает modal и заполняет его данными из data-* -->
<button class="btn" <button class="btn btn-edit"
data-id="<%= e.id %>" data-id="<%= e.id %>"
data-value="<%= e.value_cidr %>" data-value="<%= e.value_cidr %>"
data-comment="<%= e.comment || '' %>" data-comment="<%= e.comment || '' %>"
data-company="<%= companyPk || '' %>" data-company="<%= companyPk || '' %>"
onclick="openEditModal(this)"
style="margin-right:.25rem;"> style="margin-right:.25rem;">
Изменить Изменить
</button> </button>
<!-- Форма удаления: company_id нужен для admin-ветки в server.js --> <!-- Форма удаления: company_id нужен для admin-ветки в server.js -->
<form method="POST" action="/delete/<%= e.id %>" <form method="POST" action="/delete/<%= e.id %>"
style="display:inline" class="delete-form"
onsubmit="return confirm('Удалить запись <%= e.value_cidr %>?')"> data-cidr="<%= e.value_cidr %>"
style="display:inline">
<input type="hidden" name="company_id" value="<%= companyPk || '' %>"> <input type="hidden" name="company_id" value="<%= companyPk || '' %>">
<%# CSRF-токен: одно значение на всю страницу, сгенерировано сервером %> <%# CSRF-токен: одно значение на всю страницу, сгенерировано сервером %>
<input type="hidden" name="_csrf" value="<%= csrfToken %>"> <input type="hidden" name="_csrf" value="<%= csrfToken %>">
@@ -361,7 +362,7 @@
style="padding:.5rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.9rem;"> style="padding:.5rem .75rem;border:1px solid var(--border);border-radius:6px;font-size:.9rem;">
</div> </div>
<div style="display:flex;gap:.75rem;justify-content:flex-end;"> <div style="display:flex;gap:.75rem;justify-content:flex-end;">
<button type="button" class="btn" onclick="closeEditModal()">Отмена</button> <button type="button" class="btn" id="edit-cancel">Отмена</button>
<button type="submit" class="btn btn-primary">Сохранить</button> <button type="submit" class="btn btn-primary">Сохранить</button>
</div> </div>
</form> </form>
@@ -369,39 +370,53 @@
</div> </div>
<script> <script>
/** (function () {
* Открыть modal редактирования. var modal = document.getElementById('edit-modal');
* Вызывается из onclick кнопки "Изменить" в таблице.
* @param {HTMLElement} btn — кнопка с data-id, data-value, data-comment, data-company function openModal(btn) {
*/ document.getElementById('edit-value').value = btn.getAttribute('data-value');
function openEditModal(btn) { document.getElementById('edit-comment').value = btn.getAttribute('data-comment');
// Заполняем поля из data-атрибутов (EJS уже HTML-экранировал значения) document.getElementById('edit-company-id').value = btn.getAttribute('data-company');
document.getElementById('edit-value').value = btn.dataset.value; document.getElementById('edit-form').action = '/edit/' + btn.getAttribute('data-id');
document.getElementById('edit-comment').value = btn.dataset.comment;
document.getElementById('edit-company-id').value = btn.dataset.company;
// Устанавливаем action формы на конкретный /edit/:id
document.getElementById('edit-form').action = '/edit/' + btn.dataset.id;
// Показываем modal (display:flex для центрирования)
const modal = document.getElementById('edit-modal');
modal.style.display = 'flex'; modal.style.display = 'flex';
// Фокус на поле ввода для удобства клавиатурных пользователей setTimeout(function() { document.getElementById('edit-value').focus(); }, 50);
setTimeout(() => document.getElementById('edit-value').focus(), 50);
} }
/** Закрыть modal без сохранения */ function closeModal() {
function closeEditModal() { modal.style.display = 'none';
document.getElementById('edit-modal').style.display = 'none';
} }
// Закрытие кликом по backdrop (за пределами карточки) // Кнопки «Изменить» — event delegation на document
document.getElementById('edit-modal').addEventListener('click', function(e) { document.addEventListener('click', function (e) {
if (e.target === this) closeEditModal(); var btn = e.target.closest('.btn-edit');
if (btn) { openModal(btn); return; }
var cancel = e.target.closest('#edit-cancel');
if (cancel) { closeModal(); return; }
// Клик по backdrop
if (e.target === modal) { closeModal(); return; }
}); });
// Закрытие клавишей Escape // Подтверждение удаления — event delegation
document.addEventListener('keydown', function(e) { document.addEventListener('submit', function (e) {
if (e.key === 'Escape') closeEditModal(); var form = e.target.closest('.delete-form');
if (!form) return;
var cidr = form.getAttribute('data-cidr');
if (!confirm('Удалить запись ' + cidr + '?')) e.preventDefault();
}); });
// Смена компании — авто-сабмит при выборе
var sel = document.getElementById('company-select');
if (sel) {
sel.addEventListener('change', function () { this.form.submit(); });
}
// Escape закрывает modal
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') closeModal();
});
}());
</script> </script>
<footer style="text-align:right;padding:.5rem 1.5rem;font-size:.72rem;color:var(--muted);">v<%= appVersion %></footer> <footer style="text-align:right;padding:.5rem 1.5rem;font-size:.72rem;color:var(--muted);">v<%= appVersion %></footer>