188 lines
8.1 KiB
HTML
188 lines
8.1 KiB
HTML
{% extends 'base.html' %}
|
|
|
|
{% block css %}
|
|
{{ block.super }}
|
|
<style>
|
|
.media-cleanup-loading.htmx-indicator {
|
|
display: none;
|
|
}
|
|
.media-cleanup-form.htmx-request .media-cleanup-loading.htmx-indicator {
|
|
display: flex;
|
|
}
|
|
.media-cleanup-form.htmx-request button,
|
|
.media-cleanup-form.htmx-request input,
|
|
.media-cleanup-form.htmx-request textarea {
|
|
pointer-events: none;
|
|
opacity: 0.7;
|
|
}
|
|
.media-cleanup-modal-spinner {
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
|
|
<div>
|
|
<h2 class="mb-1">Unused Media Cleanup</h2>
|
|
<p class="text-muted mb-0">
|
|
Run django-unused-media from the web UI to preview or remove orphaned files.
|
|
</p>
|
|
</div>
|
|
<a href="{% url 'logs_view' %}" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-journal-text me-1"></i> View logs
|
|
</a>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm mb-4">
|
|
<div class="card-body">
|
|
<form
|
|
id="media-cleanup-form"
|
|
method="post"
|
|
hx-post="{% url 'media_cleanup' %}"
|
|
hx-target="#media-cleanup-result"
|
|
hx-swap="innerHTML"
|
|
hx-indicator="#media-cleanup-loading"
|
|
class="row g-3 media-cleanup-form"
|
|
>
|
|
{% csrf_token %}
|
|
|
|
<div class="col-12 col-lg-4">
|
|
<label for="id_minimum_file_age" class="form-label">Minimum file age (seconds)</label>
|
|
<input
|
|
id="id_minimum_file_age"
|
|
type="number"
|
|
min="0"
|
|
step="1"
|
|
name="minimum_file_age"
|
|
class="form-control"
|
|
value="{{ minimum_file_age|default:60 }}"
|
|
>
|
|
<div class="form-text">Skip very recent files to avoid race conditions with active uploads.</div>
|
|
</div>
|
|
|
|
<div class="col-12 col-lg-8">
|
|
<label for="id_exclude" class="form-label">Exclude masks (one per line)</label>
|
|
<textarea
|
|
id="id_exclude"
|
|
name="exclude"
|
|
rows="4"
|
|
class="form-control"
|
|
placeholder="example: thumbnails/*"
|
|
>{{ exclude }}</textarea>
|
|
<div class="form-text">Uses django-unused-media wildcard matching.</div>
|
|
</div>
|
|
|
|
<div class="col-12">
|
|
<div class="form-check">
|
|
<input
|
|
id="id_remove_empty_dirs"
|
|
type="checkbox"
|
|
class="form-check-input"
|
|
name="remove_empty_dirs"
|
|
{% if remove_empty_dirs %}checked{% endif %}
|
|
>
|
|
<label for="id_remove_empty_dirs" class="form-check-label">
|
|
Remove empty directories after cleanup
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-12 d-flex flex-wrap gap-2">
|
|
<button
|
|
type="submit"
|
|
name="action"
|
|
value="preview"
|
|
class="btn btn-primary"
|
|
data-loading-title="Previewing unused files"
|
|
data-loading-message="Scanning media storage for orphaned files."
|
|
>
|
|
<i class="bi bi-search me-1"></i> Preview unused files
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
name="action"
|
|
value="cleanup"
|
|
class="btn btn-danger"
|
|
hx-confirm="Delete unused media files now? This cannot be undone."
|
|
data-loading-title="Deleting unused files"
|
|
data-loading-message="Removing orphaned media and recalculating disk usage."
|
|
>
|
|
<i class="bi bi-trash me-1"></i> Delete unused files
|
|
</button>
|
|
</div>
|
|
|
|
<div id="media-cleanup-loading" class="col-12 media-cleanup-loading htmx-indicator align-items-center gap-2 text-info">
|
|
<div class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></div>
|
|
<span>Cleanup in progress. This can take a while for large media directories.</span>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="media-cleanup-result">
|
|
{% include 'rad/partials/_media_cleanup_result.html' %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="media-cleanup-loading-modal" tabindex="-1" aria-labelledby="media-cleanup-loading-title" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
|
<div class="modal-content border-0 shadow">
|
|
<div class="modal-body text-center py-4">
|
|
<div class="spinner-border text-info media-cleanup-modal-spinner mb-3" role="status" aria-hidden="true"></div>
|
|
<h5 id="media-cleanup-loading-title" class="mb-1">Working...</h5>
|
|
<p id="media-cleanup-loading-message" class="text-muted mb-0 small">Please wait while this operation completes.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var form = document.getElementById('media-cleanup-form');
|
|
var modalEl = document.getElementById('media-cleanup-loading-modal');
|
|
if (!form || !modalEl || typeof bootstrap === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
var titleEl = document.getElementById('media-cleanup-loading-title');
|
|
var messageEl = document.getElementById('media-cleanup-loading-message');
|
|
var loadingModal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
var activeButton = null;
|
|
|
|
form.addEventListener('click', function (event) {
|
|
var btn = event.target.closest('button[type="submit"]');
|
|
if (!btn || !form.contains(btn)) {
|
|
return;
|
|
}
|
|
activeButton = btn;
|
|
});
|
|
|
|
document.body.addEventListener('htmx:beforeRequest', function (event) {
|
|
if (event.target !== form) {
|
|
return;
|
|
}
|
|
var title = activeButton ? activeButton.getAttribute('data-loading-title') : null;
|
|
var message = activeButton ? activeButton.getAttribute('data-loading-message') : null;
|
|
titleEl.textContent = title || 'Working...';
|
|
messageEl.textContent = message || 'Please wait while this operation completes.';
|
|
loadingModal.show();
|
|
});
|
|
|
|
function hideLoadingModal(event) {
|
|
if (event.target !== form) {
|
|
return;
|
|
}
|
|
loadingModal.hide();
|
|
activeButton = null;
|
|
}
|
|
|
|
document.body.addEventListener('htmx:afterRequest', hideLoadingModal);
|
|
document.body.addEventListener('htmx:responseError', hideLoadingModal);
|
|
document.body.addEventListener('htmx:sendError', hideLoadingModal);
|
|
})();
|
|
</script>
|
|
{% endblock %}
|