init f0ckm
This commit is contained in:
244
views/admin/approve.html
Normal file
244
views/admin/approve.html
Normal file
@@ -0,0 +1,244 @@
|
||||
@include(snippets/header)
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<h1>APPROVAL QUEUE</h1>
|
||||
<p>Items here are pending approval.</p>
|
||||
|
||||
@if(pending.length > 0)
|
||||
<h2>Pending Uploads</h2>
|
||||
<table class="table" style="width: 100%; margin-bottom: 30px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Preview</td>
|
||||
<td>ID</td>
|
||||
<td>Uploader</td>
|
||||
<td>Type</td>
|
||||
<td>Tags</td>
|
||||
<td>Action</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(pending as post)
|
||||
<tr>
|
||||
<td>
|
||||
<video controls loop muted preload="metadata" style="max-height: 200px; max-width: 300px;">
|
||||
<source src="/b/{{ post.dest }}" type="{{ post.mime }}">
|
||||
</video>
|
||||
</td>
|
||||
<td>{{ post.id }}</td>
|
||||
<td>{!! post.username !!}</td>
|
||||
<td>{{ post.mime }}</td>
|
||||
<td>
|
||||
@each(post.tags as tag)
|
||||
<span class="badge badge-secondary" style="margin-right: 5px;">{!! tag !!}</span>
|
||||
@endeach
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/approve/?id={{ post.id }}" class="badge badge-success btn-approve-async">Approve</a>
|
||||
<a href="/admin/deny/?id={{ post.id }}" class="badge badge-danger btn-deny-async">Deny / Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 40px;">
|
||||
<h2 style="color: #ff6b6b; margin: 0;">Soft Deleted</h2>
|
||||
@if(trash.length > 0)
|
||||
<button id="btn-purge-trash" class="badge badge-danger" style="border: none; padding: 10px 15px; cursor: pointer; font-size: 14px;">Purge All Soft-Deleted</button>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-muted">These items are in the deleted folder but not purged from DB. Approving them will restore them.</p>
|
||||
|
||||
@if(trash.length > 0)
|
||||
<table class="table" style="width: 100%; opacity: 0.8;">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Preview</td>
|
||||
<td>ID</td>
|
||||
<td>Uploader</td>
|
||||
<td>Type</td>
|
||||
<td>Tags</td>
|
||||
<td>Action</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@each(trash as post)
|
||||
<tr>
|
||||
<td>
|
||||
@if(post.thumbnail)
|
||||
<img src="data:image/webp;base64,{{ post.thumbnail }}" style="max-height: 150px; opacity: 0.6;">
|
||||
@else
|
||||
<span style="color:red;">[File Missing]</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ post.id }}</td>
|
||||
<td>{!! post.username !!}</td>
|
||||
<td>{{ post.mime }}</td>
|
||||
<td>
|
||||
@each(post.tags as tag)
|
||||
<span class="badge badge-secondary" style="margin-right: 5px;">{!! tag !!}</span>
|
||||
@endeach
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/approve/?id={{ post.id }}" class="badge badge-warning btn-approve-async">Restore</a>
|
||||
<a href="/admin/deny/?id={{ post.id }}" class="badge badge-danger btn-deny-async">Purge</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p style="padding: 20px; border: 1px dashed #444; color: #888;">Trash is empty.</p>
|
||||
@endif
|
||||
|
||||
@if(pending.length === 0 && trash.length === 0)
|
||||
<div style="text-align: center; padding: 50px;">
|
||||
<h3>No pending items.</h3>
|
||||
<p>Go touch grass?</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<br>
|
||||
@if(typeof pages !== 'undefined' && pages > 1)
|
||||
<div class="pagination" style="display: flex; gap: 10px; align-items: center; justify-content: center;">
|
||||
@if(page > 1)
|
||||
<a href="/admin/approve?page={{ page - 1 }}" class="badge badge-secondary">« Prev</a>
|
||||
@endif
|
||||
<span>Page {{ page }} of {{ pages }}</span>
|
||||
@if(page < pages) <a href="/admin/approve?page={{ page + 1 }}" class="badge badge-secondary">Next »</a>
|
||||
@endif
|
||||
</div>
|
||||
<br>
|
||||
@endif
|
||||
|
||||
|
||||
|
||||
<!-- Custom Modal -->
|
||||
<div id="custom-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); justify-content: center; align-items: center; z-index: 1000;">
|
||||
<div style="background: #222; color: #fff; padding: 20px; border-radius: 8px; max-width: 400px; text-align: center; border: 1px solid #444;">
|
||||
<h3 id="modal-title" style="margin-top: 0;">Confirm Action</h3>
|
||||
<p id="modal-text">Are you sure?</p>
|
||||
<div style="display: flex; justify-content: space-around; margin-top: 20px;">
|
||||
<button id="modal-cancel" class="badge badge-secondary" style="border: none; padding: 10px 20px; cursor: pointer;">Cancel</button>
|
||||
<button id="modal-confirm" class="badge badge-danger" style="border: none; padding: 10px 20px; cursor: pointer;">Confirm</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
const modal = document.getElementById('custom-modal');
|
||||
const modalTitle = document.getElementById('modal-title');
|
||||
const modalText = document.getElementById('modal-text');
|
||||
const btnConfirm = document.getElementById('modal-confirm');
|
||||
const btnCancel = document.getElementById('modal-cancel');
|
||||
|
||||
let pendingAction = null;
|
||||
|
||||
const showModal = (title, text, action) => {
|
||||
modalTitle.innerText = title;
|
||||
modalText.innerText = text;
|
||||
pendingAction = action;
|
||||
modal.style.display = 'flex';
|
||||
|
||||
btnConfirm.onclick = async () => {
|
||||
if (!pendingAction) return;
|
||||
btnConfirm.disabled = true;
|
||||
btnConfirm.innerText = 'Processing...';
|
||||
try {
|
||||
await pendingAction();
|
||||
closeModal();
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
} finally {
|
||||
btnConfirm.disabled = false;
|
||||
btnConfirm.innerText = 'Confirm';
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
modal.style.display = 'none';
|
||||
pendingAction = null;
|
||||
};
|
||||
|
||||
if (btnCancel) btnCancel.onclick = closeModal;
|
||||
|
||||
// Single Deny
|
||||
document.querySelectorAll('.btn-deny-async').forEach(btn => {
|
||||
btn.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
const url = btn.getAttribute('href');
|
||||
const row = btn.closest('tr');
|
||||
|
||||
showModal('Deny Item', 'Permanently delete this item?', async () => {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.success) {
|
||||
if (window.flashMessage) window.flashMessage('{!! t('toast.item_deleted_success') !!}', 2000, 'success');
|
||||
row.style.opacity = '0';
|
||||
setTimeout(() => row.remove(), 300);
|
||||
} else {
|
||||
if (window.flashMessage) window.flashMessage(data.msg || '{!! t('toast.report_error') !!}', 3000, 'error');
|
||||
throw new Error(data.msg || 'Request failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Single Approve / Restore
|
||||
document.querySelectorAll('.btn-approve-async').forEach(btn => {
|
||||
btn.addEventListener('click', async e => {
|
||||
e.preventDefault();
|
||||
const url = btn.getAttribute('href');
|
||||
const row = btn.closest('tr');
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.flashMessage) window.flashMessage('{!! t('toast.approve_success') !!}', 2000, 'success');
|
||||
row.style.opacity = '0';
|
||||
setTimeout(() => row.remove(), 300);
|
||||
} else {
|
||||
if (window.flashMessage) window.flashMessage(data.msg || '{!! t('toast.approve_error') !!}', 3000, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (window.flashMessage) window.flashMessage('{!! t('toast.network_error') !!}', 3000, 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Purge All Trash
|
||||
const btnPurgeTrash = document.getElementById('btn-purge-trash');
|
||||
if (btnPurgeTrash) {
|
||||
btnPurgeTrash.addEventListener('click', () => {
|
||||
showModal('Purge Trash', 'Permanently delete ALL items in the trash? This cannot be undone.', async () => {
|
||||
const res = await fetch('/admin/purge-trash-all', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
throw new Error(data.msg || 'Purge failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
Reference in New Issue
Block a user