236 lines
12 KiB
HTML
236 lines
12 KiB
HTML
@include(snippets/header)
|
|
<div id="main">
|
|
<div class="container">
|
|
<h1>AUDIT LOG</h1>
|
|
<p>Actions performed by moderators and admins.</p>
|
|
<hr>
|
|
<div class="audit-grid" id="audit-grid">
|
|
@each(logs as entry)
|
|
<div class="audit-card">
|
|
<div class="audit-card-header">
|
|
<div class="audit-card-user">
|
|
<a href="/user/{!! entry.username !!}">{!! entry.username !!}</a>
|
|
</div>
|
|
<div class="audit-card-time">{!! entry.created_at_fmt !!}</div>
|
|
</div>
|
|
<div class="audit-card-body">
|
|
<div class="audit-card-row">
|
|
<span class="audit-label">Action:</span>
|
|
<span class="badge badge-secondary badge-action">{!! entry.action !!}</span>
|
|
</div>
|
|
<div class="audit-card-row">
|
|
<span class="audit-label">Target:</span>
|
|
<span class="audit-value">
|
|
<strong>{!! entry.target_type !!}</strong>
|
|
@if(entry.target_type === 'comment')
|
|
@if(entry.item_id)
|
|
<a href="/{!! entry.item_id !!}#c{!! entry.target_id !!}">#c{!! entry.target_id !!}</a>
|
|
@else
|
|
#c{!! entry.target_id !!}
|
|
@endif
|
|
@else
|
|
@if(entry.target_type === 'item')
|
|
<a href="/{!! entry.target_id !!}">/{!! entry.target_id !!}</a>
|
|
@else
|
|
{!! entry.target_id !!}
|
|
@endif
|
|
@endif
|
|
</span>
|
|
</div>
|
|
|
|
@if(entry.reason)
|
|
<div class="audit-card-row">
|
|
<span class="audit-label">Reason:</span>
|
|
<span class="audit-value">{!! entry.reason !!}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if(entry.uploader_info)
|
|
<div class="audit-card-row">
|
|
<span class="audit-label">Info:</span>
|
|
<span class="audit-value" style="color: #ffb8b8;">{!! entry.uploader_info !!}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if(entry.old_content !== null || entry.new_content !== null || entry.details_json)
|
|
<div class="audit-card-row" style="margin-top: 5px; flex-direction: column; align-items: stretch;">
|
|
<span class="audit-label">Changes:</span>
|
|
<div class="audit-diff">
|
|
@if(entry.old_content)
|
|
<div class="diff-removed">- {!! entry.old_content !!}</div>
|
|
@endif
|
|
@if(entry.new_content)
|
|
<div class="diff-added">+ {!! entry.new_content !!}</div>
|
|
@endif
|
|
@if(entry.details_json)
|
|
<div class="audit-details-json"
|
|
style="font-size: 0.85em; color: #aaa; margin-top: 5px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 5px;">
|
|
{!! entry.details_json !!}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endeach
|
|
</div>
|
|
|
|
<div id="audit-loading" style="text-align: center; padding: 20px; display: none;">
|
|
<span class="loading-spinner">Loading more logs...</span>
|
|
</div>
|
|
|
|
<br>
|
|
@if(typeof pages !== 'undefined' && pages > 1)
|
|
<div class="pagination-container" id="audit-pagination"
|
|
style="display: flex; gap: 10px; align-items: center; justify-content: center;">
|
|
@if(page > 1)
|
|
<a href="/mod/audit?page={!! page - 1 !!}" class="badge badge-secondary">« Prev</a>
|
|
@endif
|
|
<span>Page <span id="current-page-display">{!! page !!}</span> of {!! pages !!}</span>
|
|
@if(page < pages) <a href="/mod/audit?page={!! page + 1 !!}" id="next-page-link" class="badge badge-secondary">Next »</a>@endif
|
|
</div>
|
|
<br>
|
|
@endif
|
|
<script>
|
|
(function () {
|
|
var grid = document.getElementById('audit-grid');
|
|
var loader = document.getElementById('audit-loading');
|
|
var pagination = document.getElementById('audit-pagination');
|
|
|
|
var escapeHtml = function (unsafe) {
|
|
return (unsafe || '')
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """)
|
|
.replace(/'/g, "'");
|
|
};
|
|
|
|
var renderDiff = function (oldText, newText) {
|
|
var isEmpty = function (t) { return !t || t === 'null' || t === 'undefined'; };
|
|
if (isEmpty(oldText) && isEmpty(newText)) return '';
|
|
|
|
var html = '<div class="audit-diff">';
|
|
if (!isEmpty(oldText)) html += '<div class="diff-removed">- ' + escapeHtml(oldText) + '</div>';
|
|
if (!isEmpty(newText)) html += '<div class="diff-added">+ ' + escapeHtml(newText) + '</div>';
|
|
html += '</div>';
|
|
return html;
|
|
};
|
|
|
|
var currentPage = Number('{{ page }}') || 1;
|
|
var totalPages = Number('{{ pages }}') || 1;
|
|
var loading = false;
|
|
var hasMore = currentPage < totalPages;
|
|
|
|
if (pagination) pagination.style.display = 'none';
|
|
|
|
window.addEventListener('scroll', function () {
|
|
if (loading || !hasMore) return;
|
|
|
|
var scrollPosition = window.innerHeight + window.scrollY;
|
|
var threshold = document.documentElement.scrollHeight - 500;
|
|
|
|
if (scrollPosition > threshold) {
|
|
loadMore();
|
|
}
|
|
});
|
|
|
|
async function loadMore() {
|
|
if (loading || !hasMore) return;
|
|
loading = true;
|
|
if (loader) loader.style.display = 'block';
|
|
|
|
try {
|
|
var next = currentPage + 1;
|
|
var res = await fetch('/mod/audit?page=' + next, {
|
|
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
});
|
|
var data = await res.json();
|
|
|
|
if (data.success && data.logs && data.logs.length) {
|
|
data.logs.forEach(function (log) {
|
|
var card = document.createElement('div');
|
|
card.className = 'audit-card';
|
|
|
|
var reason = log.reason;
|
|
|
|
var html = '<div class="audit-card-header">' +
|
|
'<div class="audit-card-user">' +
|
|
'<a href="/user/' + encodeURIComponent(log.username) + '">' + log.username + '</a>' +
|
|
'</div>' +
|
|
'<div class="audit-card-time">' + (log.created_at || '') + '</div>' +
|
|
'</div>' +
|
|
'<div class="audit-card-body">' +
|
|
'<div class="audit-card-row">' +
|
|
'<span class="audit-label">Action:</span>' +
|
|
'<span class="badge badge-secondary badge-action">' + (log.action || '') + '</span>' +
|
|
'</div>' +
|
|
'<div class="audit-card-row">' +
|
|
'<span class="audit-label">Target:</span>' +
|
|
'<span class="audit-value">' +
|
|
'<strong>' + (log.target_type || '') + '</strong> ';
|
|
|
|
if (log.target_type === 'comment') {
|
|
if (log.item_id) {
|
|
html += '<a href="/' + log.item_id + '#c' + log.target_id + '">#c' + log.target_id + '</a>';
|
|
} else {
|
|
html += '#c' + (log.target_id || '');
|
|
}
|
|
} else if (log.target_type === 'item') {
|
|
html += '<a href="/' + log.target_id + '">/' + log.target_id + '</a>';
|
|
} else {
|
|
html += (log.target_id || '');
|
|
}
|
|
html += '</span></div>';
|
|
|
|
if (reason) {
|
|
html += '<div class="audit-card-row">' +
|
|
'<span class="audit-label">Reason:</span>' +
|
|
'<span class="audit-value">' + escapeHtml(reason) + '</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
if (log.uploader_info) {
|
|
html += '<div class="audit-card-row">' +
|
|
'<span class="audit-label">Info:</span>' +
|
|
'<span class="audit-value" style="color: #ffb8b8;">' + escapeHtml(log.uploader_info) + '</span>' +
|
|
'</div>';
|
|
}
|
|
|
|
if (log.old_content || log.new_content || log.details_json) {
|
|
html += '<div class="audit-card-row" style="margin-top: 5px; flex-direction: column; align-items: stretch;">' +
|
|
'<span class="audit-label">Changes:</span>' +
|
|
'<div class="audit-diff">' +
|
|
(log.old_content ? '<div class="diff-removed">- ' + escapeHtml(log.old_content) + '</div>' : '') +
|
|
(log.new_content ? '<div class="diff-added">+ ' + escapeHtml(log.new_content) + '</div>' : '') +
|
|
(log.details_json ? '<div class="audit-details-json" style="font-size: 0.85em; color: #aaa; margin-top: 5px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 5px;">' + escapeHtml(log.details_json) + '</div>' : '') +
|
|
'</div>' +
|
|
'</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
card.innerHTML = html;
|
|
grid.appendChild(card);
|
|
});
|
|
currentPage = data.page;
|
|
hasMore = data.hasMore;
|
|
if (document.getElementById('current-page-display')) {
|
|
document.getElementById('current-page-display').innerText = currentPage;
|
|
}
|
|
} else {
|
|
hasMore = false;
|
|
}
|
|
} catch (err) {
|
|
console.error('Audit infinite scroll error:', err);
|
|
hasMore = false;
|
|
} finally {
|
|
loading = false;
|
|
if (loader) loader.style.display = 'none';
|
|
}
|
|
}
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
@include(snippets/footer) |