init f0ckm

This commit is contained in:
2026-04-25 19:51:52 +02:00
commit b646107eb7
241 changed files with 70364 additions and 0 deletions

236
views/mod/audit.html Normal file
View File

@@ -0,0 +1,236 @@
@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">&laquo; 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 &raquo;</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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
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)