final dev

This commit is contained in:
2026-03-11 13:54:56 +00:00
parent 0f0cfffa7a
commit 3b66ca64f8
5 changed files with 108 additions and 5 deletions

View File

@@ -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:

View File

@@ -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',

View File

@@ -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

View File

@@ -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)

View File

@@ -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