feat: add mail source backfill controls

This commit is contained in:
Christian Krakau-Louis
2026-05-22 20:22:28 +02:00
parent b46087a1a1
commit b5d0ecaf85
4 changed files with 74 additions and 6 deletions
+71 -3
View File
@@ -114,6 +114,17 @@
<path d="M21 3v6h-6"></path>
</svg>
</button>
<button class="btn btn-ghost btn-xs" x-on:click="openBackfill(source)" title="Backfill date range"
:disabled="Boolean(fetching[source.id])">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
</button>
<button class="btn btn-ghost btn-xs" x-on:click="loadImportHistory(source)" title="Import history">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
@@ -476,6 +487,36 @@
</div>
</div>
<!-- Backfill modal -->
<div x-show="backfillSource" x-cloak
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
x-on:keydown.escape.window="closeBackfill()">
<div class="bg-base-100 rounded-lg shadow-xl w-full max-w-sm p-6 space-y-4">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">Backfill Mail Source</h2>
<button class="btn btn-ghost btn-sm btn-circle" x-on:click="closeBackfill()"></button>
</div>
<div class="space-y-3">
<p class="text-sm text-muted-foreground" x-text="backfillSource ? backfillSource.name : ''"></p>
<div class="join w-full">
<button type="button" class="btn btn-sm join-item flex-1" x-on:click="backfillDays = 7">7 days</button>
<button type="button" class="btn btn-sm join-item flex-1" x-on:click="backfillDays = 30">30 days</button>
<button type="button" class="btn btn-sm join-item flex-1" x-on:click="backfillDays = 90">90 days</button>
</div>
<label class="form-control">
<span class="label-text">Days to search</span>
<input type="number" class="input input-bordered" min="1" max="365" x-model.number="backfillDays">
</label>
</div>
<div class="flex justify-end gap-2">
<button class="btn btn-ghost btn-sm" x-on:click="closeBackfill()">Cancel</button>
<button class="btn btn-primary btn-sm" x-on:click="runBackfill()" :disabled="!validBackfillDays() || Boolean(fetching[backfillSource && backfillSource.id])">
Run Backfill
</button>
</div>
</div>
</div>
<!-- Delete confirmation modal -->
<div x-show="deleteTarget" x-cloak
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
@@ -508,6 +549,8 @@ function mailSourcesApp() {
isSaving: false,
testing: {},
fetching: {},
backfillSource: null,
backfillDays: 30,
historySource: null,
importHistory: [],
historyLoading: false,
@@ -546,11 +589,12 @@ function mailSourcesApp() {
}
},
async fetchSource(source) {
async fetchSource(source, days = 7) {
this.fetching[source.id] = true;
this.feedback = { message: '', type: '' };
try {
const resp = await fetch(`/api/v1/mail-sources/${source.id}/fetch`, {
const safeDays = Math.min(365, Math.max(1, Number(days) || 7));
const resp = await fetch(`/api/v1/mail-sources/${source.id}/fetch?days=${safeDays}`, {
method: 'POST',
});
const result = await resp.json();
@@ -558,7 +602,7 @@ function mailSourcesApp() {
throw new Error(result.detail || 'Import failed');
}
this.feedback = {
message: `Import finished for ${source.name}: ${result.reports_found} report${result.reports_found === 1 ? '' : 's'}, ${result.duplicate_reports || 0} duplicate${result.duplicate_reports === 1 ? '' : 's'}.`,
message: `Import finished for ${source.name} (${safeDays} day${safeDays === 1 ? '' : 's'}): ${result.reports_found} report${result.reports_found === 1 ? '' : 's'}, ${result.duplicate_reports || 0} duplicate${result.duplicate_reports === 1 ? '' : 's'}.`,
type: result.success ? 'success' : 'error',
};
await this.loadSources();
@@ -573,6 +617,30 @@ function mailSourcesApp() {
}
},
openBackfill(source) {
this.backfillSource = source;
this.backfillDays = 30;
this.feedback = { message: '', type: '' };
},
closeBackfill() {
this.backfillSource = null;
this.backfillDays = 30;
},
validBackfillDays() {
const days = Number(this.backfillDays);
return Number.isInteger(days) && days >= 1 && days <= 365;
},
async runBackfill() {
if (!this.backfillSource || !this.validBackfillDays()) return;
const source = this.backfillSource;
const days = Number(this.backfillDays);
this.closeBackfill();
await this.fetchSource(source, days);
},
async loadImportHistory(source) {
this.historySource = source;
this.importHistory = [];
+1 -1
View File
@@ -29,6 +29,7 @@ Recently improved:
- Mail source import history is visible in the Mail Sources UI.
- Individual mail sources can be manually imported from the Mail Sources UI.
- Import-history rows include sanitized per-attachment outcomes and imported report IDs.
- Mail source backfills can be launched from the UI with configurable search windows.
- The current Alpine-based UI is allowed by CSP and renders dynamic tables in real browsers.
Implementation note:
@@ -39,7 +40,6 @@ Implementation note:
Objective: make mailbox imports auditable and make report totals trustworthy.
Priority tasks:
- Add mailbox search controls for date-range backfills.
- Report duplicate skips separately from parse failures.
- Improve source rollups so a source IP tracks pass/fail counts over time.
+1 -1
View File
@@ -64,10 +64,10 @@ Recently delivered:
- Mail source import history is now visible from the Mail Sources UI.
- A single mail source can be manually imported from the Mail Sources UI, with the result recorded in import history.
- Import history now includes per-attachment details for imported reports, duplicates, parse errors, unsupported attachments, and imported report IDs.
- Mail sources can be backfilled from the UI with 7-day, 30-day, 90-day, or custom search windows.
- The current Alpine-based UI can run under the configured CSP, so dynamic tables render in real browsers.
Next tasks:
- Add mailbox search controls for date range/backfill without requiring code changes.
- Improve source aggregation so each sender IP keeps pass/fail totals instead of only the latest result.
Exit criteria:
+1 -1
View File
@@ -93,7 +93,7 @@ This file tracks the specific implementation tasks for each milestone of the DMA
- [x] Show recent import history in the Mail Sources UI
- [x] Add manual import trigger per mail source
- [x] Add per-import result details for duplicates, parse failures, unsupported attachments, and imported report IDs
- [ ] Add retry/backfill controls per mail source
- [x] Add retry/backfill controls per mail source
## Milestone 3: Database Integration