0.12.0-2 Checkable Infrastructure

This commit is contained in:
2026-03-19 20:19:07 +00:00
parent 24955a956d
commit d9969dfbe5
20 changed files with 1207 additions and 278 deletions
+7
View File
@@ -742,16 +742,23 @@ export const api = {
},
async deleteJobEntry(jobId, target = 'none', options = {}) {
const includeRelated = Boolean(options?.includeRelated);
const resetDriveState = Boolean(options?.resetDriveState);
const selectedMoviePaths = Array.isArray(options?.selectedMoviePaths)
? options.selectedMoviePaths
.map((item) => String(item || '').trim())
.filter(Boolean)
: null;
const hasKeepDetectedDevice = options?.keepDetectedDevice !== undefined;
const keepDetectedDevice = hasKeepDetectedDevice
? Boolean(options.keepDetectedDevice)
: null;
const result = await request(`/history/${jobId}/delete`, {
method: 'POST',
body: JSON.stringify({
target,
includeRelated,
resetDriveState,
...(hasKeepDetectedDevice ? { keepDetectedDevice } : {}),
...(selectedMoviePaths ? { selectedMoviePaths } : {})
})
});
@@ -129,6 +129,7 @@ export default function AudiobookConfigPanel({
onStart,
onCancel,
onRetry,
onDeleteJob,
busy
}) {
const context = pipeline?.context && typeof pipeline.context === 'object' ? pipeline.context : {};
@@ -353,6 +354,17 @@ export default function AudiobookConfigPanel({
/>
) : null}
{jobId ? (
<Button
label="Job löschen"
icon="pi pi-trash"
severity="danger"
outlined
onClick={() => onDeleteJob?.(jobId)}
loading={busy}
/>
) : null}
{isRunning ? (
<Button
label="Abbrechen"
@@ -229,6 +229,7 @@ export default function CdRipConfigPanel({
onRetry,
onRestartReview,
onOpenMetadata,
onDeleteJob,
busy
}) {
const context = pipeline?.context && typeof pipeline.context === 'object' ? pipeline.context : {};
@@ -809,6 +810,16 @@ export default function CdRipConfigPanel({
onClick={() => onCancel && onCancel()}
disabled={busy}
/>
{jobId ? (
<Button
label="Job löschen"
icon="pi pi-trash"
severity="danger"
outlined
onClick={() => onDeleteJob && onDeleteJob(jobId)}
disabled={busy}
/>
) : null}
</div>
) : null}
{isTerminalFailure ? (
@@ -851,6 +862,16 @@ export default function CdRipConfigPanel({
loading={busy}
/>
) : null}
{jobId ? (
<Button
label="Job löschen"
icon="pi pi-trash"
severity="danger"
outlined
onClick={() => onDeleteJob && onDeleteJob(jobId)}
loading={busy}
/>
) : null}
</div>
) : null}
@@ -1297,6 +1318,16 @@ export default function CdRipConfigPanel({
disabled={busy}
/>
) : null}
{jobId ? (
<Button
label="Job löschen"
icon="pi pi-trash"
severity="danger"
outlined
onClick={() => onDeleteJob && onDeleteJob(jobId)}
disabled={busy}
/>
) : null}
<Button
label="Abbrechen"
severity="secondary"
@@ -313,6 +313,7 @@ export default function PipelineStatusCard({
onSelectHandBrakeTitle,
onCancel,
onRetry,
onDeleteJob,
isQueued = false,
busy,
liveJobLog = ''
@@ -832,6 +833,16 @@ export default function PipelineStatusCard({
) : null}
</>
)}
{retryJobId && typeof onDeleteJob === 'function' ? (
<Button
label="Job löschen"
icon="pi pi-trash"
severity="danger"
outlined
onClick={() => onDeleteJob?.(retryJobId)}
loading={busy}
/>
) : null}
</div>
{running ? (
+66
View File
@@ -731,6 +731,16 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
&& !processingStates.includes(jobStatus)
&& resolvedMediaType !== 'audiobook'
);
const cdSkipRipReady = Boolean(
resolvedMediaType === 'cd'
&& (
Boolean(encodePlan?.skipRip)
|| (
Number(job?.rip_successful || 0) === 1
&& Boolean(String(job?.raw_path || '').trim())
)
)
);
const computedContext = {
jobId,
rawPath: job?.raw_path || null,
@@ -741,6 +751,7 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
devicePath,
cdparanoiaCmd,
cdparanoiaCommandPreview,
skipRip: cdSkipRipReady ? true : undefined,
cdRipConfig,
tracks: cdTracks,
inputPath,
@@ -1777,6 +1788,58 @@ export default function DashboardPage({
}
};
const handleDeleteDashboardJob = async (jobId) => {
const normalizedJobId = normalizeJobId(jobId);
if (!normalizedJobId) {
return;
}
const job = dashboardJobs.find((item) => normalizeJobId(item?.id) === normalizedJobId) || null;
const title = String(job?.title || job?.detected_title || `Job #${normalizedJobId}`).trim();
const confirmed = window.confirm(
`Job #${normalizedJobId} wirklich löschen?\n` +
`${title}\n\n` +
'Hinweis: Dateien bleiben erhalten. Das zugeordnete Laufwerk wird freigegeben und auf Leerzustand zurückgesetzt.'
);
if (!confirmed) {
return;
}
setJobBusy(normalizedJobId, true);
try {
const statusBeforeDelete = String(job?.status || '').trim().toUpperCase();
if (processingStates.includes(statusBeforeDelete)) {
await api.cancelPipeline(normalizedJobId);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
for (let attempt = 0; attempt < 12; attempt += 1) {
const latest = await api.getJob(normalizedJobId, { lite: true, forceRefresh: true }).catch(() => null);
const latestStatus = String(latest?.job?.status || latest?.job?.last_state || '').trim().toUpperCase();
if (!processingStates.includes(latestStatus)) {
break;
}
await wait(250);
}
}
await api.deleteJobEntry(normalizedJobId, 'none', {
includeRelated: false,
resetDriveState: true,
keepDetectedDevice: false
});
await refreshPipeline();
await loadDashboardJobs();
setExpandedJobId((prev) => (normalizeJobId(prev) === normalizedJobId ? null : prev));
toastRef.current?.show({
severity: 'success',
summary: 'Job gelöscht',
detail: `Job #${normalizedJobId} wurde entfernt.`,
life: 3200
});
} catch (error) {
showError(error);
} finally {
setJobBusy(normalizedJobId, false);
}
};
const handleRestartEncodeWithLastSettings = async (jobId) => {
const job = dashboardJobs.find((item) => normalizeJobId(item?.id) === normalizeJobId(jobId)) || null;
const title = job?.title || job?.detected_title || `Job #${jobId}`;
@@ -2702,6 +2765,7 @@ export default function DashboardPage({
onCancel={() => handleCancel(jobId, jobState)}
onRetry={() => handleRetry(jobId)}
onRestartReview={() => handleRestartCdReviewFromRaw(jobId)}
onDeleteJob={() => handleDeleteDashboardJob(jobId)}
onOpenMetadata={() => {
const ctx = pipelineForJob?.context && typeof pipelineForJob.context === 'object'
? pipelineForJob.context
@@ -2733,6 +2797,7 @@ export default function DashboardPage({
onStart={(config) => handleAudiobookStart(jobId, config)}
onCancel={() => handleCancel(jobId, jobState)}
onRetry={() => handleRetry(jobId)}
onDeleteJob={() => handleDeleteDashboardJob(jobId)}
busy={busyJobIds.has(jobId) || needsBytes}
/>
</>
@@ -2755,6 +2820,7 @@ export default function DashboardPage({
onSelectHandBrakeTitle={handleSelectHandBrakeTitle}
onCancel={handleCancel}
onRetry={handleRetry}
onDeleteJob={handleDeleteDashboardJob}
onRemoveFromQueue={handleRemoveQueuedJob}
isQueued={isQueued}
busy={busyJobIds.has(jobId)}
+1 -1
View File
@@ -15,7 +15,7 @@ const STATUS_LABELS = {
ERROR: 'Fehler',
CD_ANALYZING: 'CD-Analyse',
CD_METADATA_SELECTION: 'CD-Metadatenauswahl',
CD_READY_TO_RIP: 'CD bereit zum Rippen',
CD_READY_TO_RIP: 'CD bereit (Rip/Encode)',
CD_RIPPING: 'CD rippen',
CD_ENCODING: 'CD encodieren'
};