Extends the pipeline with infosec classification, sender profile tracking, and configurable purge rules. Adds a web dashboard for managing rules and monitoring email processing. Includes new migrations and seed script. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
156 lines
6.3 KiB
HTML
156 lines
6.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Run Purge{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-3xl mx-auto space-y-6">
|
|
|
|
<div>
|
|
<h1 class="text-2xl font-bold">Run Purge</h1>
|
|
<p class="text-sm text-base-content/60 mt-0.5">
|
|
Scan the mailbox and delete matching messages. Always preview first.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="card bg-base-100 shadow-sm">
|
|
<div class="card-body space-y-5">
|
|
|
|
<!-- Source selector -->
|
|
<div>
|
|
<label class="label"><span class="label-text font-medium">Source</span></label>
|
|
<div class="join w-full">
|
|
<button type="button" id="btn-table"
|
|
class="join-item btn btn-sm btn-primary flex-1"
|
|
onclick="setSource('table')">
|
|
Active Purge Rules
|
|
{% if active_rules %}
|
|
<span class="badge badge-sm ml-1">{{ active_rules | length }}</span>
|
|
{% endif %}
|
|
</button>
|
|
<button type="button" id="btn-custom"
|
|
class="join-item btn btn-sm btn-ghost border border-base-300 flex-1"
|
|
onclick="setSource('custom')">
|
|
Custom Address
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active rules summary (shown when source=table) -->
|
|
<div id="table-summary">
|
|
{% if active_rules %}
|
|
<div class="bg-base-200 rounded-box p-3 space-y-1 max-h-48 overflow-y-auto">
|
|
{% for r in active_rules %}
|
|
<div class="flex items-center justify-between text-sm">
|
|
<span class="font-mono">{{ r.email_address }}</span>
|
|
<span class="text-base-content/50 text-xs">delete >{{ r.older_than_days }}d old</span>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-warning py-2 text-sm">
|
|
No active purge rules.
|
|
<a href="/purge" class="link">Add rules →</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Custom address fields (hidden by default) -->
|
|
<div id="custom-fields" class="hidden space-y-3">
|
|
<div class="form-control">
|
|
<label class="label"><span class="label-text">Email Address</span></label>
|
|
<input id="custom-address-input" type="email" placeholder="sender@example.com"
|
|
class="input input-bordered input-sm w-full" />
|
|
</div>
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text">Delete emails older than</span>
|
|
</label>
|
|
<div class="flex items-center gap-2">
|
|
<input id="custom-days-input" type="number" value="30" min="1" max="3650"
|
|
class="input input-bordered input-sm w-24" />
|
|
<span class="text-sm text-base-content/60">days</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action buttons -->
|
|
<div class="flex gap-3 pt-2">
|
|
<button class="btn btn-outline btn-sm flex-1"
|
|
onclick="submitRun('preview')"
|
|
id="btn-preview">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
|
</svg>
|
|
Preview (dry run)
|
|
</button>
|
|
<button class="btn btn-error btn-sm flex-1"
|
|
onclick="submitRun('execute')"
|
|
id="btn-execute">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
Execute Purge
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results area -->
|
|
<div id="runner-results"></div>
|
|
|
|
</div>
|
|
|
|
<!-- Hidden form for htmx submission -->
|
|
<form id="runner-form"
|
|
hx-post="/runner/go"
|
|
hx-target="#runner-results"
|
|
hx-swap="innerHTML"
|
|
hx-indicator="#run-spinner"
|
|
style="display:none">
|
|
<input type="hidden" name="mode" id="f-mode" value="preview" />
|
|
<input type="hidden" name="source" id="f-source" value="table" />
|
|
<input type="hidden" name="custom_address" id="f-address" value="" />
|
|
<input type="hidden" name="custom_days" id="f-days" value="30" />
|
|
</form>
|
|
|
|
<script>
|
|
let currentSource = 'table';
|
|
|
|
function setSource(src) {
|
|
currentSource = src;
|
|
document.getElementById('table-summary').classList.toggle('hidden', src !== 'table');
|
|
document.getElementById('custom-fields').classList.toggle('hidden', src !== 'custom');
|
|
document.getElementById('btn-table').className =
|
|
'join-item btn btn-sm flex-1 ' + (src === 'table' ? 'btn-primary' : 'btn-ghost border border-base-300');
|
|
document.getElementById('btn-custom').className =
|
|
'join-item btn btn-sm flex-1 ' + (src === 'custom' ? 'btn-primary' : 'btn-ghost border border-base-300');
|
|
}
|
|
|
|
function submitRun(mode) {
|
|
if (mode === 'execute') {
|
|
const count = document.querySelectorAll('#runner-results .result-count').length;
|
|
const confirmed = confirm(
|
|
'This will permanently delete emails from the mailbox.\n\nRun a Preview first to see what will be affected.\n\nContinue with execution?'
|
|
);
|
|
if (!confirmed) return;
|
|
}
|
|
|
|
document.getElementById('f-mode').value = mode;
|
|
document.getElementById('f-source').value = currentSource;
|
|
document.getElementById('f-address').value =
|
|
document.getElementById('custom-address-input').value;
|
|
document.getElementById('f-days').value =
|
|
document.getElementById('custom-days-input').value;
|
|
|
|
// Show spinner in results area immediately
|
|
document.getElementById('runner-results').innerHTML =
|
|
'<div class="card bg-base-100 shadow-sm"><div class="card-body text-center py-10">' +
|
|
'<span class="loading loading-spinner loading-md text-primary"></span>' +
|
|
'<p class="text-sm text-base-content/50 mt-2">Scanning mailbox' +
|
|
(mode === 'execute' ? ' and deleting messages' : '') + '…</p></div></div>';
|
|
|
|
htmx.trigger(document.getElementById('runner-form'), 'submit');
|
|
}
|
|
</script>
|
|
{% endblock %}
|