feat(downloads): enrich modal with run stats, errors panel, copy, and log filter
Builds on 513b191. Consumes the new metadata fields in the Downloads
Details modal:
• Run stats rows — exit code, downloaded/skipped/per-item-failures/
warning counts, duration. Zero-value rows hide to reduce noise.
Files Downloaded row prefers run_stats.downloaded_count with
file_count fallback for pre-migration rows.
• Errors & Warnings panel — new panel at the top, populated from
metadata.stderr_errors_warnings and open-by-default when present.
• Copy-to-clipboard buttons — small icon button on each log panel
title, uses navigator.clipboard with snackbar confirmation.
• Verbose log filter — substring filter on the stderr panel.
Graceful for existing rows: every new field is v-if-gated so sessions
saved before the backend change still render correctly with their
old-shape metadata.
This commit is contained in:
@@ -301,7 +301,29 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold">Files Downloaded</td>
|
||||
<td>{{ selectedDownload.file_count }}</td>
|
||||
<td>{{ runStats?.downloaded_count ?? selectedDownload.file_count }}</td>
|
||||
</tr>
|
||||
<tr v-if="runStats">
|
||||
<td class="font-weight-bold">Skipped</td>
|
||||
<td>{{ runStats.skipped_count }} <span class="text-grey text-caption">(already in archive)</span></td>
|
||||
</tr>
|
||||
<tr v-if="runStats && runStats.per_item_failures > 0">
|
||||
<td class="font-weight-bold">Per-item Failures</td>
|
||||
<td>{{ runStats.per_item_failures }} <span class="text-grey text-caption">(single items gallery-dl skipped over)</span></td>
|
||||
</tr>
|
||||
<tr v-if="runStats && runStats.warning_count > 0">
|
||||
<td class="font-weight-bold">Warnings</td>
|
||||
<td>{{ runStats.warning_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold">Duration</td>
|
||||
<td>{{ formatDuration(selectedDownload) }}</td>
|
||||
</tr>
|
||||
<tr v-if="runStats">
|
||||
<td class="font-weight-bold">Exit Code</td>
|
||||
<td>
|
||||
<code :class="runStats.exit_code === 0 ? 'text-success' : 'text-warning'">{{ runStats.exit_code }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-weight-bold">Created</td>
|
||||
@@ -326,17 +348,69 @@
|
||||
</tbody>
|
||||
</v-table>
|
||||
|
||||
<v-expansion-panels class="mt-4" v-if="selectedDownload.metadata">
|
||||
<v-expansion-panels class="mt-4" v-model="openLogPanels" multiple v-if="selectedDownload.metadata">
|
||||
<v-expansion-panel v-if="selectedDownload.metadata.stderr_errors_warnings">
|
||||
<v-expansion-panel-title>
|
||||
Errors & Warnings
|
||||
<v-btn
|
||||
icon="mdi-content-copy"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
class="ml-2"
|
||||
@click.stop="copyToClipboard(selectedDownload.metadata.stderr_errors_warnings, 'errors & warnings')"
|
||||
>
|
||||
<v-icon size="small">mdi-content-copy</v-icon>
|
||||
<v-tooltip activator="parent">Copy</v-tooltip>
|
||||
</v-btn>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<pre class="text-caption text-error" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ selectedDownload.metadata.stderr_errors_warnings }}</pre>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
<v-expansion-panel v-if="selectedDownload.metadata.stdout">
|
||||
<v-expansion-panel-title>Output Log</v-expansion-panel-title>
|
||||
<v-expansion-panel-title>
|
||||
Output Log (stdout)
|
||||
<v-btn
|
||||
icon="mdi-content-copy"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
class="ml-2"
|
||||
@click.stop="copyToClipboard(selectedDownload.metadata.stdout, 'stdout')"
|
||||
>
|
||||
<v-icon size="small">mdi-content-copy</v-icon>
|
||||
<v-tooltip activator="parent">Copy</v-tooltip>
|
||||
</v-btn>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<pre class="text-caption" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ selectedDownload.metadata.stdout }}</pre>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
<v-expansion-panel v-if="selectedDownload.metadata.stderr">
|
||||
<v-expansion-panel-title>Verbose Log (stderr)</v-expansion-panel-title>
|
||||
<v-expansion-panel-title>
|
||||
Verbose Log (stderr)
|
||||
<v-btn
|
||||
icon="mdi-content-copy"
|
||||
size="x-small"
|
||||
variant="text"
|
||||
class="ml-2"
|
||||
@click.stop="copyToClipboard(selectedDownload.metadata.stderr, 'verbose log')"
|
||||
>
|
||||
<v-icon size="small">mdi-content-copy</v-icon>
|
||||
<v-tooltip activator="parent">Copy</v-tooltip>
|
||||
</v-btn>
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<pre class="text-caption" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ selectedDownload.metadata.stderr }}</pre>
|
||||
<v-text-field
|
||||
v-model="verboseLogFilter"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
placeholder="Filter lines (substring match)"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
class="mb-2"
|
||||
/>
|
||||
<pre class="text-caption" style="white-space: pre-wrap; max-height: 300px; overflow: auto">{{ filteredVerboseLog || '(no matching lines)' }}</pre>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
@@ -379,6 +453,8 @@ const filterExcludeSuperseded = ref(true)
|
||||
const detailsDialog = ref(false)
|
||||
const selectedDownload = ref(null)
|
||||
const retryingIds = ref([])
|
||||
const verboseLogFilter = ref('')
|
||||
const openLogPanels = ref([])
|
||||
let refreshInterval = null
|
||||
|
||||
const statusOptions = [
|
||||
@@ -595,9 +671,34 @@ function getSourceLabel(item) {
|
||||
return `Source #${sourceId}`
|
||||
}
|
||||
|
||||
const runStats = computed(() => selectedDownload.value?.metadata?.run_stats || null)
|
||||
|
||||
const filteredVerboseLog = computed(() => {
|
||||
const stderr = selectedDownload.value?.metadata?.stderr || ''
|
||||
const query = verboseLogFilter.value.trim().toLowerCase()
|
||||
if (!query) return stderr
|
||||
return stderr
|
||||
.split('\n')
|
||||
.filter(line => line.toLowerCase().includes(query))
|
||||
.join('\n')
|
||||
})
|
||||
|
||||
function showDetails(download) {
|
||||
selectedDownload.value = download
|
||||
detailsDialog.value = true
|
||||
verboseLogFilter.value = ''
|
||||
// Open the errors/warnings panel by default when there's anything worth seeing.
|
||||
openLogPanels.value = download?.metadata?.stderr_errors_warnings ? [0] : []
|
||||
}
|
||||
|
||||
async function copyToClipboard(text, label = 'content') {
|
||||
if (!text) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
notifications.success(`Copied ${label} to clipboard`)
|
||||
} catch (err) {
|
||||
notifications.error(`Copy failed: ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function retryDownload(download) {
|
||||
|
||||
Reference in New Issue
Block a user