feat(downloads): Logs button on failing-sources rollup rows
CI / lint (push) Successful in 3s
CI / backend-lint-and-test (push) Successful in 16s
CI / frontend-build (push) Successful in 18s
CI / intimp (push) Successful in 3m41s
CI / intapi (push) Successful in 7m43s
CI / intcore (push) Successful in 8m10s

Operator-asked 2026-06-01: each row in the "X sources are failing"
rollup needs a button to surface the last run's stdout/stderr/error
without leaving the Downloads tab to find the matching event row.

Wired:
- `downloadsStore.loadLastForSource(sourceId)` two-steps via
  `GET /api/downloads?source_id=N&limit=1` (most recent event) then
  the existing `loadOne(id)` for the full detail payload. Returns
  null if no events exist (toast warns).
- `FailingSourcesCard` row: new text button `Logs` with
  `mdi-text-box-search-outline`, per-row spinner via `logLoadingIds`,
  emits `view-logs` with the source record.
- `DownloadsTab.onViewFailingLogs` is the handler — same
  `DownloadDetailModal` instance the event-row clicks use.
This commit is contained in:
2026-06-01 10:04:22 -04:00
parent fb605af959
commit 94e7d20792
3 changed files with 58 additions and 2 deletions
+17 -1
View File
@@ -55,6 +55,21 @@ export const useDownloadsStore = defineStore('downloads', () => {
return selected.value
}
// Open the detail modal for the most recent DownloadEvent of a given
// source. Used by the failing-sources rollup's "Logs" button so the
// operator can troubleshoot without leaving the Downloads tab to find
// the row (operator-flagged 2026-06-01).
async function loadLastForSource(sourceId) {
const events = await api.get('/api/downloads', {
params: { source_id: sourceId, limit: 1 },
})
if (!events.length) {
selected.value = null
return null
}
return await loadOne(events[0].id)
}
async function applyFilter(patch) {
filter.value = { ...filter.value, ...patch }
await loadFirst()
@@ -98,7 +113,8 @@ export const useDownloadsStore = defineStore('downloads', () => {
return {
events, cursor, hasMore, filter, selected, loading, error, stats,
activity, failing, activeEvents,
loadFirst, loadMore, loadOne, applyFilter, closeDetail, loadStats,
loadFirst, loadMore, loadOne, loadLastForSource, applyFilter,
closeDetail, loadStats,
loadActivity, loadFailing, loadActive, recoverStalled,
}
})