feat: add mail source backfill controls
This commit is contained in:
@@ -114,6 +114,17 @@
|
|||||||
<path d="M21 3v6h-6"></path>
|
<path d="M21 3v6h-6"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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">
|
<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"
|
<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"
|
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
@@ -476,6 +487,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Delete confirmation modal -->
|
||||||
<div x-show="deleteTarget" x-cloak
|
<div x-show="deleteTarget" x-cloak
|
||||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
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,
|
isSaving: false,
|
||||||
testing: {},
|
testing: {},
|
||||||
fetching: {},
|
fetching: {},
|
||||||
|
backfillSource: null,
|
||||||
|
backfillDays: 30,
|
||||||
historySource: null,
|
historySource: null,
|
||||||
importHistory: [],
|
importHistory: [],
|
||||||
historyLoading: false,
|
historyLoading: false,
|
||||||
@@ -546,11 +589,12 @@ function mailSourcesApp() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchSource(source) {
|
async fetchSource(source, days = 7) {
|
||||||
this.fetching[source.id] = true;
|
this.fetching[source.id] = true;
|
||||||
this.feedback = { message: '', type: '' };
|
this.feedback = { message: '', type: '' };
|
||||||
try {
|
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',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
const result = await resp.json();
|
const result = await resp.json();
|
||||||
@@ -558,7 +602,7 @@ function mailSourcesApp() {
|
|||||||
throw new Error(result.detail || 'Import failed');
|
throw new Error(result.detail || 'Import failed');
|
||||||
}
|
}
|
||||||
this.feedback = {
|
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',
|
type: result.success ? 'success' : 'error',
|
||||||
};
|
};
|
||||||
await this.loadSources();
|
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) {
|
async loadImportHistory(source) {
|
||||||
this.historySource = source;
|
this.historySource = source;
|
||||||
this.importHistory = [];
|
this.importHistory = [];
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Recently improved:
|
|||||||
- Mail source import history is visible in the Mail Sources UI.
|
- Mail source import history is visible in the Mail Sources UI.
|
||||||
- Individual mail sources can be manually imported from 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.
|
- 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.
|
- The current Alpine-based UI is allowed by CSP and renders dynamic tables in real browsers.
|
||||||
|
|
||||||
Implementation note:
|
Implementation note:
|
||||||
@@ -39,7 +40,6 @@ Implementation note:
|
|||||||
Objective: make mailbox imports auditable and make report totals trustworthy.
|
Objective: make mailbox imports auditable and make report totals trustworthy.
|
||||||
|
|
||||||
Priority tasks:
|
Priority tasks:
|
||||||
- Add mailbox search controls for date-range backfills.
|
|
||||||
- Report duplicate skips separately from parse failures.
|
- Report duplicate skips separately from parse failures.
|
||||||
- Improve source rollups so a source IP tracks pass/fail counts over time.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- The current Alpine-based UI can run under the configured CSP, so dynamic tables render in real browsers.
|
||||||
|
|
||||||
Next tasks:
|
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.
|
- Improve source aggregation so each sender IP keeps pass/fail totals instead of only the latest result.
|
||||||
|
|
||||||
Exit criteria:
|
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] Show recent import history in the Mail Sources UI
|
||||||
- [x] Add manual import trigger per mail source
|
- [x] Add manual import trigger per mail source
|
||||||
- [x] Add per-import result details for duplicates, parse failures, unsupported attachments, and imported report IDs
|
- [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
|
## Milestone 3: Database Integration
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user