0.12.0-3 Plugin Integration
This commit is contained in:
@@ -197,6 +197,40 @@ function runtimeOutcomeMeta(outcome, status) {
|
||||
return runtimeStatusMeta(status);
|
||||
}
|
||||
|
||||
function driveStateIconMeta(stateLabel) {
|
||||
const normalized = String(stateLabel || '').trim().toUpperCase();
|
||||
if (!normalized || normalized === 'LEER' || normalized === 'IDLE') {
|
||||
return { icon: 'pi-stop', spin: false };
|
||||
}
|
||||
if (normalized === 'DISC_DETECTED') {
|
||||
return { icon: 'pi-check', spin: false };
|
||||
}
|
||||
if (normalized === 'FINISHED') {
|
||||
return { icon: 'pi-check-circle', spin: false };
|
||||
}
|
||||
if (normalized === 'ERROR' || normalized === 'CANCELLED') {
|
||||
return { icon: 'pi-times-circle', spin: false };
|
||||
}
|
||||
if (processingStates.includes(normalized)) {
|
||||
return { icon: 'pi-spinner', spin: true };
|
||||
}
|
||||
if (
|
||||
normalized === 'METADATA_SELECTION'
|
||||
|| normalized === 'CD_METADATA_SELECTION'
|
||||
|| normalized === 'WAITING_FOR_USER_DECISION'
|
||||
) {
|
||||
return { icon: 'pi-list', spin: false };
|
||||
}
|
||||
if (
|
||||
normalized === 'READY_TO_START'
|
||||
|| normalized === 'READY_TO_ENCODE'
|
||||
|| normalized === 'CD_READY_TO_RIP'
|
||||
) {
|
||||
return { icon: 'pi-play', spin: false };
|
||||
}
|
||||
return { icon: 'pi-question-circle', spin: false };
|
||||
}
|
||||
|
||||
function hasRuntimeOutputDetails(item) {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return false;
|
||||
@@ -304,6 +338,20 @@ function normalizeQueue(queue) {
|
||||
};
|
||||
}
|
||||
|
||||
function buildCdDriveLifecycleKey(cdDrives) {
|
||||
if (!cdDrives || typeof cdDrives !== 'object') {
|
||||
return '';
|
||||
}
|
||||
const entries = Object.entries(cdDrives)
|
||||
.map(([devicePath, driveState]) => {
|
||||
const state = String(driveState?.state || '').trim().toUpperCase();
|
||||
const jobId = normalizeJobId(driveState?.jobId || driveState?.context?.jobId) || 0;
|
||||
return `${devicePath}|${jobId}|${state}`;
|
||||
})
|
||||
.sort();
|
||||
return entries.join('::');
|
||||
}
|
||||
|
||||
function getQueueActionResult(response) {
|
||||
return response?.result && typeof response.result === 'object' ? response.result : {};
|
||||
}
|
||||
@@ -917,6 +965,10 @@ export default function DashboardPage({
|
||||
() => normalizeHardwareMonitoringPayload(hardwareMonitoring),
|
||||
[hardwareMonitoring]
|
||||
);
|
||||
const cdDriveLifecycleKey = useMemo(
|
||||
() => buildCdDriveLifecycleKey(pipeline?.cdDrives),
|
||||
[pipeline?.cdDrives]
|
||||
);
|
||||
const monitoringSample = monitoringState.sample;
|
||||
const cpuMetrics = monitoringSample?.cpu || null;
|
||||
const memoryMetrics = monitoringSample?.memory || null;
|
||||
@@ -1115,13 +1167,43 @@ export default function DashboardPage({
|
||||
}
|
||||
}, [pipeline?.cdDrives]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cdMetadataDialogVisible) {
|
||||
return;
|
||||
}
|
||||
const dialogJobId = normalizeJobId(cdMetadataDialogContext?.jobId);
|
||||
if (!dialogJobId) {
|
||||
return;
|
||||
}
|
||||
const cdDrives = pipeline?.cdDrives;
|
||||
if (!cdDrives || typeof cdDrives !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
let stillInMetadataSelection = false;
|
||||
for (const drive of Object.values(cdDrives)) {
|
||||
const driveState = String(drive?.state || '').trim().toUpperCase();
|
||||
const ctx = drive?.context && typeof drive.context === 'object' ? drive.context : null;
|
||||
const driveJobId = normalizeJobId(ctx?.jobId || drive?.jobId);
|
||||
if (driveJobId === dialogJobId && driveState === 'CD_METADATA_SELECTION') {
|
||||
stillInMetadataSelection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stillInMetadataSelection) {
|
||||
setCdMetadataDialogVisible(false);
|
||||
setCdMetadataDialogContext(null);
|
||||
}
|
||||
}, [cdMetadataDialogVisible, cdMetadataDialogContext?.jobId, pipeline?.cdDrives]);
|
||||
|
||||
useEffect(() => {
|
||||
setQueueState(normalizeQueue(pipeline?.queue));
|
||||
}, [pipeline?.queue]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadDashboardJobs();
|
||||
}, [pipeline?.state, pipeline?.activeJobId, pipeline?.context?.jobId, pipeline?.cdDrives, jobsRefreshToken]);
|
||||
}, [pipeline?.state, pipeline?.activeJobId, pipeline?.context?.jobId, cdDriveLifecycleKey, jobsRefreshToken]);
|
||||
|
||||
useEffect(() => {
|
||||
api.getDetectedDrives()
|
||||
@@ -1553,18 +1635,28 @@ export default function DashboardPage({
|
||||
try {
|
||||
const response = await api.deleteJobFiles(jobId, effectiveTarget);
|
||||
const summary = response?.summary || {};
|
||||
const deletedFiles = effectiveTarget === 'raw'
|
||||
? (summary.raw?.filesDeleted ?? 0)
|
||||
: (summary.movie?.filesDeleted ?? 0);
|
||||
const removedDirs = effectiveTarget === 'raw'
|
||||
? (summary.raw?.dirsRemoved ?? 0)
|
||||
: (summary.movie?.dirsRemoved ?? 0);
|
||||
toastRef.current?.show({
|
||||
severity: 'success',
|
||||
summary: effectiveTarget === 'raw' ? 'RAW gelöscht' : 'Movie gelöscht',
|
||||
detail: `Entfernt: ${deletedFiles} Datei(en), ${removedDirs} Ordner.`,
|
||||
life: 4000
|
||||
});
|
||||
const targetSummary = effectiveTarget === 'raw'
|
||||
? (summary.raw || {})
|
||||
: (summary.movie || {});
|
||||
const deletedFiles = targetSummary.filesDeleted ?? 0;
|
||||
const removedDirs = targetSummary.dirsRemoved ?? 0;
|
||||
const deletedSomething = Boolean(targetSummary.deleted);
|
||||
|
||||
if (!deletedSomething) {
|
||||
toastRef.current?.show({
|
||||
severity: 'warn',
|
||||
summary: effectiveTarget === 'raw' ? 'RAW nicht gelöscht' : 'Movie nicht gelöscht',
|
||||
detail: targetSummary.reason || 'Keine passenden Dateien/Ordner gefunden.',
|
||||
life: 4200
|
||||
});
|
||||
} else {
|
||||
toastRef.current?.show({
|
||||
severity: 'success',
|
||||
summary: effectiveTarget === 'raw' ? 'RAW gelöscht' : 'Movie gelöscht',
|
||||
detail: `Entfernt: ${deletedFiles} Datei(en), ${removedDirs} Ordner.`,
|
||||
life: 4000
|
||||
});
|
||||
}
|
||||
await loadDashboardJobs();
|
||||
await refreshPipeline();
|
||||
setCancelCleanupDialog({ visible: false, jobId: null, target: null, path: null });
|
||||
@@ -2503,6 +2595,8 @@ export default function DashboardPage({
|
||||
const canAnalyzeDrive = cdState
|
||||
? cdState === 'DISC_DETECTED'
|
||||
: (Boolean(pipelineDevice) && pipelineState === 'DISC_DETECTED') || Boolean(drv.detectedDisc);
|
||||
const stateIconMeta = driveStateIconMeta(stateLabel);
|
||||
const discArg = String(drv.discArg || '').trim();
|
||||
|
||||
return (
|
||||
<div key={drivePath} className="drive-list-item">
|
||||
@@ -2510,10 +2604,27 @@ export default function DashboardPage({
|
||||
<div className="drive-list-info">
|
||||
<code className="drive-list-path">{drivePath}</code>
|
||||
{drv.model && <span className="drive-list-model">{drv.model}</span>}
|
||||
{drv.discArg && <span className="drive-list-disc-arg">{drv.discArg}</span>}
|
||||
</div>
|
||||
<div className="drive-list-state">
|
||||
{stateLabel && <Tag value={stateLabel} severity={stateSeverity} className="drive-list-status-tag" />}
|
||||
{stateLabel ? (
|
||||
<span
|
||||
className={`drive-list-status-icon tone-${stateSeverity}`}
|
||||
title={getStatusLabel(stateLabel)}
|
||||
aria-label={`Status: ${getStatusLabel(stateLabel)}`}
|
||||
>
|
||||
<i className={`pi ${stateIconMeta.icon}${stateIconMeta.spin ? ' pi-spin' : ''}`} aria-hidden="true" />
|
||||
</span>
|
||||
) : null}
|
||||
{isCdDriveLocked ? (
|
||||
<span
|
||||
className="drive-list-lock-indicator"
|
||||
title="Laufwerk ist gesperrt, bis Rip/Encode abgeschlossen ist."
|
||||
aria-label="Laufwerk gesperrt"
|
||||
>
|
||||
<i className="pi pi-lock" aria-hidden="true" />
|
||||
<span>gesperrt</span>
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="drive-list-actions">
|
||||
<Button
|
||||
@@ -2522,6 +2633,7 @@ export default function DashboardPage({
|
||||
rounded
|
||||
size="small"
|
||||
severity="secondary"
|
||||
className="drive-list-action-btn"
|
||||
loading={isRescanBusy}
|
||||
disabled={isRescanBusy || isDriveActive || isCdDriveLocked}
|
||||
onClick={() => handleRescanDrive(drivePath)}
|
||||
@@ -2535,6 +2647,7 @@ export default function DashboardPage({
|
||||
rounded
|
||||
size="small"
|
||||
severity="secondary"
|
||||
className="drive-list-action-btn"
|
||||
loading={isAnalyzeBusy}
|
||||
disabled={isAnalyzeBusy}
|
||||
onClick={() => handleAnalyzeForDrive(drivePath)}
|
||||
@@ -2544,15 +2657,19 @@ export default function DashboardPage({
|
||||
)}
|
||||
{cdState === 'CD_METADATA_SELECTION' && (
|
||||
<Button
|
||||
label="Metadaten"
|
||||
icon="pi pi-list"
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
severity="info"
|
||||
className="drive-list-action-btn"
|
||||
onClick={() => {
|
||||
setCdMetadataDialogContext({ ...driveCtx, jobId: driveJobId });
|
||||
setCdMetadataDialogVisible(true);
|
||||
}}
|
||||
disabled={!driveJobId}
|
||||
title="Metadaten auswählen"
|
||||
aria-label="Metadaten auswählen"
|
||||
/>
|
||||
)}
|
||||
{(cdState === 'ERROR' || cdState === 'CANCELLED') && driveJobId && (
|
||||
@@ -2562,6 +2679,7 @@ export default function DashboardPage({
|
||||
rounded
|
||||
size="small"
|
||||
severity="warning"
|
||||
className="drive-list-action-btn"
|
||||
onClick={() => handleAnalyzeForDrive(drivePath)}
|
||||
loading={isAnalyzeBusy}
|
||||
disabled={isAnalyzeBusy}
|
||||
@@ -2571,7 +2689,12 @@ export default function DashboardPage({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{discInfo && <div className="drive-list-disc-label">{discInfo}</div>}
|
||||
{(discArg || discInfo) ? (
|
||||
<div className="drive-list-disc-meta">
|
||||
{discArg ? <span className="drive-list-disc-arg">{discArg}</span> : null}
|
||||
{discInfo ? <span className="drive-list-disc-label" title={discInfo}>{discInfo}</span> : null}
|
||||
</div>
|
||||
) : null}
|
||||
{showProgress && (
|
||||
<ProgressBar value={progressValue} style={{ height: '5px', margin: '0.2rem 0 0' }} showValue={false} />
|
||||
)}
|
||||
@@ -2663,7 +2786,7 @@ export default function DashboardPage({
|
||||
</div>
|
||||
) : (
|
||||
<Card title="Job Übersicht" subTitle="Kompakte Liste; Klick auf Zeile öffnet die volle Job-Detailansicht mit passenden CTAs">
|
||||
{jobsLoading ? (
|
||||
{jobsLoading && dashboardJobs.length === 0 ? (
|
||||
<p>Jobs werden geladen ...</p>
|
||||
) : dashboardJobs.length === 0 ? (
|
||||
<p>Keine relevanten Jobs im Dashboard (aktive/fortsetzbare Status).</p>
|
||||
|
||||
Reference in New Issue
Block a user