diff --git a/frontend/src/views/Downloads.vue b/frontend/src/views/Downloads.vue
index dfeb02e..de508d0 100644
--- a/frontend/src/views/Downloads.vue
+++ b/frontend/src/views/Downloads.vue
@@ -301,7 +301,29 @@
| Files Downloaded |
- {{ selectedDownload.file_count }} |
+ {{ runStats?.downloaded_count ?? selectedDownload.file_count }} |
+
+
+ | Skipped |
+ {{ runStats.skipped_count }} (already in archive) |
+
+
+ | Per-item Failures |
+ {{ runStats.per_item_failures }} (single items gallery-dl skipped over) |
+
+
+ | Warnings |
+ {{ runStats.warning_count }} |
+
+
+ | Duration |
+ {{ formatDuration(selectedDownload) }} |
+
+
+ | Exit Code |
+
+ {{ runStats.exit_code }}
+ |
| Created |
@@ -326,17 +348,69 @@
-
+
+
+
+ Errors & Warnings
+
+ mdi-content-copy
+ Copy
+
+
+
+ {{ selectedDownload.metadata.stderr_errors_warnings }}
+
+
- Output Log
+
+ Output Log (stdout)
+
+ mdi-content-copy
+ Copy
+
+
{{ selectedDownload.metadata.stdout }}
- Verbose Log (stderr)
+
+ Verbose Log (stderr)
+
+ mdi-content-copy
+ Copy
+
+
- {{ selectedDownload.metadata.stderr }}
+
+ {{ filteredVerboseLog || '(no matching lines)' }}
@@ -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) {