This commit is contained in:
2026-03-04 14:48:56 +00:00
parent 31d3e36597
commit 3b293bb743
11 changed files with 1116 additions and 310 deletions

View File

@@ -20,6 +20,7 @@ export default function JobDetailDialog({
onLoadLog,
logLoadingMode = null,
onAssignOmdb,
onRestartEncode,
onReencode,
onDeleteFiles,
onDeleteEntry,
@@ -32,7 +33,15 @@ export default function JobDetailDialog({
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(job?.status);
const showFinalLog = !running;
const canReencode = !!(job?.rawStatus?.exists && job?.rawStatus?.isEmpty !== true && mkDone && !running);
const canDeleteEntry = !running;
const hasConfirmedPlan = Boolean(
job?.encodePlan
&& Array.isArray(job?.encodePlan?.titles)
&& job?.encodePlan?.titles.length > 0
&& Number(job?.encode_review_confirmed || 0) === 1
);
const hasRestartInput = Boolean(job?.encode_input_path || job?.raw_path || job?.encodePlan?.encodeInputPath);
const canRestartEncode = Boolean(hasConfirmedPlan && hasRestartInput && !running);
const canDeleteEntry = !running && typeof onDeleteEntry === 'function';
const logCount = Number(job?.log_count || 0);
const logMeta = job?.logMeta && typeof job.logMeta === 'object' ? job.logMeta : null;
const logLoaded = Boolean(logMeta?.loaded) || Boolean(job?.log);
@@ -106,6 +115,14 @@ export default function JobDetailDialog({
<div>
<strong>Movie-Dir leer:</strong> {job.movieDirStatus?.isEmpty === null ? '-' : job.movieDirStatus?.isEmpty ? 'ja' : 'nein'}
</div>
<div>
<strong>Backup erfolgreich:</strong>{' '}
{job?.backupSuccess ? <span className="job-step-inline-ok"><i className="pi pi-check-circle" aria-hidden="true" /><span>ja</span></span> : 'nein'}
</div>
<div>
<strong>Encode erfolgreich:</strong>{' '}
{job?.encodeSuccess ? <span className="job-step-inline-ok"><i className="pi pi-check-circle" aria-hidden="true" /><span>ja</span></span> : 'nein'}
</div>
<div>
<strong>Fehler:</strong> {job.error_message || '-'}
</div>
@@ -136,8 +153,19 @@ export default function JobDetailDialog({
size="small"
onClick={() => onAssignOmdb?.(job)}
loading={omdbAssignBusy}
disabled={running}
disabled={running || typeof onAssignOmdb !== 'function'}
/>
{typeof onRestartEncode === 'function' ? (
<Button
label="Encode neu starten"
icon="pi pi-play"
severity="success"
size="small"
onClick={() => onRestartEncode?.(job)}
loading={actionBusy}
disabled={!canRestartEncode}
/>
) : null}
<Button
label="RAW neu encodieren"
icon="pi pi-cog"
@@ -145,7 +173,7 @@ export default function JobDetailDialog({
size="small"
onClick={() => onReencode?.(job)}
loading={reencodeBusy}
disabled={!canReencode}
disabled={!canReencode || typeof onReencode !== 'function'}
/>
<Button
label="RAW löschen"
@@ -155,7 +183,7 @@ export default function JobDetailDialog({
size="small"
onClick={() => onDeleteFiles?.(job, 'raw')}
loading={actionBusy}
disabled={!job.rawStatus?.exists}
disabled={!job.rawStatus?.exists || typeof onDeleteFiles !== 'function'}
/>
<Button
label="Movie löschen"
@@ -165,7 +193,7 @@ export default function JobDetailDialog({
size="small"
onClick={() => onDeleteFiles?.(job, 'movie')}
loading={actionBusy}
disabled={!job.outputStatus?.exists}
disabled={!job.outputStatus?.exists || typeof onDeleteFiles !== 'function'}
/>
<Button
label="Beides löschen"
@@ -174,7 +202,7 @@ export default function JobDetailDialog({
size="small"
onClick={() => onDeleteFiles?.(job, 'both')}
loading={actionBusy}
disabled={!job.rawStatus?.exists && !job.outputStatus?.exists}
disabled={(!job.rawStatus?.exists && !job.outputStatus?.exists) || typeof onDeleteFiles !== 'function'}
/>
<Button
label="Historieneintrag löschen"

View File

@@ -312,6 +312,28 @@ export default function PipelineStatusCard({
() => buildOutputPathPreview(settingsMap, selectedMetadata, retryJobId),
[settingsMap, selectedMetadata, retryJobId]
);
const buildSelectedTrackSelectionForCurrentTitle = () => {
const encodeTitleId = normalizeTitleId(selectedEncodeTitleId);
const selectionEntry = encodeTitleId
? (trackSelectionByTitle?.[encodeTitleId] || trackSelectionByTitle?.[String(encodeTitleId)] || null)
: null;
const fallbackSelection = encodeTitleId
? defaultTrackSelectionForTitle(mediaInfoReview, encodeTitleId)
: { audioTrackIds: [], subtitleTrackIds: [] };
const effectiveSelection = selectionEntry || fallbackSelection;
const selectedTrackSelection = encodeTitleId
? {
[encodeTitleId]: {
audioTrackIds: normalizeTrackIdList(effectiveSelection?.audioTrackIds || []),
subtitleTrackIds: normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || [])
}
}
: null;
return {
encodeTitleId,
selectedTrackSelection
};
};
return (
<Card title="Pipeline Status" subTitle="Live Zustand und Fortschritt">
@@ -353,37 +375,6 @@ export default function PipelineStatusCard({
/>
)}
{state === 'READY_TO_ENCODE' && retryJobId && (
<Button
label="Auswahl bestätigen"
icon="pi pi-check"
severity="warning"
outlined
onClick={() => {
const encodeTitleId = normalizeTitleId(selectedEncodeTitleId);
const selectionEntry = encodeTitleId
? (trackSelectionByTitle?.[encodeTitleId] || trackSelectionByTitle?.[String(encodeTitleId)] || null)
: null;
const fallbackSelection = encodeTitleId
? defaultTrackSelectionForTitle(mediaInfoReview, encodeTitleId)
: { audioTrackIds: [], subtitleTrackIds: [] };
const effectiveSelection = selectionEntry || fallbackSelection;
const selectedTrackSelection = encodeTitleId
? {
[encodeTitleId]: {
audioTrackIds: normalizeTrackIdList(effectiveSelection?.audioTrackIds || []),
subtitleTrackIds: normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || [])
}
}
: null;
onConfirmReview(retryJobId, encodeTitleId, selectedTrackSelection);
}}
loading={busy}
disabled={reviewConfirmed || !canConfirmReview}
/>
)}
{playlistDecisionRequiredBeforeStart && retryJobId && (
<Button
label="Playlist übernehmen"
@@ -398,12 +389,25 @@ export default function PipelineStatusCard({
{state === 'READY_TO_ENCODE' && retryJobId && (
<Button
label={isPreRipReview ? 'Backup + Encode starten' : 'Encode starten'}
label={isPreRipReview ? 'Backup + Encoding starten' : 'Encoding starten'}
icon="pi pi-play"
severity="success"
onClick={() => onStart(retryJobId)}
onClick={async () => {
const requiresAutoConfirm = !reviewConfirmed;
if (!requiresAutoConfirm) {
await onStart(retryJobId);
return;
}
const { encodeTitleId, selectedTrackSelection } = buildSelectedTrackSelectionForCurrentTitle();
await onStart(retryJobId, {
ensureConfirmed: true,
selectedEncodeTitleId: encodeTitleId,
selectedTrackSelection
});
}}
loading={busy}
disabled={!canStartReadyJob || !reviewConfirmed}
disabled={!canStartReadyJob || !canConfirmReview}
/>
)}
@@ -550,8 +554,8 @@ export default function PipelineStatusCard({
{state === 'READY_TO_ENCODE' && !reviewConfirmed ? (
<small>
{isPreRipReview
? 'Backup/Rip + Encode ist gesperrt, bis die Spurauswahl bestätigt wurde.'
: 'Encode ist gesperrt, bis die Titel-/Spurauswahl bestätigt wurde.'}
? 'Spurauswahl kann direkt übernommen werden. Beim Klick auf "Backup + Encoding starten" wird automatisch bestätigt und gestartet.'
: 'Spurauswahl kann direkt übernommen werden. Beim Klick auf "Encoding starten" wird automatisch bestätigt und gestartet.'}
{reviewPlaylistDecisionRequired ? ' Bitte den korrekten Titel per Checkbox auswählen.' : ''}
</small>
) : null}