Add audit details modal and translate remaining action labels
- Make Детали cell clickable to open a modal with the full details text\n- Translate missing audit action keys (auth.login, user.*, branch.*, group.photo.*, entry.photo.*)
This commit is contained in:
@@ -353,6 +353,10 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
||||
.audit-table tr:last-child td{border-bottom:none}
|
||||
.audit-table tr:hover td{background:rgba(37,99,235,.04)}
|
||||
.audit-wrap{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);overflow-x:auto}
|
||||
.audit-details{background:none;border:none;padding:0;margin:0;color:var(--accent);font-size:.8rem;text-align:left;cursor:pointer;max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.audit-details:hover{text-decoration:underline}
|
||||
.audit-details-body{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;max-height:60vh;overflow:auto}
|
||||
.audit-details-body pre{margin:0;white-space:pre-wrap;word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.78rem;line-height:1.45;color:var(--text)}
|
||||
|
||||
/* --- AI Correction --- */
|
||||
.ai-btn{background:none;border:none;color:var(--muted);cursor:pointer;font-size:1.1rem;padding:4px 8px;border-radius:6px;transition:color .15s,background .15s;vertical-align:middle;margin-left:6px}
|
||||
|
||||
@@ -38,6 +38,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-overlay" id="auditDetailsModal">
|
||||
<div class="edit-modal" style="max-width:640px">
|
||||
<h3 id="auditDetailsTitle">Детали действия</h3>
|
||||
<div class="audit-details-body"><pre id="auditDetailsBody"></pre></div>
|
||||
<div class="card-foot" style="justify-content:flex-end;gap:8px">
|
||||
<button type="button" class="cancel" id="auditDetailsCloseBtn">Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toast" id="toast"></div>
|
||||
<script src="vendor/lucide.min.js"></script>
|
||||
<script src="admin.js"></script>
|
||||
|
||||
+40
-2
@@ -1,16 +1,28 @@
|
||||
const ACTION_LABELS = {
|
||||
'auth.login': 'Вход в систему',
|
||||
'settings.update': 'Изменены настройки',
|
||||
'user.create': 'Создан пользователь',
|
||||
'user.update': 'Изменён пользователь',
|
||||
'user.delete': 'Удалён пользователь',
|
||||
'group.create': 'Создана группа',
|
||||
'group.update': 'Изменена группа',
|
||||
'group.delete': 'Удалена группа',
|
||||
'group.photo.create': 'Загружена фото группы',
|
||||
'group.photo.update': 'Изменено фото группы',
|
||||
'group.photo.reorder': 'Изменён порядок фото группы',
|
||||
'group.photo.set_cover': 'Обновлена обложка группы',
|
||||
'group.photo.delete': 'Удалено фото группы',
|
||||
'branch.create': 'Создан филиал',
|
||||
'branch.update': 'Изменён филиал',
|
||||
'branch.delete': 'Удалён филиал',
|
||||
'student.create': 'Добавлен ученик',
|
||||
'student.update': 'Изменён ученик',
|
||||
'student.delete': 'Удалён ученик',
|
||||
'student.batch-group': 'Ученики перенесены в группу',
|
||||
'entry.update': 'Изменена запись',
|
||||
'entry.photo.update': 'Изменено фото записи',
|
||||
'entry.photo.set_main': 'Главное фото записи',
|
||||
'entry.photo.delete': 'Удалено фото записи',
|
||||
'entry.soft-delete': 'Запись в корзину',
|
||||
'entry.restore': 'Запись восстановлена',
|
||||
'entry.permanent-delete': 'Запись удалена безвозвратно',
|
||||
@@ -32,6 +44,7 @@ const ACTION_LABELS = {
|
||||
const AUDIT_PAGE_SIZE = 50;
|
||||
let auditPage = 1;
|
||||
let auditTotal = 0;
|
||||
let auditRows = [];
|
||||
|
||||
function fmtTime(t) {
|
||||
try { return new Date(t).toLocaleString('ru'); } catch { return t || ''; }
|
||||
@@ -48,6 +61,14 @@ function dt(t) {
|
||||
} catch { return esc(String(t).slice(0, 120)); }
|
||||
}
|
||||
|
||||
function fullTarget(t) {
|
||||
if (!t) return '';
|
||||
if (typeof t === 'object') return JSON.stringify(t, null, 2);
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(t), null, 2);
|
||||
} catch { return String(t); }
|
||||
}
|
||||
|
||||
function renderPager(container, page, totalPages, onPageChange) {
|
||||
if (totalPages <= 1) { container.innerHTML = ''; return; }
|
||||
const pages = [];
|
||||
@@ -86,11 +107,12 @@ async function loadAudit() {
|
||||
el.innerHTML = '';
|
||||
} else {
|
||||
document.getElementById('auditEmpty').style.display = 'none';
|
||||
el.innerHTML = filtered.map(r => `
|
||||
auditRows = filtered;
|
||||
el.innerHTML = filtered.map((r, i) => `
|
||||
<tr>
|
||||
<td style="white-space:nowrap">${fmtTime(r.created_at)}</td>
|
||||
<td><b>${esc(ACTION_LABELS[r.action] || r.action)}</b></td>
|
||||
<td style="color:var(--muted)">${dt(r.target)}</td>
|
||||
<td><button type="button" class="audit-details" data-audit-idx="${i}">${dt(r.target)}</button></td>
|
||||
<td style="color:var(--muted);white-space:nowrap">${esc(r.ip || '')}</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
@@ -105,6 +127,22 @@ function goAuditPage(p) {
|
||||
document.getElementById('auditFilter').addEventListener('input', () => { auditPage = 1; loadAudit(); });
|
||||
document.getElementById('auditRefreshBtn').addEventListener('click', loadAudit);
|
||||
|
||||
document.getElementById('auditTable').addEventListener('click', e => {
|
||||
const btn = e.target.closest('.audit-details');
|
||||
if (!btn) return;
|
||||
const r = auditRows[parseInt(btn.dataset.auditIdx, 10)];
|
||||
if (!r) return;
|
||||
document.getElementById('auditDetailsTitle').textContent = ACTION_LABELS[r.action] || r.action;
|
||||
document.getElementById('auditDetailsBody').textContent = fullTarget(r.target);
|
||||
document.getElementById('auditDetailsModal').classList.add('open');
|
||||
});
|
||||
document.getElementById('auditDetailsCloseBtn').addEventListener('click', () => {
|
||||
document.getElementById('auditDetailsModal').classList.remove('open');
|
||||
});
|
||||
document.getElementById('auditDetailsModal').addEventListener('click', e => {
|
||||
if (e.target === e.currentTarget) e.currentTarget.classList.remove('open');
|
||||
});
|
||||
|
||||
(async () => {
|
||||
if (await requireAdminPage()) {
|
||||
buildSidebar(document.body.dataset.page);
|
||||
|
||||
Reference in New Issue
Block a user