feat(downloads): Logs button on failing-sources rollup rows
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:
@@ -58,6 +58,7 @@
|
||||
:retrying-all="retryingAll"
|
||||
@retry="onRetrySource"
|
||||
@retry-all="onRetryAll"
|
||||
@view-logs="onViewFailingLogs"
|
||||
/>
|
||||
|
||||
<div v-if="store.loading && store.events.length === 0" class="fc-dl__loading">
|
||||
@@ -364,6 +365,23 @@ watch(filterModel, async (m) => {
|
||||
async function openDetail(id) {
|
||||
await store.loadOne(id)
|
||||
}
|
||||
|
||||
async function onViewFailingLogs(source) {
|
||||
// Find and open the most recent DownloadEvent for this source.
|
||||
// Reuses the existing DownloadDetailModal — same stdout/stderr/error
|
||||
// surface the row-click in the events feed shows.
|
||||
try {
|
||||
const ev = await store.loadLastForSource(source.id)
|
||||
if (!ev) {
|
||||
toast({
|
||||
text: `No download events recorded for ${source.artist_name || source.platform} yet.`,
|
||||
type: 'warning',
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
toast({ text: `Failed to load logs: ${e.message}`, type: 'error' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
{{ s.last_error || 'no error message recorded' }}
|
||||
</span>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
size="x-small" variant="text" prepend-icon="mdi-text-box-search-outline"
|
||||
:loading="logLoadingIds.has(s.id)"
|
||||
@click="onViewLogs(s)"
|
||||
title="Show the most recent download event's stdout/stderr/error"
|
||||
>
|
||||
Logs
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="x-small" variant="text" prepend-icon="mdi-refresh"
|
||||
:loading="retryingIds.has(s.id)"
|
||||
@@ -52,9 +60,23 @@ defineProps({
|
||||
retryingIds: { type: Set, default: () => new Set() },
|
||||
retryingAll: { type: Boolean, default: false },
|
||||
})
|
||||
defineEmits(['retry', 'retry-all'])
|
||||
const emit = defineEmits(['retry', 'retry-all', 'view-logs'])
|
||||
|
||||
const open = ref(true)
|
||||
// Per-row loading flag so the spinner lives on the row whose Logs
|
||||
// button was clicked, not on every row.
|
||||
const logLoadingIds = ref(new Set())
|
||||
async function onViewLogs(s) {
|
||||
if (logLoadingIds.value.has(s.id)) return
|
||||
logLoadingIds.value = new Set(logLoadingIds.value).add(s.id)
|
||||
try {
|
||||
await emit('view-logs', s)
|
||||
} finally {
|
||||
const next = new Set(logLoadingIds.value)
|
||||
next.delete(s.id)
|
||||
logLoadingIds.value = next
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user