Mar 04 2026: added purge old logs button

This commit is contained in:
2026-03-04 11:26:08 -05:00
parent 345d14fcfd
commit 795c4bb4c7
2 changed files with 121 additions and 4 deletions
+60 -2
View File
@@ -1,9 +1,11 @@
import logging
from flask import Blueprint, render_template, request
from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required
from app.models.audit import AuditLog
from app.models.user import User
from app.utils.decorators import admin_required
from app.utils.audit import log_action, ACTION_DELETE
bp = Blueprint('audit', __name__, url_prefix='/audit')
@@ -88,5 +90,61 @@ def view(log_id):
return render_template('audit/view.html', entry=entry)
# ── Purge old logs ────────────────────────────────────────────────────────────
PURGE_OPTIONS = {
7: '7 days',
30: '30 days',
60: '60 days',
90: '90 days',
180: '180 days',
365: '1 year',
}
@bp.route('/purge', methods=['POST'])
@login_required
@admin_required
def purge():
"""Delete audit log entries older than the selected threshold.
Accepts a POST form field `older_than` (integer days).
The purge itself is recorded as a new audit log entry so there is
always a traceable record of who purged what and when.
"""
try:
older_than = int(request.form.get('older_than', 0))
except (ValueError, TypeError):
older_than = 0
if older_than not in PURGE_OPTIONS:
flash('Invalid purge threshold selected.', 'danger')
return redirect(url_for('audit.index'))
cutoff = datetime.utcnow() - timedelta(days=older_than)
deleted = AuditLog.query.filter(AuditLog.created_at < cutoff).delete()
db.session.flush()
label = PURGE_OPTIONS[older_than]
log_action(
ACTION_DELETE, 'AuditLog', None,
f'Purged {deleted} log entries older than {label}',
f'cutoff={cutoff.strftime("%Y-%m-%d %H:%M:%S")} UTC; deleted={deleted}',
)
db.session.commit()
logger.info(
'AUDIT PURGE | deleted=%s | older_than=%s days | by=%s',
deleted, older_than, request.remote_addr,
)
flash(
f'{deleted} audit log entr{"ies" if deleted != 1 else "y"} '
f'older than {label} have been permanently deleted.',
'success' if deleted else 'info',
)
return redirect(url_for('audit.index'))
# Avoid circular import — imported after function definitions
from app import db # noqa: E402
from app import db # noqa: E402
+61 -2
View File
@@ -75,7 +75,13 @@
<span class="badge bg-info ms-1">Filtered</span>
{% endif %}
</span>
<small class="text-muted">Page {{ logs.page }} of {{ logs.pages }}</small>
<div class="d-flex align-items-center gap-2">
<small class="text-muted">Page {{ logs.page }} of {{ logs.pages }}</small>
<button type="button" class="btn btn-sm btn-outline-danger"
data-bs-toggle="modal" data-bs-target="#purgeModal">
<i class="bi bi-trash me-1"></i>Purge Old Logs
</button>
</div>
</div>
<div class="card-body p-0">
{% if logs.items %}
@@ -192,4 +198,57 @@
</div>
{% endif %}
</div>
{% endblock %}
<!-- ── Purge Modal ──────────────────────────────────────────────────────────── -->
<div class="modal fade" id="purgeModal" tabindex="-1" aria-labelledby="purgeModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="purgeModalLabel">
<i class="bi bi-trash me-2"></i>Purge Old Audit Logs
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" action="{{ url_for('audit.purge') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="modal-body">
<div class="alert alert-warning d-flex align-items-center gap-2 mb-3">
<i class="bi bi-exclamation-triangle-fill fs-5 flex-shrink-0"></i>
<span>This action is <strong>permanent and irreversible.</strong>
Deleted log entries cannot be recovered.</span>
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Delete logs older than</label>
<select name="older_than" class="form-select" id="purgeOlderThan" required>
<option value="">— Select a threshold —</option>
<option value="7">7 days</option>
<option value="30">30 days</option>
<option value="60">60 days</option>
<option value="90">90 days</option>
<option value="180">180 days</option>
<option value="365">1 year</option>
</select>
</div>
<p class="text-muted small mb-0">
All audit log entries created before the selected threshold will be
permanently deleted. A single audit entry recording this purge will
be retained.
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger" id="purgeSubmitBtn" disabled>
<i class="bi bi-trash me-1"></i>Purge Logs
</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.getElementById('purgeOlderThan').addEventListener('change', function () {
document.getElementById('purgeSubmitBtn').disabled = !this.value;
});
</script>
{% endblock %}