feat: add mail source backfill controls
This commit is contained in:
@@ -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 = [];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user