feat(downloads): copy buttons in the download detail modal
Operator-flagged 2026-05-28: the download event detail modal had no way to copy the error / stdout / stderr text for researching a failure (parity with the ErrorDetailModal Copy button shipped in v26.05.26.0). Added per-block Copy buttons (Error, Errors & warnings, Raw stdout, Raw stderr — the stdout/stderr ones sit in the expansion-panel title with @click.stop so they don't toggle the panel) plus a "Copy all diagnostics" button in the footer that assembles a single block: header line (event id / platform / artist / status / timestamps) + error + errors&warnings + full stdout + stderr, ready to paste into an issue. All routed through utils/clipboard.js copyText() — the navigator.clipboard → execCommand fallback that works on the plain-HTTP homelab origin (per feedback_no_secure_context_apis). Each copy shows a confirmation toast.
This commit is contained in:
@@ -35,20 +35,53 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-if="event.error">
|
||||||
|
<div class="fc-dl-blockhead mt-4">
|
||||||
|
<h3 class="text-subtitle-2">Error</h3>
|
||||||
|
<v-btn
|
||||||
|
size="x-small" variant="text" prepend-icon="mdi-content-copy"
|
||||||
|
@click="onCopy('Error', event.error)"
|
||||||
|
>Copy</v-btn>
|
||||||
|
</div>
|
||||||
|
<pre class="fc-dl-pre">{{ event.error }}</pre>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-if="errorsWarnings">
|
<template v-if="errorsWarnings">
|
||||||
<h3 class="text-subtitle-2 mt-4">Errors & warnings</h3>
|
<div class="fc-dl-blockhead mt-4">
|
||||||
|
<h3 class="text-subtitle-2">Errors & warnings</h3>
|
||||||
|
<v-btn
|
||||||
|
size="x-small" variant="text" prepend-icon="mdi-content-copy"
|
||||||
|
@click="onCopy('Errors & warnings', errorsWarnings)"
|
||||||
|
>Copy</v-btn>
|
||||||
|
</div>
|
||||||
<pre class="fc-dl-pre">{{ errorsWarnings }}</pre>
|
<pre class="fc-dl-pre">{{ errorsWarnings }}</pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-expansion-panels class="mt-4">
|
<v-expansion-panels class="mt-4">
|
||||||
<v-expansion-panel>
|
<v-expansion-panel>
|
||||||
<v-expansion-panel-title>Raw stdout</v-expansion-panel-title>
|
<v-expansion-panel-title>
|
||||||
|
<span>Raw stdout</span>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
size="x-small" variant="text" prepend-icon="mdi-content-copy"
|
||||||
|
class="me-2"
|
||||||
|
@click.stop="onCopy('stdout', event.metadata?.stdout || '')"
|
||||||
|
>Copy</v-btn>
|
||||||
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<pre class="fc-dl-pre">{{ event.metadata?.stdout || '(empty)' }}</pre>
|
<pre class="fc-dl-pre">{{ event.metadata?.stdout || '(empty)' }}</pre>
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
<v-expansion-panel>
|
<v-expansion-panel>
|
||||||
<v-expansion-panel-title>Raw stderr</v-expansion-panel-title>
|
<v-expansion-panel-title>
|
||||||
|
<span>Raw stderr</span>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
size="x-small" variant="text" prepend-icon="mdi-content-copy"
|
||||||
|
class="me-2"
|
||||||
|
@click.stop="onCopy('stderr', event.metadata?.stderr || '')"
|
||||||
|
>Copy</v-btn>
|
||||||
|
</v-expansion-panel-title>
|
||||||
<v-expansion-panel-text>
|
<v-expansion-panel-text>
|
||||||
<pre class="fc-dl-pre">{{ event.metadata?.stderr || '(empty)' }}</pre>
|
<pre class="fc-dl-pre">{{ event.metadata?.stderr || '(empty)' }}</pre>
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
@@ -56,6 +89,10 @@
|
|||||||
</v-expansion-panels>
|
</v-expansion-panels>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
variant="text" prepend-icon="mdi-content-copy"
|
||||||
|
@click="onCopy('All diagnostics', allDiagnostics)"
|
||||||
|
>Copy all diagnostics</v-btn>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn variant="text" @click="onClose(false)">Close</v-btn>
|
<v-btn variant="text" @click="onClose(false)">Close</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
@@ -66,6 +103,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { copyText } from '../../utils/clipboard.js'
|
||||||
|
|
||||||
const props = defineProps({ event: { type: Object, default: null } })
|
const props = defineProps({ event: { type: Object, default: null } })
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
@@ -74,6 +113,32 @@ const summary = computed(() => props.event?.metadata?.import_summary || {})
|
|||||||
const quarantinedPaths = computed(() => props.event?.metadata?.quarantined_paths || [])
|
const quarantinedPaths = computed(() => props.event?.metadata?.quarantined_paths || [])
|
||||||
const errorsWarnings = computed(() => props.event?.metadata?.stderr_errors_warnings || '')
|
const errorsWarnings = computed(() => props.event?.metadata?.stderr_errors_warnings || '')
|
||||||
|
|
||||||
|
// One combined block for "research the issue elsewhere" — header line +
|
||||||
|
// error + full stdout/stderr. Built lazily from the current event.
|
||||||
|
const allDiagnostics = computed(() => {
|
||||||
|
const e = props.event
|
||||||
|
if (!e) return ''
|
||||||
|
const md = e.metadata || {}
|
||||||
|
return [
|
||||||
|
`event #${e.id} · ${e.platform || '—'} · ${e.artist_name || '—'}`,
|
||||||
|
`status: ${e.status}`,
|
||||||
|
`started: ${e.started_at} finished: ${e.finished_at || '(running)'}`,
|
||||||
|
e.error ? `\n--- error ---\n${e.error}` : '',
|
||||||
|
errorsWarnings.value ? `\n--- errors & warnings ---\n${errorsWarnings.value}` : '',
|
||||||
|
`\n--- stdout ---\n${md.stdout || '(empty)'}`,
|
||||||
|
`\n--- stderr ---\n${md.stderr || '(empty)'}`,
|
||||||
|
].filter(Boolean).join('\n')
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onCopy(label, text) {
|
||||||
|
try {
|
||||||
|
await copyText(text || '')
|
||||||
|
window.__fcToast?.({ text: `${label} copied`, type: 'success' })
|
||||||
|
} catch (e) {
|
||||||
|
window.__fcToast?.({ text: `Copy failed: ${e.message}`, type: 'error' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const statusColor = computed(() => ({
|
const statusColor = computed(() => ({
|
||||||
ok: 'success', error: 'error', running: 'info',
|
ok: 'success', error: 'error', running: 'info',
|
||||||
pending: 'secondary', skipped: 'warning',
|
pending: 'secondary', skipped: 'warning',
|
||||||
@@ -114,4 +179,8 @@ function onClose() {
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
.fc-dl-quar { padding-left: 1.5rem; font-size: 0.85rem; }
|
.fc-dl-quar { padding-left: 1.5rem; font-size: 0.85rem; }
|
||||||
|
.fc-dl-blockhead {
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user