diff --git a/backend/package-lock.json b/backend/package-lock.json
index c92b3a5..b6bcebc 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ripster-backend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-backend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"dependencies": {
"archiver": "^7.0.1",
"cors": "^2.8.5",
diff --git a/backend/package.json b/backend/package.json
index 11dd7dc..0d03e05 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "ripster-backend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"private": true,
"type": "commonjs",
"scripts": {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a1b7af4..8429477 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ripster-frontend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster-frontend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"dependencies": {
"primeicons": "^7.0.0",
"primereact": "^10.9.2",
diff --git a/frontend/package.json b/frontend/package.json
index 926b2d1..b4de584 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "ripster-frontend",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"private": true,
"type": "module",
"scripts": {
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 0f3c528..4ca2564 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -389,15 +389,6 @@ function App() {
outlined={location.pathname !== item.path}
/>
))}
-
@@ -471,6 +462,7 @@ function App() {
jobsRefreshToken={dashboardJobsRefreshToken}
pendingExpandedJobId={pendingDashboardJobId}
onPendingExpandedJobHandled={handleDashboardJobFocusConsumed}
+ downloadSummary={downloadSummary}
/>
}
/>
diff --git a/frontend/src/components/JobDetailDialog.jsx b/frontend/src/components/JobDetailDialog.jsx
index d5e4018..4642462 100644
--- a/frontend/src/components/JobDetailDialog.jsx
+++ b/frontend/src/components/JobDetailDialog.jsx
@@ -1,3 +1,5 @@
+import { useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import MediaInfoReviewPanel from './MediaInfoReviewPanel';
@@ -5,6 +7,7 @@ import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg';
import otherIndicatorIcon from '../assets/media-other.svg';
import { getStatusLabel } from '../utils/statusPresentation';
+import { api } from '../api/client';
const CD_FORMAT_LABELS = {
flac: 'FLAC',
@@ -478,6 +481,15 @@ export default function JobDetailDialog({
deleteEntryBusy = false,
downloadBusyTarget = null
}) {
+ const [expertMode, setExpertMode] = useState(false);
+ useEffect(() => {
+ api.getSettings().then((res) => {
+ const allSettings = (res?.categories || []).flatMap((c) => c.settings || []);
+ const val = allSettings.find((s) => s.key === 'ui_expert_mode')?.value;
+ setExpertMode(val === 'true' || val === true);
+ }).catch(() => {});
+ }, []);
+
const mkDone = Boolean(job?.ripSuccessful) || !job?.makemkvInfo || job?.makemkvInfo?.status === 'SUCCESS';
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(job?.status);
const showFinalLog = !running;
@@ -579,7 +591,7 @@ export default function JobDetailDialog({
modal
>
{!job ? null : (
- <>
+
{detailLoading ?
Details werden geladen ...
: null}
@@ -792,33 +804,48 @@ export default function JobDetailDialog({
Fehler: {job.error_message}
) : null}
+ {expertMode ? (
+
+
+ DB-Eintrag ansehen
+
+
+ ) : null}
- {!isCd && (hasConfiguredSelection || encodePlanUserPreset || executedHandBrakeCommand || job.handbrakeInfo?.preEncodeScripts?.configured > 0 || job.handbrakeInfo?.postEncodeScripts?.configured > 0) ? (
+ {!isCd && (hasConfiguredSelection || encodePlanUserPreset || job.encodePlan?.minLengthMinutes != null || job.encodePlan?.playlistRecommendation || job.handbrakeInfo?.preEncodeScripts?.configured > 0 || job.handbrakeInfo?.postEncodeScripts?.configured > 0) ? (
Encode-Konfiguration
- {(hasConfiguredSelection || encodePlanUserPreset) && !isAudiobook ? (
-
+
+ {job.encodePlan?.minLengthMinutes != null && !isAudiobook ? (
- Pre-Skripte: {configuredSelection.preScripts.length > 0 ? configuredSelection.preScripts.join(', ') : (configuredSelection.preChains.length > 0 ? configuredSelection.preChains.join(', ') : '-')}
+ Mindestlaufzeit: {job.encodePlan.minLengthMinutes} Min.
+ ) : null}
+ {job.encodePlan?.playlistRecommendation && !isAudiobook ? (
- Post-Skripte: {configuredSelection.postScripts.length > 0 ? configuredSelection.postScripts.join(', ') : (configuredSelection.postChains.length > 0 ? configuredSelection.postChains.join(', ') : '-')}
+ Playlist-Auswahl:{' '}
+ {job.encodePlan.playlistRecommendation.playlistFile || '-'}
+ {job.encodePlan.playlistRecommendation.reason ? ` — ${job.encodePlan.playlistRecommendation.reason}` : ''}
- {encodePlanUserPreset ? (
-
-
Preset: {encodePlanUserPreset.name || '-'}{encodePlanUserPreset.handbrakePreset ? ` · ${encodePlanUserPreset.handbrakePreset}` : ''}{encodePlanUserPreset.extraArgs ? ` · ${encodePlanUserPreset.extraArgs}` : ''}
+ ) : null}
+ {(hasConfiguredSelection || encodePlanUserPreset) && !isAudiobook ? (
+ <>
+
+ Pre-Skripte: {configuredSelection.preScripts.length > 0 ? configuredSelection.preScripts.join(', ') : (configuredSelection.preChains.length > 0 ? configuredSelection.preChains.join(', ') : '-')}
- ) : null}
-
- ) : null}
- {executedHandBrakeCommand ? (
-
-
{isAudiobook ? 'FFmpeg' : 'HandBrakeCLI'}:
-
{executedHandBrakeCommand}
-
- ) : null}
+
+ Post-Skripte: {configuredSelection.postScripts.length > 0 ? configuredSelection.postScripts.join(', ') : (configuredSelection.postChains.length > 0 ? configuredSelection.postChains.join(', ') : '-')}
+
+ {encodePlanUserPreset ? (
+
+ Preset: {encodePlanUserPreset.name || '-'}{encodePlanUserPreset.handbrakePreset ? ` · ${encodePlanUserPreset.handbrakePreset}` : ''}{encodePlanUserPreset.extraArgs ? ` · ${encodePlanUserPreset.extraArgs}` : ''}
+
+ ) : null}
+ >
+ ) : null}
+
{!isAudiobook && (job.handbrakeInfo?.preEncodeScripts?.configured > 0 || job.handbrakeInfo?.postEncodeScripts?.configured > 0) ? (
@@ -828,32 +855,37 @@ export default function JobDetailDialog({
) : null}
-
- {!isCd && !isAudiobook ? : null}
-
- {!isCd && !isAudiobook ? : null}
-
- {!isCd ? : null}
-
+
+ Rohdaten
+
+ {!isCd && !isAudiobook ? : null}
+
+ {!isCd && !isAudiobook ? : null}
+
+ {!isCd ? : null}
+
+
- {!isCd && !isAudiobook && job.encodePlan ? (
- <>
-
Mediainfo-Prüfung (Auswertung)
-
0
- ? Math.trunc(encodePlanUserPresetId)
- : null}
- />
- >
+ {!isCd && !isAudiobook && Array.isArray(job.encodePlan?.titles) && job.encodePlan.titles.length > 0 ? (
+
+ Titel
+
+ {job.encodePlan.titles.map((t) => {
+ const isSelected = t.id === job.encodePlan.encodeInputTitleId || t.selectedForEncode;
+ const sizeMb = t.sizeBytes > 0 ? `${(t.sizeBytes / 1024 / 1024).toFixed(0)} MB` : null;
+ return (
+
+ #{t.id}
+ {t.fileName || '-'}
+ {t.durationMinutes != null ? `${t.durationMinutes} Min.` : '-'}
+ {sizeMb ? {sizeMb} : null}
+ {t.playlistFile ? {t.playlistFile} : null}
+ {isSelected ? gewählt : null}
+
+ );
+ })}
+
+
) : null}
Log
@@ -1035,7 +1067,7 @@ export default function JobDetailDialog({
/>
) : null}
- >
+
)}
);
diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx
index fcfcd9b..12660de 100644
--- a/frontend/src/pages/DashboardPage.jsx
+++ b/frontend/src/pages/DashboardPage.jsx
@@ -821,7 +821,8 @@ export default function DashboardPage({
onAudiobookUpload,
jobsRefreshToken,
pendingExpandedJobId,
- onPendingExpandedJobHandled
+ onPendingExpandedJobHandled,
+ downloadSummary = null
}) {
const navigate = useNavigate();
const [busy, setBusy] = useState(false);
@@ -2694,7 +2695,7 @@ export default function DashboardPage({
-
+
0 ? 'warning' : 'success'} />
@@ -2852,6 +2853,27 @@ export default function DashboardPage({
)}
+
+
+
ZIP-Downloads
+
+ {downloadSummary?.activeCount > 0 ? (
+
+ ) : downloadSummary?.totalCount > 0 ? (
+ 0 ? `Fertig (${downloadSummary.failedCount} fehlgeschlagen)` : `${downloadSummary.totalCount} fertig`} severity={downloadSummary.failedCount > 0 ? 'danger' : 'success'} />
+ ) : (
+
+ )}
+
+
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
index 9cea254..da2575b 100644
--- a/frontend/src/styles/app.css
+++ b/frontend/src/styles/app.css
@@ -2588,11 +2588,42 @@ body {
grid-column: 1 / -1;
}
+.dashboard-downloads-row {
+ border-top: 1px solid var(--surface-border);
+ padding-top: 0.75rem;
+ margin-top: 0.5rem;
+}
+
+.dashboard-downloads-row h4 {
+ margin: 0 0 0.45rem;
+ font-size: 0.88rem;
+}
+
+.dashboard-downloads-meta {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
.job-meta-subtle {
font-size: 0.82rem;
color: var(--text-color-secondary);
}
+.job-db-link {
+ font-size: 0.82rem;
+ color: var(--text-color-secondary);
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+}
+.job-db-link:hover {
+ color: var(--primary-color);
+ text-decoration: underline;
+}
+
.job-meta-encode-config {
margin-bottom: 0.5rem;
}
@@ -2642,10 +2673,78 @@ body {
color: #46556a;
}
+.job-detail-body {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.job-detail-body > h4 {
+ margin: 0.25rem 0 0;
+}
+
.job-json-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 1rem;
+ gap: 0.55rem;
+}
+
+.job-titles-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.3rem;
+ font-size: 0.83rem;
+}
+
+.job-title-row {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ padding: 0.25rem 0.4rem;
+ border-radius: 0.3rem;
+ background: var(--surface-ground);
+ flex-wrap: wrap;
+}
+
+.job-title-row.job-title-row-selected {
+ background: color-mix(in srgb, var(--primary-color) 10%, transparent);
+ border: 1px solid color-mix(in srgb, var(--primary-color) 30%, transparent);
+}
+
+.job-title-id {
+ font-weight: 600;
+ color: var(--text-color-secondary);
+ min-width: 2rem;
+}
+
+.job-title-name {
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.job-title-duration,
+.job-title-size {
+ color: var(--text-color-secondary);
+ white-space: nowrap;
+}
+
+.job-title-playlist {
+ color: var(--text-color-secondary);
+ font-size: 0.78rem;
+ white-space: nowrap;
+}
+
+.job-title-selected-badge {
+ font-size: 0.72rem;
+ font-weight: 600;
+ background: var(--primary-color);
+ color: var(--primary-color-text);
+ padding: 0.1rem 0.4rem;
+ border-radius: 999px;
+ white-space: nowrap;
}
.json-box {
diff --git a/package-lock.json b/package-lock.json
index ea63338..dd51da7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ripster",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ripster",
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"devDependencies": {
"concurrently": "^9.1.2"
}
diff --git a/package.json b/package.json
index f0c3713..8592036 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ripster",
"private": true,
- "version": "0.10.2-21",
+ "version": "0.10.2-22",
"scripts": {
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
"dev:backend": "npm run dev --prefix backend",