From fbd439f3185bf1f3a661fb9bd8af8e98bc7f0cb6 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Mon, 16 Mar 2026 07:15:17 +0000 Subject: [PATCH] 0.10.2-3 Layout --- frontend/src/pages/DashboardPage.jsx | 786 ++++++++++++++------------- frontend/src/styles/app.css | 29 + 2 files changed, 426 insertions(+), 389 deletions(-) diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 4e961b7..e1b021a 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -2058,7 +2058,9 @@ export default function DashboardPage({
- +
+
+
- +
- { - const nextFile = event.target?.files?.[0] || null; - setAudiobookUploadFile(nextFile); - }} - disabled={audiobookUploadBusy} - />
- {audiobookUploadPhase !== 'idle' ? ( -
-
- {audiobookUploadStatusLabel} - -
- {audiobookUpload?.statusText ? {audiobookUpload.statusText} : null} - {audiobookUploadFileName ? ( - - Datei: {audiobookUploadFileName} - - ) : null} -
- - - {audiobookUploadPhase === 'processing' - ? '100% | Upload fertig, Job wird vorbereitet ...' - : audiobookUploadTotalBytes > 0 - ? `${Math.round(audiobookUploadProgress)}% | ${formatBytes(audiobookUploadLoadedBytes)} / ${formatBytes(audiobookUploadTotalBytes)}` - : `${Math.round(audiobookUploadProgress)}%`} - -
-
- ) : null} - - {audiobookUploadFileName && audiobookUploadPhase === 'idle' - ? `Ausgewählt: ${audiobookUploadFileName}` - : 'Unterstützt im MVP: AAX-Upload. Danach erscheint ein eigener Audiobook-Startschritt mit Format- und Qualitätswahl.'} - -
- - -
- - - - {queueState?.cdBypassesQueue && } - 0 ? 'warning' : 'success'} /> - 0 ? 'warning' : 'success'} /> - 0 ? 'warning' : 'success'} /> -
- -
-
-

Laufende Jobs

- {queueRunningJobs.length === 0 ? ( - Keine laufenden Jobs. - ) : ( - queueRunningJobs.map((item) => { - const hasScriptSummary = hasQueueScriptSummary(item); - const detailKey = buildRunningQueueScriptKey(item?.jobId); - const detailsExpanded = hasScriptSummary && expandedQueueScriptKeys.has(detailKey); - return ( -
-
-
- - #{item.jobId} | {item.title || `Job #${item.jobId}`} - {item.hasScripts ? : null} - {item.hasChains ? : null} - - {getStatusLabel(item.status)} -
- {hasScriptSummary ? ( - - ) : null} -
- {detailsExpanded ? : null} -
- ); - }) - )} -
-
-
-

Warteschlange

- -
- {queuedJobs.length === 0 ? ( - Queue ist leer. - ) : ( - <> - {queuedJobs.map((item) => { - const entryId = Number(item?.entryId); - const isNonJob = item.type && item.type !== 'job'; - const isDragging = Number(draggingQueueEntryId) === entryId; - const hasScriptSummary = !isNonJob && hasQueueScriptSummary(item); - const detailKey = buildQueuedQueueScriptKey(entryId); - const detailsExpanded = hasScriptSummary && expandedQueueScriptKeys.has(detailKey); - return ( -
-
setDraggingQueueEntryId(entryId)} - onDragEnter={() => handleQueueDragEnter(entryId)} - onDragOver={(event) => event.preventDefault()} - onDrop={(event) => { - event.preventDefault(); - void handleQueueDrop(); - }} - onDragEnd={() => { - setDraggingQueueEntryId(null); - void syncQueueFromServer(); - }} - > - - - - -
- {isNonJob ? ( - {item.position || '-'}. {queueEntryLabel(item)} - ) : ( - <> - - {item.position || '-'} | #{item.jobId} | {item.title || `Job #${item.jobId}`} - {item.hasScripts ? : null} - {item.hasChains ? : null} - - {item.actionLabel || item.action || '-'} | {getStatusLabel(item.status)} - - )} -
-
- {hasScriptSummary ? ( - - ) : null} -
-
- {detailsExpanded ? : null} - -
- ); - })} - - )} -
-
-
- - -
- 0 ? 'warning' : 'success'} /> - - -
- - {runtimeLoading && runtimeActiveItems.length === 0 && runtimeRecentItems.length === 0 ? ( -

Aktivitäten werden geladen ...

- ) : ( -
-
-

Aktiv

- {runtimeActiveItems.length === 0 ? ( - Keine laufenden Skript-/Ketten-/Cron-Ausführungen. - ) : ( -
- {runtimeActiveItems.map((item, index) => { - const statusMeta = runtimeStatusMeta(item?.status); - const canCancel = Boolean(item?.canCancel); - const canNextStep = String(item?.type || '').trim().toLowerCase() === 'chain' && Boolean(item?.canNextStep); - const cancelBusy = isRuntimeActionBusy(item?.id, 'cancel'); - const nextStepBusy = isRuntimeActionBusy(item?.id, 'next-step'); - return ( -
-
- {item?.name || '-'} -
- - -
-
- - Quelle: {item?.source || '-'} - {item?.jobId ? ` | Job #${item.jobId}` : ''} - {item?.cronJobId ? ` | Cron #${item.cronJobId}` : ''} - - {item?.currentStep ? Schritt: {item.currentStep} : null} - {item?.currentScriptName ? Laufendes Skript: {item.currentScriptName} : null} - {item?.message ? {item.message} : null} - - Gestartet: {formatUpdatedAt(item?.startedAt)} - {canCancel || canNextStep ? ( -
- {canNextStep ? ( -
- ) : null} -
- ); - })} -
- )} + {device ? ( +
+
+ Pfad: {device.path || '-'}
- -
-

Zuletzt abgeschlossen

- {runtimeRecentItems.length === 0 ? ( - Keine abgeschlossenen Einträge vorhanden. - ) : ( -
- {runtimeRecentItems.map((item, index) => { - const outcomeMeta = runtimeOutcomeMeta(item?.outcome, item?.status); - return ( -
-
- {item?.name || '-'} -
- - -
-
- - Quelle: {item?.source || '-'} - {item?.jobId ? ` | Job #${item.jobId}` : ''} - {item?.cronJobId ? ` | Cron #${item.cronJobId}` : ''} - - {Number.isFinite(Number(item?.exitCode)) ? Exit-Code: {item.exitCode} : null} - {item?.message ? {item.message} : null} - {item?.errorMessage ? {item.errorMessage} : null} - {hasRuntimeOutputDetails(item) ? ( - - ) : null} - - Ende: {formatUpdatedAt(item?.finishedAt || item?.startedAt)} - {item?.durationMs != null ? ` | Dauer: ${formatDurationMs(item.durationMs)}` : ''} - -
- ); - })} -
- )} +
+ Modell: {device.model || '-'} +
+
+ Disk-Label: {device.discLabel || '-'} +
+
+ Laufwerks-Label: {device.label || '-'} +
+
+ Mount: {device.mountpoint || '-'}
+ ) : ( +

Aktuell keine Disk erkannt.

)} +
- +
+ {jobsLoading ? (

Jobs werden geladen ...

) : dashboardJobs.length === 0 ? ( @@ -2815,54 +2505,372 @@ export default function DashboardPage({
)}
+
- +
+
-
- {device ? ( -
-
- Pfad: {device.path || '-'} + {audiobookUploadPhase !== 'idle' ? ( +
+
+ {audiobookUploadStatusLabel} +
-
- Modell: {device.model || '-'} -
-
- Disk-Label: {device.discLabel || '-'} -
-
- Laufwerks-Label: {device.label || '-'} -
-
- Mount: {device.mountpoint || '-'} + {audiobookUpload?.statusText ? {audiobookUpload.statusText} : null} + {audiobookUploadFileName ? ( + + Datei: {audiobookUploadFileName} + + ) : null} +
+ + + {audiobookUploadPhase === 'processing' + ? '100% | Upload fertig, Job wird vorbereitet ...' + : audiobookUploadTotalBytes > 0 + ? `${Math.round(audiobookUploadProgress)}% | ${formatBytes(audiobookUploadLoadedBytes)} / ${formatBytes(audiobookUploadTotalBytes)}` + : `${Math.round(audiobookUploadProgress)}%`} +
+ ) : null} + + {audiobookUploadFileName && audiobookUploadPhase === 'idle' + ? `Ausgewählt: ${audiobookUploadFileName}` + : 'Unterstützt im MVP: AAX-Upload. Danach erscheint ein eigener Audiobook-Startschritt mit Format- und Qualitätswahl.'} + + + + +
+ + + + {queueState?.cdBypassesQueue && } + 0 ? 'warning' : 'success'} /> + 0 ? 'warning' : 'success'} /> + 0 ? 'warning' : 'success'} /> +
+ +
+
+

Laufende Jobs

+ {queueRunningJobs.length === 0 ? ( + Keine laufenden Jobs. + ) : ( + queueRunningJobs.map((item) => { + const hasScriptSummary = hasQueueScriptSummary(item); + const detailKey = buildRunningQueueScriptKey(item?.jobId); + const detailsExpanded = hasScriptSummary && expandedQueueScriptKeys.has(detailKey); + return ( +
+
+
+ + #{item.jobId} | {item.title || `Job #${item.jobId}`} + {item.hasScripts ? : null} + {item.hasChains ? : null} + + {getStatusLabel(item.status)} +
+ {hasScriptSummary ? ( + + ) : null} +
+ {detailsExpanded ? : null} +
+ ); + }) + )} +
+
+
+

Warteschlange

+ +
+ {queuedJobs.length === 0 ? ( + Queue ist leer. + ) : ( + <> + {queuedJobs.map((item) => { + const entryId = Number(item?.entryId); + const isNonJob = item.type && item.type !== 'job'; + const isDragging = Number(draggingQueueEntryId) === entryId; + const hasScriptSummary = !isNonJob && hasQueueScriptSummary(item); + const detailKey = buildQueuedQueueScriptKey(entryId); + const detailsExpanded = hasScriptSummary && expandedQueueScriptKeys.has(detailKey); + return ( +
+
setDraggingQueueEntryId(entryId)} + onDragEnter={() => handleQueueDragEnter(entryId)} + onDragOver={(event) => event.preventDefault()} + onDrop={(event) => { + event.preventDefault(); + void handleQueueDrop(); + }} + onDragEnd={() => { + setDraggingQueueEntryId(null); + void syncQueueFromServer(); + }} + > + + + + +
+ {isNonJob ? ( + {item.position || '-'}. {queueEntryLabel(item)} + ) : ( + <> + + {item.position || '-'} | #{item.jobId} | {item.title || `Job #${item.jobId}`} + {item.hasScripts ? : null} + {item.hasChains ? : null} + + {item.actionLabel || item.action || '-'} | {getStatusLabel(item.status)} + + )} +
+
+ {hasScriptSummary ? ( + + ) : null} +
+
+ {detailsExpanded ? : null} + +
+ ); + })} + + )} +
+
+
+ + +
+ 0 ? 'warning' : 'success'} /> + + +
+ + {runtimeLoading && runtimeActiveItems.length === 0 && runtimeRecentItems.length === 0 ? ( +

Aktivitäten werden geladen ...

) : ( -

Aktuell keine Disk erkannt.

+
+
+

Aktiv

+ {runtimeActiveItems.length === 0 ? ( + Keine laufenden Skript-/Ketten-/Cron-Ausführungen. + ) : ( +
+ {runtimeActiveItems.map((item, index) => { + const statusMeta = runtimeStatusMeta(item?.status); + const canCancel = Boolean(item?.canCancel); + const canNextStep = String(item?.type || '').trim().toLowerCase() === 'chain' && Boolean(item?.canNextStep); + const cancelBusy = isRuntimeActionBusy(item?.id, 'cancel'); + const nextStepBusy = isRuntimeActionBusy(item?.id, 'next-step'); + return ( +
+
+ {item?.name || '-'} +
+ + +
+
+ + Quelle: {item?.source || '-'} + {item?.jobId ? ` | Job #${item.jobId}` : ''} + {item?.cronJobId ? ` | Cron #${item.cronJobId}` : ''} + + {item?.currentStep ? Schritt: {item.currentStep} : null} + {item?.currentScriptName ? Laufendes Skript: {item.currentScriptName} : null} + {item?.message ? {item.message} : null} + + Gestartet: {formatUpdatedAt(item?.startedAt)} + {canCancel || canNextStep ? ( +
+ {canNextStep ? ( +
+ ) : null} +
+ ); + })} +
+ )} +
+ +
+

Zuletzt abgeschlossen

+ {runtimeRecentItems.length === 0 ? ( + Keine abgeschlossenen Einträge vorhanden. + ) : ( +
+ {runtimeRecentItems.map((item, index) => { + const outcomeMeta = runtimeOutcomeMeta(item?.outcome, item?.status); + return ( +
+
+ {item?.name || '-'} +
+ + +
+
+ + Quelle: {item?.source || '-'} + {item?.jobId ? ` | Job #${item.jobId}` : ''} + {item?.cronJobId ? ` | Cron #${item.cronJobId}` : ''} + + {Number.isFinite(Number(item?.exitCode)) ? Exit-Code: {item.exitCode} : null} + {item?.message ? {item.message} : null} + {item?.errorMessage ? {item.errorMessage} : null} + {hasRuntimeOutputDetails(item) ? ( + + ) : null} + + Ende: {formatUpdatedAt(item?.finishedAt || item?.startedAt)} + {item?.durationMs != null ? ` | Dauer: ${formatDurationMs(item.durationMs)}` : ''} + +
+ ); + })} +
+ )} +
+
)}
+
+