final dev
This commit is contained in:
@@ -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)
|
- Pre- und Post-Encode-Ausführungen (Skripte und/oder Skript-Ketten)
|
||||||
- Pipeline-Queue mit Job- und Nicht-Job-Einträgen (`script`, `chain`, `wait`)
|
- Pipeline-Queue mit Job- und Nicht-Job-Einträgen (`script`, `chain`, `wait`)
|
||||||
- Cron-Jobs für Skripte/Ketten (inkl. Logs und manueller Auslösung)
|
- 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
|
- Historie mit Re-Encode, Review-Neustart, File-/Job-Löschung und Orphan-Import
|
||||||
- Hardware-Monitoring (CPU/RAM/GPU/Storage) im Dashboard
|
- Hardware-Monitoring (CPU/RAM/GPU/Storage) im Dashboard
|
||||||
|
|
||||||
@@ -190,6 +191,12 @@ ripster/
|
|||||||
- `POST /api/crons/:id/run`
|
- `POST /api/crons/:id/run`
|
||||||
- `POST /api/crons/validate-expression`
|
- `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
|
## Troubleshooting
|
||||||
|
|
||||||
- WebSocket verbindet nicht:
|
- WebSocket verbindet nicht:
|
||||||
|
|||||||
@@ -2172,6 +2172,18 @@ function isPathInsideDirectory(parentPath, candidatePath) {
|
|||||||
return candidate.startsWith(parentWithSep);
|
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) {
|
function isJobFinished(jobLike = null) {
|
||||||
const status = String(jobLike?.status || '').trim().toUpperCase();
|
const status = String(jobLike?.status || '').trim().toUpperCase();
|
||||||
const lastState = String(jobLike?.last_state || '').trim().toUpperCase();
|
const lastState = String(jobLike?.last_state || '').trim().toUpperCase();
|
||||||
@@ -9123,10 +9135,10 @@ class PipelineService extends EventEmitter {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasRawInput = Boolean(
|
const resolvedReviewInput = hasBluRayBackupStructure(resolvedReviewRawPath)
|
||||||
hasBluRayBackupStructure(resolvedReviewRawPath)
|
? { path: resolvedReviewRawPath }
|
||||||
|| findPreferredRawInput(resolvedReviewRawPath)
|
: findPreferredRawInput(resolvedReviewRawPath);
|
||||||
);
|
const hasRawInput = Boolean(resolvedReviewInput?.path);
|
||||||
if (!hasRawInput) {
|
if (!hasRawInput) {
|
||||||
let hasAnyRawEntries = false;
|
let hasAnyRawEntries = false;
|
||||||
try {
|
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();
|
const currentStatus = String(sourceJob.status || '').trim().toUpperCase();
|
||||||
if (['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(currentStatus)) {
|
if (['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(currentStatus)) {
|
||||||
const error = new Error(`Review-Neustart nicht möglich: Job ${jobId} ist noch aktiv (${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,
|
handbrake_info_json: null,
|
||||||
mediainfo_info_json: null,
|
mediainfo_info_json: null,
|
||||||
encode_plan_json: null,
|
encode_plan_json: null,
|
||||||
encode_input_path: null,
|
encode_input_path: normalizedReviewInputPath || null,
|
||||||
encode_review_confirmed: 0,
|
encode_review_confirmed: 0,
|
||||||
makemkv_info_json: nextMakemkvInfoJson
|
makemkv_info_json: nextMakemkvInfoJson
|
||||||
};
|
};
|
||||||
@@ -9202,6 +9227,13 @@ class PipelineService extends EventEmitter {
|
|||||||
jobUpdatePayload.raw_path = resolvedReviewRawPath;
|
jobUpdatePayload.raw_path = resolvedReviewRawPath;
|
||||||
}
|
}
|
||||||
await historyService.updateJob(jobId, jobUpdatePayload);
|
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(
|
await historyService.appendLog(
|
||||||
jobId,
|
jobId,
|
||||||
'USER_ACTION',
|
'USER_ACTION',
|
||||||
|
|||||||
@@ -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
|
## Reconnect-Verhalten
|
||||||
|
|||||||
@@ -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
|
## Weitere Services
|
||||||
|
|
||||||
- `scriptService.js` (CRUD + Test + Wrapper-Ausführung)
|
- `scriptService.js` (CRUD + Test + Wrapper-Ausführung)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ nav:
|
|||||||
- Settings API: api/settings.md
|
- Settings API: api/settings.md
|
||||||
- History API: api/history.md
|
- History API: api/history.md
|
||||||
- Cron API: api/crons.md
|
- Cron API: api/crons.md
|
||||||
|
- Runtime Activities API: api/runtime-activities.md
|
||||||
- WebSocket Events: api/websocket.md
|
- WebSocket Events: api/websocket.md
|
||||||
- Konfiguration:
|
- Konfiguration:
|
||||||
- configuration/index.md
|
- configuration/index.md
|
||||||
|
|||||||
Reference in New Issue
Block a user