diff --git a/README.md b/README.md index 8a020b1..ec0da04 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Ripster ist eine lokale Web-Anwendung für halbautomatisches Disc-Ripping mit Ma - Pre- und Post-Encode-Ausführungen (Skripte und/oder Skript-Ketten) - Pipeline-Queue mit Job- und Nicht-Job-Einträgen (`script`, `chain`, `wait`) - Cron-Jobs für Skripte/Ketten (inkl. Logs und manueller Auslösung) +- **Aktivitäts-Tracking**: Laufende und abgeschlossene Aktionen (Skripte, Ketten, Cron, Tasks) in Echtzeit im Dashboard - Historie mit Re-Encode, Review-Neustart, File-/Job-Löschung und Orphan-Import - Hardware-Monitoring (CPU/RAM/GPU/Storage) im Dashboard @@ -190,6 +191,12 @@ ripster/ - `POST /api/crons/:id/run` - `POST /api/crons/validate-expression` +**Runtime-Aktivitäten** +- `GET /api/activities` +- `POST /api/activities/:id/cancel` +- `POST /api/activities/:id/next-step` +- `POST /api/activities/clear-recent` + ## Troubleshooting - WebSocket verbindet nicht: diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js index 00ec432..6f186bf 100644 --- a/backend/src/services/pipelineService.js +++ b/backend/src/services/pipelineService.js @@ -2172,6 +2172,18 @@ function isPathInsideDirectory(parentPath, candidatePath) { return candidate.startsWith(parentWithSep); } +function isEncodeInputMismatchedWithRaw(rawPath, encodeInputPath) { + const raw = normalizeComparablePath(rawPath); + const input = normalizeComparablePath(encodeInputPath); + if (!raw || !input) { + return true; + } + if (raw === input) { + return false; + } + return !isPathInsideDirectory(raw, input); +} + function isJobFinished(jobLike = null) { const status = String(jobLike?.status || '').trim().toUpperCase(); const lastState = String(jobLike?.last_state || '').trim().toUpperCase(); @@ -9123,10 +9135,10 @@ class PipelineService extends EventEmitter { throw error; } - const hasRawInput = Boolean( - hasBluRayBackupStructure(resolvedReviewRawPath) - || findPreferredRawInput(resolvedReviewRawPath) - ); + const resolvedReviewInput = hasBluRayBackupStructure(resolvedReviewRawPath) + ? { path: resolvedReviewRawPath } + : findPreferredRawInput(resolvedReviewRawPath); + const hasRawInput = Boolean(resolvedReviewInput?.path); if (!hasRawInput) { let hasAnyRawEntries = false; try { @@ -9146,6 +9158,19 @@ class PipelineService extends EventEmitter { ); } + const existingEncodeInputPath = String(sourceJob.encode_input_path || '').trim() || null; + const shouldRealignEncodeInput = Boolean( + resolvedReviewInput?.path + && ( + !existingEncodeInputPath + || !fs.existsSync(existingEncodeInputPath) + || isEncodeInputMismatchedWithRaw(resolvedReviewRawPath, existingEncodeInputPath) + ) + ); + const normalizedReviewInputPath = shouldRealignEncodeInput + ? resolvedReviewInput.path + : existingEncodeInputPath; + const currentStatus = String(sourceJob.status || '').trim().toUpperCase(); if (['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(currentStatus)) { const error = new Error(`Review-Neustart nicht möglich: Job ${jobId} ist noch aktiv (${currentStatus}).`); @@ -9194,7 +9219,7 @@ class PipelineService extends EventEmitter { handbrake_info_json: null, mediainfo_info_json: null, encode_plan_json: null, - encode_input_path: null, + encode_input_path: normalizedReviewInputPath || null, encode_review_confirmed: 0, makemkv_info_json: nextMakemkvInfoJson }; @@ -9202,6 +9227,13 @@ class PipelineService extends EventEmitter { jobUpdatePayload.raw_path = resolvedReviewRawPath; } await historyService.updateJob(jobId, jobUpdatePayload); + if (shouldRealignEncodeInput) { + await historyService.appendLog( + jobId, + 'SYSTEM', + `Review-Neustart: Encode-Input auf aktuellen RAW-Pfad abgeglichen: ${existingEncodeInputPath || '-'} -> ${normalizedReviewInputPath}` + ); + } await historyService.appendLog( jobId, 'USER_ACTION', diff --git a/docs/api/websocket.md b/docs/api/websocket.md index 13b3b4c..3fd802e 100644 --- a/docs/api/websocket.md +++ b/docs/api/websocket.md @@ -204,6 +204,44 @@ Laufzeitstatus eines Cron-Jobs geändert. } ``` +### RUNTIME_ACTIVITY_CHANGED + +Vollständiger Snapshot aller laufenden und kürzlich abgeschlossenen Aktivitäten. + +Wird ausgelöst, wenn eine Aktivität gestartet, aktualisiert oder abgeschlossen wird sowie nach `clear-recent`. + +```json +{ + "type": "RUNTIME_ACTIVITY_CHANGED", + "payload": { + "active": [ + { + "id": 7, + "type": "chain", + "name": "Post-Encode Aufräumen", + "status": "running", + "source": "cron", + "message": "Schritt 2 von 3", + "currentStep": "cleanup.sh", + "currentStepType": "script", + "stepIndex": 2, + "stepTotal": 3, + "canCancel": true, + "canNextStep": false, + "outcome": "running", + "startedAt": "2026-03-10T10:00:00.000Z", + "finishedAt": null, + "durationMs": null + } + ], + "recent": [], + "updatedAt": "2026-03-10T10:00:05.000Z" + } +} +``` + +Vollständige Feldbeschreibung: [Runtime Activities API](runtime-activities.md). + --- ## Reconnect-Verhalten diff --git a/docs/architecture/backend.md b/docs/architecture/backend.md index 02963de..21f092b 100644 --- a/docs/architecture/backend.md +++ b/docs/architecture/backend.md @@ -91,6 +91,31 @@ Features: --- +## `runtimeActivityService.js` + +In-Memory-Tracking aller laufenden und kürzlich abgeschlossenen Aktivitäten (Skripte, Ketten, Cron-Jobs, Tasks). + +Features: + +- `startActivity(type, payload)` → Aktivität registrieren, ID zurückgeben +- `updateActivity(id, patch)` → Laufende Aktivität aktualisieren +- `completeActivity(id, payload)` → Aktivität abschließen und in `recent` verschieben +- `setControls(id, { cancel, nextStep })` → Steuer-Handler registrieren (für `canCancel`/`canNextStep`) +- `requestCancel(id)` / `requestNextStep(id)` → Steuer-Handler aufrufen +- `clearRecent()` → Abgeschlossene Aktivitäten löschen +- `getSnapshot()` → Snapshot mit `active` + `recent` + `updatedAt` +- Broadcasts `RUNTIME_ACTIVITY_CHANGED` über WebSocket bei jeder Änderung + +Limits: + +- `recent` max. 120 Einträge +- `stdout`/`stderr`/`output` max. 12.000 Zeichen +- `message`/`errorMessage` max. 2.000 Zeichen + +Vollständige API-Dokumentation: [Runtime Activities API](../api/runtime-activities.md) + +--- + ## Weitere Services - `scriptService.js` (CRUD + Test + Wrapper-Ausführung) diff --git a/mkdocs.yml b/mkdocs.yml index 089778d..006f501 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,6 +69,7 @@ nav: - Settings API: api/settings.md - History API: api/history.md - Cron API: api/crons.md + - Runtime Activities API: api/runtime-activities.md - WebSocket Events: api/websocket.md - Konfiguration: - configuration/index.md