0.15.0-2 Misc Fixes
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-backend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"dependencies": {
|
||||
"archiver": "^7.0.1",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
|
||||
@@ -770,6 +770,8 @@ async function migrateOutputTemplates(db) {
|
||||
async function removeDeprecatedSettings(db) {
|
||||
const deprecatedKeys = [
|
||||
'pushover_notify_disc_detected',
|
||||
'pushover_notify_cron_success',
|
||||
'pushover_notify_cron_error',
|
||||
'mediainfo_extra_args',
|
||||
'makemkv_rip_mode',
|
||||
'makemkv_analyze_extra_args',
|
||||
|
||||
@@ -591,6 +591,32 @@ function toProcessLogPath(jobId) {
|
||||
return path.join(getJobLogDir(), `job-${Math.trunc(normalizedId)}.process.log`);
|
||||
}
|
||||
|
||||
function buildArchivedProcessLogPath(sourceJobId, options = {}) {
|
||||
const normalizedId = Number(sourceJobId);
|
||||
if (!Number.isFinite(normalizedId) || normalizedId <= 0) {
|
||||
return null;
|
||||
}
|
||||
const targetJobId = Number(options?.replacementJobId);
|
||||
const safeReason = String(options?.reason || 'retired')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_-]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|| 'retired';
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const archiveDir = path.join(getJobLogDir(), 'archived');
|
||||
const targetSuffix = Number.isFinite(targetJobId) && targetJobId > 0
|
||||
? `.to-${Math.trunc(targetJobId)}`
|
||||
: '';
|
||||
return {
|
||||
archiveDir,
|
||||
archivePath: path.join(
|
||||
archiveDir,
|
||||
`job-${Math.trunc(normalizedId)}.process.${safeReason}${targetSuffix}.${timestamp}.log`
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
function hasProcessLogFile(jobId) {
|
||||
const filePath = toProcessLogPath(jobId);
|
||||
return Boolean(filePath && fs.existsSync(filePath));
|
||||
@@ -2944,7 +2970,17 @@ class HistoryService {
|
||||
}
|
||||
|
||||
await this.closeProcessLog(fromJobId);
|
||||
this._deleteProcessLogFile(fromJobId);
|
||||
const archivedProcessLogPath = this._archiveProcessLogFile(fromJobId, {
|
||||
replacementJobId: toJobId,
|
||||
reason
|
||||
});
|
||||
if (archivedProcessLogPath) {
|
||||
this.appendProcessLog(
|
||||
toJobId,
|
||||
'SYSTEM',
|
||||
`Vorheriger Prozess-Log von Job #${fromJobId} archiviert: ${archivedProcessLogPath}`
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn('job:retired', {
|
||||
sourceJobId: fromJobId,
|
||||
@@ -2957,7 +2993,8 @@ class HistoryService {
|
||||
retired: true,
|
||||
sourceJobId: fromJobId,
|
||||
replacementJobId: toJobId,
|
||||
reason
|
||||
reason,
|
||||
archivedProcessLogPath: archivedProcessLogPath || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6708,6 +6745,33 @@ class HistoryService {
|
||||
}
|
||||
}
|
||||
|
||||
_archiveProcessLogFile(jobId, options = {}) {
|
||||
const processLogPath = toProcessLogPath(jobId);
|
||||
if (!processLogPath || !fs.existsSync(processLogPath)) {
|
||||
return null;
|
||||
}
|
||||
const archiveTarget = buildArchivedProcessLogPath(jobId, options);
|
||||
if (!archiveTarget?.archivePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(archiveTarget.archiveDir, { recursive: true });
|
||||
fs.renameSync(processLogPath, archiveTarget.archivePath);
|
||||
return archiveTarget.archivePath;
|
||||
} catch (error) {
|
||||
logger.warn('job:process-log:archive-failed', {
|
||||
jobId,
|
||||
fromPath: processLogPath,
|
||||
toPath: archiveTarget.archivePath,
|
||||
reason: String(options?.reason || '').trim() || null,
|
||||
replacementJobId: Number(options?.replacementJobId) || null,
|
||||
error: error?.message || String(error)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteJobFiles(jobId, target = 'both', options = {}) {
|
||||
const allowedTargets = new Set(['raw', 'movie', 'both']);
|
||||
if (!allowedTargets.has(target)) {
|
||||
@@ -6992,9 +7056,12 @@ class HistoryService {
|
||||
movieCandidatePathForTracking = outputPath;
|
||||
} else {
|
||||
const parentDir = normalizeComparablePath(path.dirname(outputPath));
|
||||
const parentDirName = String(path.basename(parentDir || '') || '').trim();
|
||||
const isIncompleteMergeParentDir = /^incomplete_merge_.+_job_\d+\s*$/i.test(parentDirName);
|
||||
// Converter jobs output a single file — never delete the parent dir
|
||||
const isConverterJob = resolvedPaths.mediaType === 'converter';
|
||||
const canDeleteParentDir = !isConverterJob
|
||||
&& !isIncompleteMergeParentDir
|
||||
&& parentDir
|
||||
&& parentDir !== movieRoot
|
||||
&& isPathInside(movieRoot, parentDir)
|
||||
@@ -7388,11 +7455,26 @@ class HistoryService {
|
||||
};
|
||||
const collectIncompleteMoviePathsFromPreview = (preview) => {
|
||||
const rows = Array.isArray(preview?.pathCandidates?.movie) ? preview.pathCandidates.movie : [];
|
||||
const incompleteJobPattern = /(^|[\\/])incomplete_job-\d+([\\/]|$)/i;
|
||||
const incompleteMergePattern = /(^|[\\/])incomplete_merge_[^\\/]+_job_\d+([\\/]|$)/i;
|
||||
return rows
|
||||
.filter((row) => Boolean(row?.exists))
|
||||
.filter((row) => {
|
||||
const candidatePath = String(row?.path || '').trim();
|
||||
if (!candidatePath) {
|
||||
return false;
|
||||
}
|
||||
if (incompleteJobPattern.test(candidatePath)) {
|
||||
return true;
|
||||
}
|
||||
if (incompleteMergePattern.test(candidatePath)) {
|
||||
// Shared multipart merge folders must never be auto-selected as a whole directory.
|
||||
return Boolean(row?.isFile);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((row) => String(row?.path || '').trim())
|
||||
.filter((candidatePath) => Boolean(candidatePath))
|
||||
.filter((candidatePath) => /(^|[\\/])incomplete_job-\d+([\\/]|$)/i.test(candidatePath));
|
||||
.filter(Boolean);
|
||||
};
|
||||
const cleanupIncompleteMovieFoldersForJobs = async (jobRows = [], ownerJobIds = []) => {
|
||||
const normalizedOwnerJobIds = Array.from(new Set(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"dependencies": {
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -602,15 +602,15 @@ function App() {
|
||||
|
||||
<div
|
||||
className="app-upload-banner-progress"
|
||||
aria-label={`Audiobook Upload ${Math.round(uploadProgress)} Prozent`}
|
||||
aria-label={`Audiobook Upload ${Math.trunc(uploadProgress)} Prozent`}
|
||||
>
|
||||
<ProgressBar value={uploadProgress} showValue={false} />
|
||||
<small>
|
||||
{uploadPhase === 'processing'
|
||||
? `100% | ${uploadBytesLabel || 'Upload abgeschlossen'}`
|
||||
: uploadBytesLabel
|
||||
? `${Math.round(uploadProgress)}% | ${uploadBytesLabel}`
|
||||
: `${Math.round(uploadProgress)}%`}
|
||||
? `${Math.trunc(uploadProgress)}% | ${uploadBytesLabel}`
|
||||
: `${Math.trunc(uploadProgress)}%`}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -297,9 +297,9 @@ export default function AudiobookConfigPanel({
|
||||
</div>
|
||||
|
||||
{isRunning ? (
|
||||
<div className="ripper-job-row-progress" aria-label={`Audiobook Fortschritt ${Math.round(progress)}%`}>
|
||||
<div className="ripper-job-row-progress" aria-label={`Audiobook Fortschritt ${Math.trunc(progress)}%`}>
|
||||
<ProgressBar value={progress} showValue={false} />
|
||||
<small>{Math.round(progress)}%{currentChapter ? ` | Kapitel ${currentChapter.index}/${currentChapter.total}: ${currentChapter.title}` : ''}</small>
|
||||
<small>{Math.trunc(progress)}%{currentChapter ? ` | Kapitel ${currentChapter.index}/${currentChapter.total}: ${currentChapter.title}` : ''}</small>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -182,15 +182,15 @@ export default function AudiobookUploadPanel({
|
||||
) : null}
|
||||
<div
|
||||
className="ripper-job-row-progress audiobook-upload-progress"
|
||||
aria-label={`Audiobook Upload ${Math.round(progress)} Prozent`}
|
||||
aria-label={`Audiobook Upload ${Math.trunc(progress)} Prozent`}
|
||||
>
|
||||
<ProgressBar value={progress} showValue={false} />
|
||||
<small>
|
||||
{phase === 'processing'
|
||||
? '100% | Upload fertig, Job wird vorbereitet ...'
|
||||
: totalBytes > 0
|
||||
? `${Math.round(progress)}% | ${formatBytes(loadedBytes)} / ${formatBytes(totalBytes)}`
|
||||
: `${Math.round(progress)}%`}
|
||||
? `${Math.trunc(progress)}% | ${formatBytes(loadedBytes)} / ${formatBytes(totalBytes)}`
|
||||
: `${Math.trunc(progress)}%`}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,11 +115,8 @@ function formatProgressLabel(value) {
|
||||
return '0%';
|
||||
}
|
||||
const clamped = Math.max(0, Math.min(100, parsed));
|
||||
const rounded = Math.round(clamped * 10) / 10;
|
||||
if (Number.isInteger(rounded)) {
|
||||
return `${rounded}%`;
|
||||
}
|
||||
return `${rounded.toFixed(1)}%`;
|
||||
const whole = Math.trunc(clamped);
|
||||
return `${whole}%`;
|
||||
}
|
||||
|
||||
function normalizeTrackStageStatus(value) {
|
||||
@@ -617,7 +614,7 @@ export default function CdRipConfigPanel({
|
||||
}).length;
|
||||
const progress = Number(pipeline?.progress ?? 0);
|
||||
const clampedProgress = Math.max(0, Math.min(100, progress));
|
||||
const roundedProgress = Math.round(clampedProgress * 10) / 10;
|
||||
const wholeProgress = Math.trunc(clampedProgress);
|
||||
const eta = String(pipeline?.eta || '').trim();
|
||||
const statusText = String(pipeline?.statusText || '').trim();
|
||||
const stateLabel = getStatusLabel(state);
|
||||
@@ -793,12 +790,12 @@ export default function CdRipConfigPanel({
|
||||
{isRipping ? (
|
||||
<div className="progress-wrap">
|
||||
<ProgressBar
|
||||
value={roundedProgress}
|
||||
value={wholeProgress}
|
||||
showValue
|
||||
displayValueTemplate={(value) => formatProgressLabel(value)}
|
||||
/>
|
||||
<small>
|
||||
{`Fortschritt: ${formatProgressLabel(roundedProgress)} | ${livePhaseLabel} ${completedRipCount}/${selectedTrackRows.length || effectiveTrackRows.length} Rip | ${completedEncodeCount}/${selectedTrackRows.length || effectiveTrackRows.length} Encode`}
|
||||
{`Fortschritt: ${formatProgressLabel(wholeProgress)} | ${livePhaseLabel} ${completedRipCount}/${selectedTrackRows.length || effectiveTrackRows.length} Rip | ${completedEncodeCount}/${selectedTrackRows.length || effectiveTrackRows.length} Encode`}
|
||||
</small>
|
||||
<small>{eta ? `ETA ${eta}` : 'ETA unbekannt'}</small>
|
||||
</div>
|
||||
|
||||
@@ -1071,7 +1071,7 @@ export default function ConverterJobCard({
|
||||
const liveProgress = jobProgress?.progress ?? null;
|
||||
const liveEta = jobProgress?.eta || null;
|
||||
const liveStatusText = jobProgress?.statusText || null;
|
||||
const progressValue = liveProgress !== null ? Math.round(liveProgress) : null;
|
||||
const progressValue = liveProgress !== null ? Math.trunc(liveProgress) : null;
|
||||
|
||||
// ── Audio details (for active/terminal state) ─────────────────────────────
|
||||
const audioTracks = isAudio ? deriveAudioTracks(plan) : [];
|
||||
|
||||
@@ -125,7 +125,7 @@ export default function ConverterUploadPanel({ onUploaded, allowedExtensions = n
|
||||
// XHR für Upload-Fortschritt
|
||||
const result = await uploadWithProgress(formData, (pct) => {
|
||||
setProgress(pct);
|
||||
setStatusText(`Upload: ${Math.round(pct)}%`);
|
||||
setStatusText(`Upload: ${Math.trunc(pct)}%`);
|
||||
});
|
||||
|
||||
setPhase('done');
|
||||
|
||||
@@ -2001,7 +2001,7 @@ export default function JobDetailDialog({
|
||||
<span><strong>Titel-ID:</strong> {episode?.titleId || '-'}</span>
|
||||
</div>
|
||||
<div className="track-item">
|
||||
<span><strong>Fortschritt:</strong> {`${boundedProgress.toFixed(2)}%`}</span>
|
||||
<span><strong>Fortschritt:</strong> {`${Math.trunc(boundedProgress)}%`}</span>
|
||||
</div>
|
||||
<div className="track-item">
|
||||
<span><strong>Gestartet:</strong> {formatDateTimeOrDash(episode?.startedAt)}</span>
|
||||
|
||||
@@ -660,12 +660,12 @@ function normalizeSeriesBatchProgress(value, fallback = 0) {
|
||||
return Math.max(0, Math.min(100, parsed));
|
||||
}
|
||||
|
||||
function formatPercentTwoDecimals(value) {
|
||||
function formatPercentNoDecimals(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return '0.00%';
|
||||
return '0%';
|
||||
}
|
||||
return `${parsed.toFixed(2)}%`;
|
||||
return `${Math.trunc(Math.max(0, Math.min(100, parsed)))}%`;
|
||||
}
|
||||
|
||||
function getSeriesBatchTagSeverity(status) {
|
||||
@@ -2528,6 +2528,15 @@ export default function PipelineStatusCard({
|
||||
|
||||
const manualDecisionState = pipeline?.context?.manualDecisionState || null;
|
||||
const rawDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && manualDecisionState === 'waiting_for_raw_decision';
|
||||
const rawDecisionOptions = pipeline?.context?.rawDecisionOptions && typeof pipeline.context.rawDecisionOptions === 'object'
|
||||
? pipeline.context.rawDecisionOptions
|
||||
: null;
|
||||
const rawDecisionAllowsMultipartMergeWithOrphan = Boolean(
|
||||
rawDecisionRequiredBeforeStart
|
||||
&& rawDecisionOptions?.allowMultipartMergeWithOrphan
|
||||
);
|
||||
const rawDecisionOrphanDiscNumber = normalizePositiveInt(rawDecisionOptions?.orphanDiscNumber);
|
||||
const rawDecisionCurrentDiscNumber = normalizePositiveInt(rawDecisionOptions?.currentDiscNumber);
|
||||
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired && !rawDecisionRequiredBeforeStart;
|
||||
const isSeriesDvdReview = useMemo(() => {
|
||||
if (jobMediaProfile !== 'dvd' && jobMediaProfile !== 'bluray') {
|
||||
@@ -2867,7 +2876,7 @@ export default function PipelineStatusCard({
|
||||
: (pipeline?.eta || null);
|
||||
const primaryStatusText = hasSeriesBatchProgress
|
||||
? (
|
||||
`Serien-Encoding ${primaryProgressValue.toFixed(2)}%`
|
||||
`Serien-Encoding ${formatPercentNoDecimals(primaryProgressValue)}`
|
||||
+ `${runningSeriesEpisode?.title ? ` - ${runningSeriesEpisode.title}` : ''}`
|
||||
)
|
||||
: (pipeline?.statusText || 'Bereit');
|
||||
@@ -3365,7 +3374,7 @@ export default function PipelineStatusCard({
|
||||
<ProgressBar
|
||||
value={primaryProgressValue}
|
||||
showValue
|
||||
displayValueTemplate={(value) => formatPercentTwoDecimals(value)}
|
||||
displayValueTemplate={(value) => formatPercentNoDecimals(value)}
|
||||
/>
|
||||
<small>
|
||||
{primaryEta
|
||||
@@ -3404,7 +3413,7 @@ export default function PipelineStatusCard({
|
||||
<Tag value={getStatusLabel(child.status)} severity={getSeriesBatchTagSeverity(child.status)} />
|
||||
</div>
|
||||
<ProgressBar value={child.progress} showValue={false} style={{ height: '5px' }} />
|
||||
<small>{child.eta ? `${Math.round(child.progress)}% | ETA ${child.eta}` : `${Math.round(child.progress)}%`}</small>
|
||||
<small>{child.eta ? `${formatPercentNoDecimals(child.progress)} | ETA ${child.eta}` : `${formatPercentNoDecimals(child.progress)}`}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -3669,6 +3678,14 @@ export default function PipelineStatusCard({
|
||||
<small>
|
||||
Vorhandenes Raw zur Disk gefunden. Wie soll weiter verfahren werden?
|
||||
</small>
|
||||
{rawDecisionAllowsMultipartMergeWithOrphan ? (
|
||||
<small className="muted-inline mt-2">
|
||||
Optional: Orphan-RAW als eigene Disc in einen Multipart-Container uebernehmen
|
||||
{(rawDecisionOrphanDiscNumber || rawDecisionCurrentDiscNumber)
|
||||
? ` (Orphan Disc ${rawDecisionOrphanDiscNumber || '?'} | Laufwerk Disc ${rawDecisionCurrentDiscNumber || '?'})`
|
||||
: ''}.
|
||||
</small>
|
||||
) : null}
|
||||
<div className="actions-row mt-3">
|
||||
<Button
|
||||
label="mit RAW weiter machen"
|
||||
@@ -3677,6 +3694,16 @@ export default function PipelineStatusCard({
|
||||
onClick={() => onSubmitRawDecision?.(retryJobId, 'continue')}
|
||||
loading={busy}
|
||||
/>
|
||||
{rawDecisionAllowsMultipartMergeWithOrphan ? (
|
||||
<Button
|
||||
label="Multipart movie (Orphan + LW)"
|
||||
icon="pi pi-sitemap"
|
||||
severity="info"
|
||||
outlined
|
||||
onClick={() => onSubmitRawDecision?.(retryJobId, 'multipart_orphan_merge')}
|
||||
loading={busy}
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
label="RAW löschen und Rip erneut starten"
|
||||
icon="pi pi-trash"
|
||||
|
||||
@@ -56,6 +56,23 @@ const CD_FORMAT_LABELS = {
|
||||
ogg: 'Ogg Vorbis'
|
||||
};
|
||||
|
||||
const MULTIPART_CONTAINER_LIVE_STATUSES = new Set([
|
||||
'ANALYZING',
|
||||
'METADATA_SELECTION',
|
||||
'WAITING_FOR_USER_DECISION',
|
||||
'READY_TO_START',
|
||||
'MEDIAINFO_CHECK',
|
||||
'READY_TO_ENCODE',
|
||||
'RIPPING',
|
||||
'ENCODING',
|
||||
'POST_ENCODE_SCRIPTS',
|
||||
'CD_METADATA_SELECTION',
|
||||
'CD_READY_TO_RIP',
|
||||
'CD_ANALYZING',
|
||||
'CD_RIPPING',
|
||||
'CD_ENCODING'
|
||||
]);
|
||||
|
||||
function normalizePositiveInteger(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
@@ -1774,6 +1791,18 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
};
|
||||
|
||||
const renderStatusTag = (row) => {
|
||||
const normalizedStatus = normalizeStatus(row?.status || row?.last_state);
|
||||
const rowId = normalizeJobId(row?.id);
|
||||
const isQueued = Boolean(rowId && queuedJobIdSet.has(rowId));
|
||||
const renderDefaultStatusTag = () => (
|
||||
<div className="history-status-tag-wrap">
|
||||
<Tag
|
||||
value={getStatusLabel(row?.status || row?.last_state, { queued: isQueued })}
|
||||
severity={getStatusSeverity(normalizedStatus, { queued: isQueued })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isMultipartContainerHistoryRow(row)) {
|
||||
const mergeSummary = row?.seriesChildSummary?.merge && typeof row.seriesChildSummary.merge === 'object'
|
||||
? row.seriesChildSummary.merge
|
||||
@@ -1792,6 +1821,9 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (MULTIPART_CONTAINER_LIVE_STATUSES.has(normalizedStatus)) {
|
||||
return renderDefaultStatusTag();
|
||||
}
|
||||
if (mergeState === 'done' || isCompleted) {
|
||||
return (
|
||||
<div className="history-status-tag-wrap">
|
||||
@@ -1830,17 +1862,7 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const normalizedStatus = normalizeStatus(row?.status);
|
||||
const rowId = normalizeJobId(row?.id);
|
||||
const isQueued = Boolean(rowId && queuedJobIdSet.has(rowId));
|
||||
return (
|
||||
<div className="history-status-tag-wrap">
|
||||
<Tag
|
||||
value={getStatusLabel(row?.status, { queued: isQueued })}
|
||||
severity={getStatusSeverity(normalizedStatus, { queued: isQueued })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return renderDefaultStatusTag();
|
||||
};
|
||||
|
||||
const renderPoster = (row, className = 'history-dv-poster') => {
|
||||
|
||||
@@ -87,7 +87,16 @@ function isIncompleteOutputPath(outputPath) {
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(^|\/)incomplete_job-\d+(\/|$)/i.test(normalized);
|
||||
return /(^|\/)incomplete_job-\d+(\/|$)/i.test(normalized)
|
||||
|| /(^|\/)incomplete_merge_[^/]+_job_\d+(\/|$)/i.test(normalized);
|
||||
}
|
||||
|
||||
function isIncompleteMergeOutputPath(outputPath) {
|
||||
const normalized = normalizePathForMatch(outputPath);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(^|\/)incomplete_merge_[^/]+_job_\d+(\/|$)/i.test(normalized);
|
||||
}
|
||||
|
||||
function getIncompleteOutputSelectionPaths(outputPath) {
|
||||
@@ -96,20 +105,28 @@ function getIncompleteOutputSelectionPaths(outputPath) {
|
||||
return [];
|
||||
}
|
||||
const segments = normalized.split('/').filter(Boolean);
|
||||
const incompleteIndex = segments.findIndex((segment) => /^incomplete_job-\d+$/i.test(segment));
|
||||
if (incompleteIndex < 0) {
|
||||
return [];
|
||||
const incompleteJobIndex = segments.findIndex((segment) => /^incomplete_job-\d+$/i.test(segment));
|
||||
if (incompleteJobIndex >= 0) {
|
||||
const folderPath = `/${segments.slice(0, incompleteJobIndex + 1).join('/')}`;
|
||||
return Array.from(new Set([normalized, folderPath]));
|
||||
}
|
||||
const folderPath = `/${segments.slice(0, incompleteIndex + 1).join('/')}`;
|
||||
return Array.from(new Set([normalized, folderPath]));
|
||||
const incompleteMergeIndex = segments.findIndex((segment) => /^incomplete_merge_[^/]+_job_\d+$/i.test(segment));
|
||||
if (incompleteMergeIndex >= 0) {
|
||||
const mergeFolderPath = `/${segments.slice(0, incompleteMergeIndex + 1).join('/')}`;
|
||||
const normalizedLooksLikeFolder = incompleteMergeIndex === (segments.length - 1);
|
||||
return normalizedLooksLikeFolder
|
||||
? [mergeFolderPath]
|
||||
: [normalized];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function formatPercent(value, digits = 1) {
|
||||
function formatPercent(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return 'n/a';
|
||||
}
|
||||
return `${parsed.toFixed(digits)}%`;
|
||||
return `${Math.trunc(parsed)}%`;
|
||||
}
|
||||
|
||||
function formatTemperature(value) {
|
||||
@@ -2338,6 +2355,12 @@ export default function RipperPage({
|
||||
incompleteMovieSelectionPaths = Array.from(new Set(
|
||||
movieCandidates
|
||||
.filter((candidate) => Boolean(candidate?.exists) && isIncompleteOutputPath(candidate?.path))
|
||||
.filter((candidate) => {
|
||||
if (!isIncompleteMergeOutputPath(candidate?.path)) {
|
||||
return true;
|
||||
}
|
||||
return Boolean(candidate?.isFile);
|
||||
})
|
||||
.map((candidate) => String(candidate?.path || '').trim())
|
||||
.filter(Boolean)
|
||||
));
|
||||
@@ -3384,7 +3407,7 @@ export default function RipperPage({
|
||||
const clampedProgress = Number.isFinite(rawProgress)
|
||||
? Math.max(0, Math.min(100, rawProgress))
|
||||
: 0;
|
||||
const progressLabel = `${Math.round(clampedProgress)}%`;
|
||||
const progressLabel = `${Math.trunc(clampedProgress)}%`;
|
||||
const etaLabel = isCancelledState ? '' : String(
|
||||
hasSeriesBatchProgress
|
||||
? (runningSeriesChild?.eta || pipelineForJob?.eta || '')
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster",
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.2"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"private": true,
|
||||
"version": "0.15.0-1",
|
||||
"version": "0.15.0-2",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
||||
"dev:backend": "npm run dev --prefix backend",
|
||||
|
||||
Reference in New Issue
Block a user