feat(fc3c): /downloads view + DownloadEventRow + DownloadDetailModal + FilterPills

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 20:46:48 -04:00
parent 397c21c88e
commit d35605ab73
5 changed files with 294 additions and 3 deletions
@@ -0,0 +1,117 @@
<template>
<v-dialog :model-value="!!event" max-width="900" @update:model-value="onClose">
<v-card v-if="event">
<v-card-title class="d-flex align-center" style="gap: 0.5rem">
<v-chip size="small" :color="statusColor">{{ event.status }}</v-chip>
<span>{{ event.platform || '—' }} · {{ event.artist_name || '—' }}</span>
</v-card-title>
<v-card-text>
<p class="text-caption" style="opacity: 0.75">
{{ event.started_at }} {{ event.finished_at || '(running)' }}
({{ fmtDuration(event.summary?.duration_seconds) }})
</p>
<h3 class="text-subtitle-2 mt-3">Run stats</h3>
<div class="fc-dl-grid">
<div><span class="fc-dl-grid__label">Downloaded</span><span>{{ stats.downloaded_count ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Skipped</span><span>{{ stats.skipped_count ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Per-item failures</span><span>{{ stats.per_item_failures ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Warnings</span><span>{{ stats.warning_count ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Tier-gated</span><span>{{ stats.tier_gated_count ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Quarantined</span><span>{{ stats.quarantined_count ?? 0 }}</span></div>
</div>
<h3 class="text-subtitle-2 mt-4">Import summary</h3>
<div class="fc-dl-grid">
<div><span class="fc-dl-grid__label">Attached</span><span>{{ summary.attached ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Skipped (dedup)</span><span>{{ summary.skipped ?? 0 }}</span></div>
<div><span class="fc-dl-grid__label">Errors</span><span>{{ summary.errors ?? 0 }}</span></div>
</div>
<template v-if="quarantinedPaths.length">
<h3 class="text-subtitle-2 mt-4">Quarantined paths</h3>
<ul class="fc-dl-quar">
<li v-for="p in quarantinedPaths" :key="p"><code>{{ p }}</code></li>
</ul>
</template>
<template v-if="errorsWarnings">
<h3 class="text-subtitle-2 mt-4">Errors &amp; warnings</h3>
<pre class="fc-dl-pre">{{ errorsWarnings }}</pre>
</template>
<v-expansion-panels class="mt-4">
<v-expansion-panel>
<v-expansion-panel-title>Raw stdout</v-expansion-panel-title>
<v-expansion-panel-text>
<pre class="fc-dl-pre">{{ event.metadata?.stdout || '(empty)' }}</pre>
</v-expansion-panel-text>
</v-expansion-panel>
<v-expansion-panel>
<v-expansion-panel-title>Raw stderr</v-expansion-panel-title>
<v-expansion-panel-text>
<pre class="fc-dl-pre">{{ event.metadata?.stderr || '(empty)' }}</pre>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="onClose(false)">Close</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({ event: { type: Object, default: null } })
const emit = defineEmits(['close'])
const stats = computed(() => props.event?.metadata?.run_stats || {})
const summary = computed(() => props.event?.metadata?.import_summary || {})
const quarantinedPaths = computed(() => props.event?.metadata?.quarantined_paths || [])
const errorsWarnings = computed(() => props.event?.metadata?.stderr_errors_warnings || '')
const statusColor = computed(() => ({
ok: 'success', error: 'error', running: 'info',
pending: 'secondary', skipped: 'warning',
}[props.event?.status] || undefined))
function fmtDuration(sec) {
if (sec == null) return '—'
if (sec < 60) return `${sec.toFixed(1)}s`
const m = Math.floor(sec / 60), s = Math.floor(sec % 60)
return `${m}m ${s}s`
}
function onClose() {
emit('close')
}
</script>
<style scoped>
.fc-dl-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 0.5rem 1rem;
font-variant-numeric: tabular-nums;
}
.fc-dl-grid > div { display: flex; justify-content: space-between; }
.fc-dl-grid__label {
color: rgb(var(--v-theme-on-surface-variant));
}
.fc-dl-pre {
background: rgb(var(--v-theme-surface));
border: 1px solid rgb(var(--v-theme-on-surface-variant) / 0.18);
padding: 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
max-height: 400px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
}
.fc-dl-quar { padding-left: 1.5rem; font-size: 0.85rem; }
</style>