Bans: confirm unban via alert; audit labels for ip.ban/ip.unban; worker page lucide icons
This commit is contained in:
@@ -339,6 +339,8 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
||||
.sys-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:4px 0}
|
||||
.sys-label{font-size:.82rem;color:var(--muted)}
|
||||
.sys-value{font-size:.9rem;font-weight:600;color:var(--text);text-align:right;word-break:break-word}
|
||||
.lucide-inline{display:inline-flex;align-items:center;vertical-align:-2px}
|
||||
.lucide-inline svg{width:14px;height:14px;flex-shrink:0}
|
||||
.sys-details{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}
|
||||
.sys-details summary{cursor:pointer;font-size:.75rem;color:var(--muted);font-weight:600}
|
||||
.sys-table-list{margin-top:8px;display:flex;flex-direction:column;gap:4px}
|
||||
|
||||
@@ -4,6 +4,8 @@ const ACTION_LABELS = {
|
||||
'user.create': 'Создан пользователь',
|
||||
'user.update': 'Изменён пользователь',
|
||||
'user.delete': 'Удалён пользователь',
|
||||
'ip.ban': 'Заблокирован IP-адрес',
|
||||
'ip.unban': 'Разблокирован IP-адрес',
|
||||
'group.create': 'Создана группа',
|
||||
'group.update': 'Изменена группа',
|
||||
'group.delete': 'Удалена группа',
|
||||
|
||||
+4
-29
@@ -49,43 +49,18 @@ function renderPage() {
|
||||
renderPager(document.getElementById('pager'), page, Math.max(1, Math.ceil(allBans.length / PAGE_SIZE)), p => { page = p; renderPage(); });
|
||||
}
|
||||
|
||||
function askUnban(ip) {
|
||||
const old = document.getElementById('unbanConfirmRow');
|
||||
if (old) old.remove();
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = 'unbanConfirmRow';
|
||||
tr.innerHTML = `
|
||||
<td colspan="5" style="text-align:center;padding:14px;background:rgba(239,68,68,.06)">
|
||||
Разблокировать <b>${esc(ip)}</b>?
|
||||
<button class="btn-primary" id="unbanYes" type="button" style="padding:8px 18px;margin-left:12px">Да, разблокировать</button>
|
||||
<button type="button" class="btn-primary ghost" id="unbanNo" style="padding:8px 18px;margin-left:8px">Отмена</button>
|
||||
</td>`;
|
||||
tr.querySelector('#unbanYes').addEventListener('click', () => doUnban(ip));
|
||||
tr.querySelector('#unbanNo').addEventListener('click', () => tr.remove());
|
||||
document.getElementById('bansBody').appendChild(tr);
|
||||
tr.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
}
|
||||
|
||||
async function doUnban(ip) {
|
||||
const ban = allBans.find(b => b.ip === ip) || {};
|
||||
if (!confirm(`Удалить IP ${ip} из блокировки (${reasonLabel(ban.reason)})?`)) return;
|
||||
const res = await fetch(`${API}/api/bans/${encodeURIComponent(ip)}`, { method: 'DELETE', headers: hdr() });
|
||||
if (res.ok) showToast('IP разблокирован: ' + ip);
|
||||
if (res.ok) showToast('IP удалён из блокировки: ' + ip);
|
||||
else showToast('Ошибка разблокировки');
|
||||
document.getElementById('unbanConfirmRow')?.remove();
|
||||
loadBans();
|
||||
}
|
||||
|
||||
document.getElementById('bansBody').addEventListener('click', e => {
|
||||
const ip = e.target.closest('[data-unban]')?.dataset.unban;
|
||||
if (ip) askUnban(ip);
|
||||
});
|
||||
|
||||
document.getElementById('bansBody').addEventListener('click', async e => {
|
||||
const ip = e.target.closest('[data-unban]')?.dataset.unban;
|
||||
if (!ip) return;
|
||||
const res = await fetch(`${API}/api/bans/${encodeURIComponent(ip)}`, { method: 'DELETE', headers: hdr() });
|
||||
if (res.ok) showToast('IP разблокирован: ' + ip);
|
||||
else showToast('Ошибка разблокировки');
|
||||
loadBans();
|
||||
if (ip) doUnban(ip);
|
||||
});
|
||||
|
||||
function openBanModal() {
|
||||
|
||||
+3
-2
@@ -77,7 +77,7 @@ function renderCards() {
|
||||
<div class="sys-card">
|
||||
<div class="sys-title">Модель</div>
|
||||
<div class="sys-item"><span class="sys-label">Модель</span><span class="sys-value">${esc(state.model)}</span></div>
|
||||
<div class="sys-item"><span class="sys-label">Сервис</span><span class="sys-value">${svc.reachable ? '✅ доступен' : '❌ недоступен'}</span></div>
|
||||
<div class="sys-item"><span class="sys-label">Сервис</span><span class="sys-value">${svc.reachable ? '<span class="lucide-inline" style="color:#16a34a"><i data-lucide="check"></i></span> доступен' : '<span class="lucide-inline" style="color:#ef4444"><i data-lucide="x"></i></span> недоступен'}</span></div>
|
||||
<div class="sys-item"><span class="sys-label">Отклик</span><span class="sys-value">${svc.latency_ms !== undefined ? svc.latency_ms + ' мс' : '—'}</span></div>
|
||||
<div class="sys-item"><span class="sys-label">Адрес</span><span class="sys-value" style="font-size:.75rem">${esc(state.ai_url || '')}</span></div>
|
||||
${svc.error ? `<div class="sys-meta" style="color:#ef4444">${esc(svc.error)}</div>` : ''}
|
||||
@@ -149,7 +149,7 @@ function renderRecent() {
|
||||
<td>${esc(r.student_name)}</td>
|
||||
<td style="color:var(--muted)">${esc(r.group_name || '')}</td>
|
||||
<td>${statusBadge(r.ai_status)}</td>
|
||||
<td>${r.changed ? '✏️ исправлен' : '—'}</td>
|
||||
<td>${r.changed ? '<span class="lucide-inline" style="color:#f59e0b"><i data-lucide="pencil"></i></span> исправлен' : '—'}</td>
|
||||
<td><button type="button" class="ai-details" data-ai-idx="${i}">Подробнее</button></td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
@@ -175,6 +175,7 @@ function render() {
|
||||
renderPending();
|
||||
renderRecent();
|
||||
renderErrors();
|
||||
renderIcons();
|
||||
}
|
||||
|
||||
async function load() {
|
||||
|
||||
+3
-3
@@ -36,7 +36,7 @@
|
||||
<div class="settings-stack settings-stack-grid" style="margin-top:20px">
|
||||
<div class="settings-card wide">
|
||||
<div class="card-head">
|
||||
<div class="ic">⏳</div>
|
||||
<div class="ic"><i data-lucide="hourglass"></i></div>
|
||||
<h3>Очередь (<span id="pendingCount">0</span>)</h3>
|
||||
</div>
|
||||
<p class="hint">Записи, ожидающие проверки моделью. Обновляются автоматически.</p>
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<div class="settings-card wide">
|
||||
<div class="card-head">
|
||||
<div class="ic">🕒</div>
|
||||
<div class="ic"><i data-lucide="history"></i></div>
|
||||
<h3>Последние проверки</h3>
|
||||
</div>
|
||||
<div class="actions-row" style="margin-bottom:10px">
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<div class="settings-card wide" id="errorsCard" style="display:none">
|
||||
<div class="card-head">
|
||||
<div class="ic">⚠️</div>
|
||||
<div class="ic"><i data-lucide="triangle-alert"></i></div>
|
||||
<h3>Ошибки (<span id="errorsCount">0</span>)</h3>
|
||||
</div>
|
||||
<p class="hint">Записи, которые не удалось обработать после всех попыток. Нажмите «Перепроверить ошибки», чтобы запустить их снова.</p>
|
||||
|
||||
Reference in New Issue
Block a user