0.16.1-1 release snapshot

This commit is contained in:
2026-04-29 08:35:24 +00:00
parent 6115090da1
commit 901a3b1ad7
269 changed files with 127336 additions and 1 deletions
+290
View File
@@ -0,0 +1,290 @@
# Converter API
Endpunkte für den Datei-Converter: Datei-Explorer, Upload, Job-Verwaltung.
Basis-Pfad: `/api/converter`
---
## Scan & Explorer
### `GET /api/converter/tree`
Vollständiger Verzeichnisbaum des `converter_raw_dir` (FS-basiert, keine DB).
**Response:**
```json
{
"tree": [
{
"name": "filme",
"relPath": "filme",
"isDir": true,
"children": [
{
"name": "film.mkv",
"relPath": "filme/film.mkv",
"isDir": false,
"size": 10485760
}
]
}
]
}
```
---
### `GET /api/converter/browse?parent=relPath`
DB-basierter Datei-Explorer. Gibt Einträge für einen Unterordner zurück.
**Query-Parameter:**
| Parameter | Typ | Beschreibung |
|---|---|---|
| `parent` | string | Relativer Pfad zum Unterordner (leer = Root) |
**Response:**
```json
{
"entries": [
{
"id": 1,
"rel_path": "filme/film.mkv",
"is_dir": false,
"size": 10485760,
"job_id": null
}
],
"rawDir": "/data/output/converter-raw"
}
```
---
### `POST /api/converter/scan`
Manuellen Scan des `converter_raw_dir` auslösen. Aktualisiert `converter_scan_entries` in der DB.
**Response:**
```json
{ "result": { "added": 3, "removed": 1 } }
```
---
## Jobs erstellen
### `POST /api/converter/jobs/from-selection`
Jobs aus im Datei-Explorer ausgewählten Dateien erstellen.
**Body:**
```json
{
"relPaths": ["filme/film.mkv", "musik/track.flac"],
"audioMode": "individual"
}
```
| Feld | Typ | Beschreibung |
|---|---|---|
| `relPaths` | string[] | Relative Pfade der ausgewählten Dateien |
| `audioMode` | string | `individual` (ein Job pro Datei) oder `shared` (ein gemeinsamer Job) |
**Response:**
```json
{ "jobs": [{ "id": 42, "status": "READY_TO_START" }] }
```
---
### `POST /api/converter/create-jobs`
Jobs aus DB-Scan-Einträgen erstellen.
**Body:**
```json
{
"entries": [
{ "relPath": "filme/film.mkv", "converterMediaType": "video" }
]
}
```
---
### `POST /api/converter/upload`
Dateien hochladen (Multipart, max. 50 Dateien).
**Form-Felder:**
| Feld | Typ | Beschreibung |
|---|---|---|
| `files` | File[] | Hochzuladende Dateien |
| `folderName` | string | (Optional) Ziel-Unterordner |
**Response:**
```json
{
"folders": [{ "folderRelPath": "upload-2026-03-30", "fileCount": 2 }]
}
```
---
## Job-Verwaltung
### `GET /api/converter/jobs`
Alle Converter-Jobs zurückgeben.
**Response:**
```json
{ "jobs": [{ "id": 42, "status": "READY_TO_START", "title": "film.mkv" }] }
```
---
### `GET /api/converter/jobs/:jobId`
Einzelnen Converter-Job abrufen.
---
### `POST /api/converter/jobs/:jobId/config`
Konfigurationsentwurf für einen Job speichern (persistiert in `encode_plan_json`).
**Body:** Partielles Konfig-Objekt (Ausgabeformat, Presets, Metadaten, Track-Auswahl).
---
### `POST /api/converter/jobs/:jobId/assign-files`
Dateien einem bestehenden (noch nicht gestarteten) Job hinzufügen.
**Body:**
```json
{ "relPaths": ["musik/track2.flac"] }
```
---
### `POST /api/converter/jobs/:jobId/remove-file`
Datei aus einem Job entfernen (per relativer Pfad).
**Body:**
```json
{ "relPath": "musik/track2.flac" }
```
---
### `POST /api/converter/jobs/:jobId/remove-input`
Datei aus einem Job entfernen (per absolutem Eingabepfad).
**Body:**
```json
{ "inputPath": "/data/output/converter-raw/musik/track2.flac" }
```
---
### `POST /api/converter/jobs/:jobId/start`
Job mit finaler Konfiguration starten.
**Body:**
```json
{
"converterMediaType": "video",
"outputFormat": "mkv",
"userPreset": "H.264 MKV 1080p30",
"trackSelection": {},
"handBrakeTitleId": 1,
"audioFormatOptions": {}
}
```
---
### `POST /api/converter/jobs/:jobId/cancel`
Laufenden Job abbrechen.
---
### `DELETE /api/converter/jobs/:jobId`
Job aus der DB löschen.
---
## Datei-Operationen
Alle Datei-Operationen arbeiten direkt auf dem Dateisystem (ohne DB-Aktualisierung). Ein anschließender Scan-Aufruf synchronisiert die DB.
### `DELETE /api/converter/files`
Datei oder Ordner löschen.
**Body:**
```json
{ "relPath": "filme/film.mkv" }
```
---
### `POST /api/converter/files/rename`
Datei oder Ordner umbenennen.
**Body:**
```json
{ "relPath": "filme/film.mkv", "newName": "film-neu.mkv" }
```
---
### `POST /api/converter/files/move`
Datei oder Ordner verschieben.
**Body:**
```json
{ "relPath": "filme/film.mkv", "targetParentRelPath": "archiv" }
```
`targetParentRelPath = ""` verschiebt in das Root-Verzeichnis.
---
### `POST /api/converter/files/folder`
Neuen Ordner anlegen.
**Body:**
```json
{ "parentRelPath": "filme", "name": "neu" }
```
+182
View File
@@ -0,0 +1,182 @@
# Cron API
Ripster enthält ein eingebautes Cron-System für Skripte und Skript-Ketten (`sourceType: script|chain`).
---
## GET /api/crons
Listet alle Cron-Jobs.
```json
{
"jobs": [
{
"id": 1,
"name": "Nachtlauf Backup",
"cronExpression": "0 2 * * *",
"sourceType": "script",
"sourceId": 3,
"sourceName": "Backup-Skript",
"enabled": true,
"pushoverEnabled": true,
"lastRunAt": "2026-03-10T02:00:00.000Z",
"lastRunStatus": "success",
"nextRunAt": "2026-03-11T02:00:00.000Z",
"createdAt": "2026-03-01T10:00:00.000Z",
"updatedAt": "2026-03-10T02:00:05.000Z"
}
]
}
```
---
## POST /api/crons
Erstellt Cron-Job.
```json
{
"name": "Nachtlauf Backup",
"cronExpression": "0 2 * * *",
"sourceType": "script",
"sourceId": 3,
"enabled": true,
"pushoverEnabled": true
}
```
Response: `201` mit `{ "job": { ... } }`
---
## GET /api/crons/:id
Response:
```json
{ "job": { "id": 1, "name": "..." } }
```
---
## PUT /api/crons/:id
Aktualisiert Cron-Job. Felder wie bei `POST`.
Response:
```json
{ "job": { ... } }
```
---
## DELETE /api/crons/:id
Response:
```json
{ "removed": { "id": 1, "name": "Nachtlauf Backup" } }
```
---
## GET /api/crons/:id/logs
Liefert Ausführungs-Logs.
**Query-Parameter:**
| Parameter | Typ | Default | Beschreibung |
|-----------|-----|---------|-------------|
| `limit` | number | `20` | Anzahl Einträge, max. `100` |
**Response:**
```json
{
"logs": [
{
"id": 42,
"cronJobId": 1,
"startedAt": "2026-03-10T02:00:01.000Z",
"finishedAt": "2026-03-10T02:00:05.000Z",
"status": "success",
"output": "Backup abgeschlossen.",
"errorMessage": null
}
]
}
```
`status`: `running` | `success` | `error`
---
## POST /api/crons/:id/run
Triggert Job manuell (asynchron).
**Response:**
```json
{ "triggered": true, "cronJobId": 1 }
```
Wenn Job bereits läuft: `409`.
---
## POST /api/crons/validate-expression
Validiert 5-Felder-Cron-Ausdruck und berechnet nächsten Lauf.
**Request:**
```json
{ "cronExpression": "*/15 * * * *" }
```
**Gültige Response:**
```json
{
"valid": true,
"nextRunAt": "2026-03-10T14:15:00.000Z"
}
```
**Ungültige Response:**
```json
{
"valid": false,
"error": "Cron-Ausdruck muss genau 5 Felder haben (Minute Stunde Tag Monat Wochentag).",
"nextRunAt": null
}
```
---
## Cron-Format
Ripster unterstützt 5 Felder:
```text
Minute Stunde Tag Monat Wochentag
```
Beispiele:
- `0 2 * * *` täglich 02:00
- `*/15 * * * *` alle 15 Minuten
- `0 6 * * 1-5` Mo-Fr 06:00
---
## WebSocket-Events zu Cron
- `CRON_JOBS_UPDATED` bei Create/Update/Delete
- `CRON_JOB_UPDATED` bei Laufzeitstatus (`running` -> `success|error`)
+143
View File
@@ -0,0 +1,143 @@
# Downloads API
Endpunkte für die Download-Queue. Ausgabedateien aus der Job-Historie können als ZIP heruntergeladen werden.
Basis-Pfad: `/api/downloads`
---
## `GET /api/downloads`
Liste aller Download-Einträge plus Zusammenfassung.
**Response:**
```json
{
"items": [
{
"id": "dl-42-raw",
"jobId": 42,
"target": "raw",
"status": "ready",
"archiveName": "Job_42_raw.zip",
"outputPath": null,
"createdAt": "2026-03-30T10:00:00.000Z",
"updatedAt": "2026-03-30T10:01:00.000Z",
"errorMessage": null
}
],
"summary": {
"total": 3,
"pending": 1,
"processing": 0,
"ready": 1,
"failed": 1
}
}
```
**Status-Werte:**
| Status | Beschreibung |
|---|---|
| `pending` | In der Queue, noch nicht gestartet |
| `processing` | ZIP wird gerade erstellt |
| `ready` | ZIP fertig, Download verfügbar |
| `failed` | Fehler bei der Erstellung |
---
## `GET /api/downloads/summary`
Nur die Zusammenfassung der Download-Queue (ohne Item-Liste).
**Response:**
```json
{
"summary": {
"total": 3,
"pending": 1,
"processing": 0,
"ready": 1,
"failed": 1
}
}
```
---
## `POST /api/downloads/history/:jobId`
Job-Ausgabe in die Download-Queue einreihen.
**Parameter:**
| Parameter | Typ | Beschreibung |
|---|---|---|
| `jobId` | number | ID des Jobs in der Historie |
**Body:**
```json
{
"target": "raw",
"outputPath": null
}
```
| Feld | Typ | Default | Beschreibung |
|---|---|---|---|
| `target` | string | `raw` | `raw` (RAW-Dateien) oder `movie` (Ausgabedateien) |
| `outputPath` | string | `null` | Optionaler expliziter Ausgabepfad |
**Response (201 Created):**
```json
{
"created": true,
"id": "dl-42-raw",
"status": "pending",
"summary": { "total": 1, "pending": 1, "processing": 0, "ready": 0, "failed": 0 }
}
```
Ist der Eintrag bereits vorhanden, wird `201` durch `200` ersetzt und `created: false` zurückgegeben.
---
## `GET /api/downloads/:id/file`
Fertige ZIP-Datei herunterladen.
**Parameter:**
| Parameter | Typ | Beschreibung |
|---|---|---|
| `id` | string | Download-ID (z. B. `dl-42-raw`) |
**Response:** Datei-Download (`Content-Disposition: attachment`).
Schlägt fehl mit `404`, wenn kein Download-Eintrag mit dieser ID existiert oder die Datei noch nicht bereit ist.
---
## `DELETE /api/downloads/:id`
Download-Eintrag löschen (auch fertige ZIP-Dateien werden vom Dateisystem entfernt).
**Response:**
```json
{
"ok": true,
"summary": { "total": 2, "pending": 0, "processing": 0, "ready": 2, "failed": 0 }
}
```
---
## WebSocket
Statusänderungen werden über `DOWNLOADS_UPDATED` in Echtzeit gemeldet. Vollständige Dokumentation: [WebSocket Events](websocket.md#downloads_updated).
+229
View File
@@ -0,0 +1,229 @@
# History API
Endpunkte für Job-Historie, Metadaten-Nachpflege, Orphan-Import und Löschoperationen.
---
## GET /api/history
Liefert Jobs mit optionalen Filtern.
**Query-Parameter:**
| Parameter | Typ | Beschreibung |
|----------|-----|-------------|
| `status` | string | Einzelstatus-Filter (legacy-kompatibel). |
| `statuses` | string | Mehrfachstatus als CSV, z. B. `FINISHED,ERROR`. |
| `search` | string | Suche in Titel-/IMDb-Feldern. |
| `limit` | number | Maximale Anzahl Ergebnisse. |
| `lite` | bool | Ohne Dateisystem-Checks (schneller). |
| `includeChildren` | bool | Child-Jobs (z. B. Serien-/Multipart-Kinder) explizit einbeziehen. |
**Beispiel:**
```text
GET /api/history?statuses=FINISHED,ERROR&search=Inception&limit=50&lite=1
```
**Response (Beispiel):**
```json
{
"jobs": [
{
"id": 42,
"status": "FINISHED",
"title": "Inception",
"job_kind": "bluray",
"raw_path": "/mnt/raw/Inception - RAW - job-42",
"output_path": "/mnt/movies/Inception (2010)/Inception (2010).mkv",
"mediaType": "bluray",
"ripSuccessful": true,
"encodeSuccess": true,
"created_at": "2026-03-10T08:00:00.000Z",
"updated_at": "2026-03-10T10:00:00.000Z"
}
]
}
```
---
## GET /api/history/:id
Liefert Job-Detail.
**Query-Parameter:**
| Parameter | Typ | Standard | Beschreibung |
|----------|-----|---------|-------------|
| `includeLogs` | bool | `false` | Prozesslog laden. |
| `includeLiveLog` | bool | `false` | Live-/Tail-Log laden. |
| `includeAllLogs` | bool | `false` | vollständiges Log statt Tail. |
| `logTailLines` | number | `800` | Tail-Länge falls nicht `includeAllLogs`. |
| `lite` | bool | `false` | Detailantwort ohne FS-Checks. |
**Response (Beispiel):**
```json
{
"job": {
"id": 42,
"status": "FINISHED",
"makemkvInfo": {},
"mediainfoInfo": {},
"handbrakeInfo": {},
"encodePlan": {},
"log": "...",
"log_count": 1,
"logMeta": {
"loaded": true,
"total": 800,
"returned": 800,
"truncated": true
}
}
}
```
---
## GET /api/history/orphan-raw
Sucht RAW-Ordner ohne zugehörigen Job.
## POST /api/history/orphan-raw/import
Importiert einen RAW-Ordner als Job und triggert optional die Analyse des Imports.
**Request:**
```json
{ "rawPath": "/mnt/raw/Inception (2010) [tt1375666] - RAW - job-99" }
```
**Response (Beispiel):**
```json
{
"job": { "id": 77, "status": "FINISHED" },
"activation": { "started": true },
"activationError": null
}
```
---
## POST /api/history/:id/omdb/assign
Weist OMDb-/Filmdaten nachträglich zu.
## POST /api/history/:id/cd/assign
Weist CD-Metadaten (z. B. MusicBrainz) nachträglich zu.
## POST /api/history/:id/error/ack
Quittiert einen Fehlerzustand im Historieneintrag.
---
## GET /api/history/:id/delete-preview
Liefert eine Vorschau der löschbaren Pfade (inkl. Scope auf verknüpfte Jobs).
**Query-Parameter:**
| Parameter | Typ | Standard | Beschreibung |
|----------|-----|---------|-------------|
| `includeRelated` | bool | `true` | Verknüpfte Jobs in die Vorschau einbeziehen. |
**Response (Beispiel):**
```json
{
"preview": {
"jobId": 42,
"relatedJobs": [{ "id": 42 }, { "id": 73 }],
"pathCandidates": {
"raw": [{ "path": "/mnt/raw/...", "exists": true, "isDirectory": true }],
"movie": [{ "path": "/mnt/movies/...", "exists": true, "isDirectory": true }]
}
}
}
```
---
## POST /api/history/:id/delete-files
Löscht Dateien eines Jobs, behält DB-Eintrag.
**Request:**
```json
{
"target": "both",
"includeRelated": true,
"selectedRawPaths": ["/mnt/raw/Inception - RAW - Disc1"],
"selectedMoviePaths": ["/mnt/movies/Inception (2010)"]
}
```
`target`: `raw` | `movie` | `both`
`selectedRawPaths` / `selectedMoviePaths` sind optional und begrenzen die Löschung auf ausgewählte Pfade aus der Vorschau.
---
## POST /api/history/:id/delete
Löscht Job aus DB; optional auch Dateien.
**Request:**
```json
{
"target": "both",
"includeRelated": true,
"resetDriveState": false,
"keepDetectedDevice": true,
"preserveRawForImportJobs": false,
"selectedRawPaths": ["/mnt/raw/Inception - RAW - Disc1"],
"selectedMoviePaths": ["/mnt/movies/Inception (2010)"]
}
```
`target`: `none` | `raw` | `movie` | `both`
**Response (Beispiel):**
```json
{
"deleted": true,
"jobId": 42,
"fileTarget": "both",
"fileSummary": {
"target": "both",
"raw": { "filesDeleted": 10 },
"movie": { "filesDeleted": 1 }
},
"uiReset": {
"reset": true,
"state": "IDLE"
},
"safeguards": {
"containsOrphanRawImportJob": false,
"resetDriveStateApplied": false,
"keepDetectedDeviceApplied": true
}
}
```
---
## Hinweise
- Ein aktiver Pipeline-Job kann nicht gelöscht werden (`409`).
- Löschoperationen sind irreversibel.
- Bei Serien-/Multipart-Containern steuern `includeRelated` und Pfadselektionen den genauen Scope.
+93
View File
@@ -0,0 +1,93 @@
# Anhang: API-Referenz
REST- und WebSocket-Schnittstellen für Integration, Automatisierung und Debugging.
## Basis-URL
```text
http://localhost:3001
```
API-Prefix: `/api`
## API-Gruppen
<div class="grid cards" markdown>
- :material-heart-pulse: **Health**
---
Service-Liveness.
`GET /api/health`
- :material-pipe: **Pipeline API**
---
Analyse, Start/Retry/Cancel, Queue, Re-Encode.
[:octicons-arrow-right-24: Pipeline API](pipeline.md)
- :material-cog: **Settings API**
---
Einstellungen, Skripte/Ketten, User-Presets.
[:octicons-arrow-right-24: Settings API](settings.md)
- :material-history: **History API**
---
Job-Historie, Orphan-Import, Löschoperationen.
[:octicons-arrow-right-24: History API](history.md)
- :material-clock-outline: **Cron API**
---
Zeitgesteuerte Skript-/Kettenausführung.
[:octicons-arrow-right-24: Cron API](crons.md)
- :material-swap-horizontal: **Converter API**
---
Datei-Explorer, Upload, Job-Verwaltung für den Converter.
[:octicons-arrow-right-24: Converter API](converter.md)
- :material-download: **Downloads API**
---
Download-Queue für Ausgabedateien aus der Job-Historie.
[:octicons-arrow-right-24: Downloads API](downloads.md)
- :material-lightning-bolt: **Runtime Activities API**
---
Laufende und abgeschlossene Aktivitäten (Skripte, Ketten, Cron, Tasks).
[:octicons-arrow-right-24: Runtime Activities](runtime-activities.md)
- :material-websocket: **WebSocket Events**
---
Pipeline-, Queue-, Disk-, Settings-, Cron-, Converter- und Download-Events.
[:octicons-arrow-right-24: WebSocket](websocket.md)
</div>
## Hinweis
Ripster hat keine eingebaute Authentifizierung und ist für lokalen, geschützten Betrieb gedacht.
+534
View File
@@ -0,0 +1,534 @@
# Pipeline API
Endpunkte zur Steuerung des Pipeline-Workflows.
---
## GET /api/pipeline/state
Liefert aktuellen Pipeline- und Hardware-Monitoring-Snapshot.
**Response (Beispiel):**
```json
{
"pipeline": {
"state": "READY_TO_ENCODE",
"activeJobId": 42,
"progress": 0,
"eta": null,
"statusText": "Mediainfo bestätigt - Encode manuell starten",
"context": {
"jobId": 42
},
"jobProgress": {
"42": {
"state": "MEDIAINFO_CHECK",
"progress": 68.5,
"eta": null,
"statusText": "MEDIAINFO_CHECK 68.50%"
}
},
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
},
"hardwareMonitoring": {
"enabled": true,
"intervalMs": 5000,
"updatedAt": "2026-03-10T09:00:00.000Z",
"sample": {
"cpu": {},
"memory": {},
"gpu": {},
"storage": {}
},
"error": null
}
}
```
---
## POST /api/pipeline/analyze
Startet Disc-Analyse und legt Job an.
**Response:**
```json
{
"result": {
"jobId": 42,
"detectedTitle": "INCEPTION",
"omdbCandidates": []
}
}
```
---
## POST /api/pipeline/rescan-disc
Erzwingt erneute Laufwerksprüfung.
**Response (Beispiel):**
```json
{
"result": {
"present": true,
"changed": true,
"emitted": "discInserted",
"device": {
"path": "/dev/sr0",
"discLabel": "INCEPTION",
"mediaProfile": "bluray"
}
}
}
```
---
## POST /api/pipeline/rescan-drive
Erzwingt Rescan für ein konkretes Laufwerk.
**Request:**
```json
{ "devicePath": "/dev/sr0" }
```
---
## GET /api/pipeline/omdb/search?q=<query>
OMDb-Titelsuche.
**Response:**
```json
{
"results": [
{
"imdbId": "tt1375666",
"title": "Inception",
"year": "2010",
"type": "movie",
"poster": "https://..."
}
]
}
```
---
## GET /api/pipeline/tmdb/series/search?q=<query>&season=<n>
TMDb-Seriensuche (für Serien-Workflow bei DVD/Blu-ray).
---
## GET /api/pipeline/cd/drives
Liefert Snapshot der aktuell bekannten CD-Laufwerke.
---
## GET /api/pipeline/cd/musicbrainz/search?q=<query>
MusicBrainz-Suche für CD-Metadaten.
## GET /api/pipeline/cd/musicbrainz/release/:mbId
Lädt Release-Details zu einer MusicBrainz-ID.
## POST /api/pipeline/cd/select-metadata
Übernimmt CD-Metadaten für einen Job.
**Request (Beispiel):**
```json
{
"jobId": 91,
"title": "Album",
"artist": "Artist",
"year": 2020,
"mbId": "f5093c06-23e3-404f-aeaa-40f72885ee3a",
"coverUrl": "https://...",
"tracks": []
}
```
## POST /api/pipeline/cd/start/:jobId
Startet/queued CD-Rip mit Format-/Script-/Chain-Konfiguration.
---
## POST /api/pipeline/audiobook/upload
Upload von AAX-Datei (Multipart/FormData).
**FormData-Felder:**
- `file` (Pflicht)
- `format` (optional)
- `startImmediately` (optional)
## GET /api/pipeline/audiobook/pending-activation
Zeigt AAX-Jobs mit fehlenden Activation-Bytes.
## POST /api/pipeline/audiobook/start/:jobId
Startet Audiobook-Job mit optionaler Konfiguration.
## GET /api/pipeline/audiobook/jobs
Liefert Audiobook-Jobliste.
## GET /api/pipeline/audiobook/output-tree
Read-only Baumansicht des Audiobook-Ausgabeordners.
---
## POST /api/pipeline/select-metadata
Setzt Metadaten (und optional Playlist) für einen Job.
**Request:**
```json
{
"jobId": 42,
"title": "Inception",
"year": 2010,
"imdbId": "tt1375666",
"poster": "https://...",
"fromOmdb": true,
"selectedPlaylist": "00800"
}
```
Wichtige optionale Felder:
- `selectedHandBrakeTitleId` / `selectedHandBrakeTitleIds`: Titel-Auswahl für Review/MediaInfo.
- `metadataProvider`: `omdb` oder `tmdb`.
- `workflowKind`: bei Disc-Jobs typischerweise `film` oder `series`.
- `discNumber`: Pflicht für Serien-Disc-Zuordnung (`workflowKind=series`).
- `duplicateAction`: Duplikatverhalten bei Film-Metadaten (`allow_new` oder `multipart_movie`).
- `existingJobId`: optionaler Referenzjob für `multipart_movie`.
- `existingDiscNumber`: Disc-Nummer des bestehenden Jobs für `multipart_movie`.
**Response:**
```json
{ "job": { "id": 42, "status": "READY_TO_START" } }
```
**Konflikt-Response (Beispiel, HTTP 409):**
```json
{
"message": "Metadaten bereits in der Historie gefunden. Bitte Auswahl übernehmen oder Multipart movie wählen.",
"details": [
{
"code": "METADATA_DUPLICATE_FOUND",
"mediaProfile": "bluray",
"existingJob": {
"id": 17,
"title": "Inception",
"year": 2010,
"status": "FINISHED",
"jobKind": "bluray",
"discNumber": 1,
"isMultipartMovie": false
}
}
]
}
```
**Relevante Fehlercodes (`POST /api/pipeline/select-metadata`):**
| Code | Bedeutung |
|---|---|
| `METADATA_DUPLICATE_FOUND` | Film-Metadaten existieren bereits in der Historie (Konfliktdialog im UI). |
| `MULTIPART_DISC_REQUIRED` | Für Multipart fehlen Disc-Nummern (`discNumber`/`existingDiscNumber`). |
| `MULTIPART_DISC_ALREADY_EXISTS` | Disc-Nummer im Multipart-Container bereits vergeben oder doppelt. |
| `MULTIPART_MEDIA_MISMATCH` | Multipart nur bei gleichem Medientyp (DVD oder Blu-ray). |
| `MULTIPART_SERIES_NOT_ALLOWED` | Multipart ist nur für Film-Jobs erlaubt, nicht für Serien-Workflow. |
| `MULTIPART_METADATA_MISMATCH` | Ausgewählter bestehender Job passt nicht zu den Film-Metadaten. |
| `SERIES_DISC_ALREADY_EXISTS` | Serien-Disc-Nummer innerhalb einer Staffel bereits vorhanden. |
---
## POST /api/pipeline/jobs/:jobId/raw-decision
Trifft Entscheidung bei vorhandenem RAW (`continue` oder `restart` je nach Dialogfluss).
**Request:**
```json
{ "decision": "continue" }
```
---
## POST /api/pipeline/start/:jobId
Startet vorbereiteten Job oder queued ihn (je nach Parallel-Limit).
**Mögliche Responses:**
```json
{ "result": { "started": true, "stage": "RIPPING" } }
```
```json
{ "result": { "queued": true, "started": false, "queuePosition": 2, "action": "START_PREPARED" } }
```
---
## POST /api/pipeline/confirm-encode/:jobId
Bestätigt Review-Auswahl (Tracks, Pre/Post-Skripte/Ketten, User-Preset).
**Request (typisch):**
```json
{
"selectedEncodeTitleId": 1,
"selectedTrackSelection": {
"1": {
"audioTrackIds": [1, 2],
"subtitleTrackIds": [3]
}
},
"selectedPreEncodeScriptIds": [1],
"selectedPostEncodeScriptIds": [2, 7],
"selectedPreEncodeChainIds": [3],
"selectedPostEncodeChainIds": [4],
"selectedUserPresetId": 5,
"skipPipelineStateUpdate": false
}
```
**Response:**
```json
{ "job": { "id": 42, "encode_review_confirmed": 1 } }
```
---
## POST /api/pipeline/cancel
Bricht laufenden Job ab oder entfernt Queue-Eintrag.
**Request (optional):**
```json
{ "jobId": 42 }
```
**Mögliche Responses:**
```json
{ "result": { "cancelled": true, "queuedOnly": true, "jobId": 42 } }
```
```json
{ "result": { "cancelled": true, "queuedOnly": false, "jobId": 42 } }
```
```json
{ "result": { "cancelled": true, "queuedOnly": false, "pending": true, "jobId": 42 } }
```
---
## POST /api/pipeline/retry/:jobId
Retry für `ERROR`/`CANCELLED`-Jobs (oder Queue-Einreihung).
## POST /api/pipeline/reencode/:jobId
Startet Re-Encode aus bestehendem RAW.
## POST /api/pipeline/restart-review/:jobId
Berechnet Review aus RAW neu.
## POST /api/pipeline/restart-encode/:jobId
Startet Encoding mit letzter bestätigter Review neu.
## POST /api/pipeline/restart-cd-review/:jobId
Berechnet CD-Review aus vorhandenem RAW neu.
## POST /api/pipeline/resume-ready/:jobId
Lädt `READY_TO_ENCODE`-Job nach Neustart wieder in aktive Session.
## GET /api/pipeline/output-folders/:jobId
Liefert bekannte Output-Ordner entlang der Job-Lineage.
## POST /api/pipeline/delete-output-folders/:jobId
Löscht ausgewählte Output-Ordner.
**Request:**
```json
{ "folderPaths": ["/mnt/movies/Inception (2010)"] }
```
Alle Endpunkte liefern `{ result: ... }` bzw. `{ job: ... }`.
---
## Queue-Endpunkte
### GET /api/pipeline/queue
Liefert Queue-Snapshot.
```json
{
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 3,
"runningJobs": [
{
"jobId": 41,
"title": "Inception",
"status": "ENCODING",
"lastState": "ENCODING"
}
],
"queuedJobs": [
{
"entryId": 11,
"position": 1,
"type": "job",
"jobId": 42,
"action": "START_PREPARED",
"actionLabel": "Start",
"title": "Matrix",
"status": "READY_TO_ENCODE",
"lastState": "READY_TO_ENCODE",
"hasScripts": true,
"hasChains": false,
"enqueuedAt": "2026-03-10T09:00:00.000Z"
},
{
"entryId": 12,
"position": 2,
"type": "wait",
"waitSeconds": 30,
"title": "Warten 30s",
"status": "QUEUED",
"enqueuedAt": "2026-03-10T09:01:00.000Z"
}
],
"updatedAt": "2026-03-10T09:01:02.000Z"
}
}
```
### POST /api/pipeline/queue/reorder
Sortiert Queue-Einträge neu.
**Request:**
```json
{
"orderedEntryIds": [12, 11]
}
```
Legacy fallback wird akzeptiert:
```json
{
"orderedJobIds": [42, 43]
}
```
### POST /api/pipeline/queue/entry
Fügt Nicht-Job-Queue-Eintrag hinzu (`script`, `chain`, `wait`).
**Request-Beispiele:**
```json
{ "type": "script", "scriptId": 3 }
```
```json
{ "type": "chain", "chainId": 2, "insertAfterEntryId": 11 }
```
```json
{ "type": "wait", "waitSeconds": 45 }
```
**Response:**
```json
{
"result": { "entryId": 12, "type": "wait", "position": 2 },
"queue": { "...": "..." }
}
```
### DELETE /api/pipeline/queue/entry/:entryId
Entfernt Queue-Eintrag.
**Response:**
```json
{ "queue": { "...": "..." } }
```
---
## Pipeline-Zustände
| State | Bedeutung |
|------|-----------|
| `IDLE` | Wartet auf Medium |
| `DISC_DETECTED` | Medium erkannt |
| `ANALYZING` | MakeMKV-Analyse läuft |
| `METADATA_SELECTION` | Metadaten-Auswahl |
| `WAITING_FOR_USER_DECISION` | Nutzerentscheidung erforderlich (Playlist-Auswahl oder RAW-Entscheidung) |
| `READY_TO_START` | Übergang vor Start |
| `RIPPING` | MakeMKV-Rip läuft |
| `MEDIAINFO_CHECK` | Titel-/Track-Auswertung |
| `READY_TO_ENCODE` | Review bereit |
| `ENCODING` | HandBrake-Encoding läuft |
| `CD_METADATA_SELECTION` | CD-Metadatenauswahl aktiv |
| `CD_READY_TO_RIP` | CD-Job ist startbereit |
| `CD_ANALYZING` | CD-Struktur/Tracks werden vorbereitet |
| `CD_RIPPING` | CD-Ripping läuft |
| `CD_ENCODING` | CD-Encode läuft |
| `FINISHED` | Abgeschlossen |
| `DONE` | Abgeschlossen (v. a. Audiobook/Converter/CD-Varianten) |
| `CANCELLED` | Abgebrochen |
| `ERROR` | Fehler |
+235
View File
@@ -0,0 +1,235 @@
# Runtime Activities API
Ripster verfolgt alle laufenden und kürzlich abgeschlossenen Aktivitäten (Skripte, Skript-Ketten, Cron-Jobs, interne Tasks) in Echtzeit über den `RuntimeActivityService`.
---
## Übersicht
Aktivitäten entstehen, wenn Ripster intern Aktionen ausführt z. B. beim Start eines Cron-Jobs, beim Ausführen einer Skript-Kette oder beim Durchlaufen von Pipeline-Schritten. Sie sind **nicht persistent** (kein DB-Speicher) und werden nur im Arbeitsspeicher gehalten.
- **Aktive Aktivitäten** (`active`): Laufen gerade.
- **Letzte Aktivitäten** (`recent`): Abgeschlossen, max. 120 Einträge.
Änderungen werden über WebSocket (`RUNTIME_ACTIVITY_CHANGED`) in Echtzeit gesendet.
---
## Aktivitäts-Objekt
```json
{
"id": 7,
"type": "chain",
"name": "Post-Encode Aufräumen",
"status": "running",
"source": "cron",
"message": "Schritt 2 von 3",
"currentStep": "cleanup.sh",
"currentStepType": "script",
"currentScriptName": "cleanup.sh",
"stepIndex": 2,
"stepTotal": 3,
"parentActivityId": null,
"jobId": 42,
"cronJobId": 3,
"chainId": 5,
"scriptId": null,
"canCancel": true,
"canNextStep": false,
"outcome": "running",
"errorMessage": null,
"output": null,
"stdout": null,
"stderr": null,
"stdoutTruncated": false,
"stderrTruncated": false,
"startedAt": "2026-03-10T10:00:00.000Z",
"finishedAt": null,
"durationMs": null,
"exitCode": null,
"success": null
}
```
### Felder
| Feld | Typ | Beschreibung |
|------|-----|--------------|
| `id` | `number` | Eindeutige ID (Laufzähler, nicht persistent) |
| `type` | `string` | Art der Aktivität: `script` \| `chain` \| `cron` \| `task` |
| `name` | `string \| null` | Anzeigename der Aktivität |
| `status` | `string` | Aktueller Status: `running` \| `success` \| `error` |
| `source` | `string \| null` | Auslöser (z. B. `cron`, `pipeline`, `manual`) |
| `message` | `string \| null` | Kurztext zum aktuellen Zustand |
| `currentStep` | `string \| null` | Name des aktuell ausgeführten Schritts |
| `currentStepType` | `string \| null` | Typ des Schritts (z. B. `script`, `wait`) |
| `currentScriptName` | `string \| null` | Name des Skripts im aktuellen Schritt |
| `stepIndex` | `number \| null` | Aktueller Schritt (1-basiert) |
| `stepTotal` | `number \| null` | Gesamtanzahl Schritte |
| `parentActivityId` | `number \| null` | ID der übergeordneten Aktivität |
| `jobId` | `number \| null` | Verknüpfte Job-ID |
| `cronJobId` | `number \| null` | Verknüpfte Cron-Job-ID |
| `chainId` | `number \| null` | Verknüpfte Skript-Ketten-ID |
| `scriptId` | `number \| null` | Verknüpfte Skript-ID |
| `canCancel` | `boolean` | Abbrechen über API möglich |
| `canNextStep` | `boolean` | Nächster Schritt über API auslösbar |
| `outcome` | `string \| null` | Abschluss-Ergebnis: `success` \| `error` \| `cancelled` \| `skipped` \| `running` |
| `errorMessage` | `string \| null` | Fehlermeldung (max. 2.000 Zeichen) |
| `output` | `string \| null` | Allgemeine Ausgabe (max. 12.000 Zeichen) |
| `stdout` | `string \| null` | Standardausgabe des Prozesses (max. 12.000 Zeichen) |
| `stderr` | `string \| null` | Fehlerausgabe des Prozesses (max. 12.000 Zeichen) |
| `stdoutTruncated` | `boolean` | `true`, wenn `stdout` gekürzt wurde |
| `stderrTruncated` | `boolean` | `true`, wenn `stderr` gekürzt wurde |
| `startedAt` | `string` | ISO-8601-Zeitstempel des Starts |
| `finishedAt` | `string \| null` | ISO-8601-Zeitstempel des Endes |
| `durationMs` | `number \| null` | Laufzeit in Millisekunden |
| `exitCode` | `number \| null` | Exit-Code des Prozesses |
| `success` | `boolean \| null` | Erfolgsstatus (`null` bei laufender Aktivität) |
---
## Snapshot-Objekt
Alle Aktivitäts-Endpunkte geben einen Snapshot zurück:
```json
{
"active": [ /* laufende Aktivitäten, nach startedAt absteigend */ ],
"recent": [ /* abgeschlossene Aktivitäten, nach finishedAt absteigend, max. 120 */ ],
"updatedAt": "2026-03-10T10:05:00.000Z"
}
```
---
## Endpunkte
### GET `/api/runtime/activities`
Aktuellen Aktivitäts-Snapshot abrufen.
**Antwort:**
```json
{
"active": [],
"recent": [
{
"id": 5,
"type": "script",
"name": "notify.sh",
"status": "success",
"outcome": "success",
"startedAt": "2026-03-10T09:58:00.000Z",
"finishedAt": "2026-03-10T09:58:02.000Z",
"durationMs": 2100,
"exitCode": 0,
"success": true,
"canCancel": false,
"canNextStep": false
}
],
"updatedAt": "2026-03-10T10:05:00.000Z"
}
```
---
### POST `/api/runtime/activities/:id/cancel`
Aktive Aktivität abbrechen (nur wenn `canCancel: true`).
**Parameter:**
| Name | In | Typ | Beschreibung |
|------|----|-----|--------------|
| `id` | path | `number` | Aktivitäts-ID |
| `reason` | body | `string` | Optionaler Abbruchgrund |
**Request Body:**
```json
{ "reason": "Manueller Abbruch durch Benutzer" }
```
**Antwort (Erfolg):**
```json
{
"ok": true,
"action": null,
"snapshot": { "active": [], "recent": [], "updatedAt": "..." }
}
```
**Fehlercodes:**
| HTTP | Bedeutung |
|------|-----------|
| `404` | Aktivität nicht gefunden oder bereits abgeschlossen |
| `409` | Abbrechen wird von dieser Aktivität nicht unterstützt |
---
### POST `/api/runtime/activities/:id/next-step`
Nächsten Schritt einer Aktivität auslösen (nur wenn `canNextStep: true`).
**Parameter:**
| Name | In | Typ | Beschreibung |
|------|----|-----|--------------|
| `id` | path | `number` | Aktivitäts-ID |
**Antwort (Erfolg):**
```json
{
"ok": true,
"action": null,
"snapshot": { "active": [], "recent": [], "updatedAt": "..." }
}
```
**Fehlercodes:**
| HTTP | Bedeutung |
|------|-----------|
| `404` | Aktivität nicht gefunden |
| `409` | Nächster Schritt wird von dieser Aktivität nicht unterstützt |
---
### POST `/api/runtime/activities/clear-recent`
Alle abgeschlossenen Aktivitäten aus `recent` löschen.
**Antwort:**
```json
{
"ok": true,
"removed": 14,
"snapshot": { "active": [], "recent": [], "updatedAt": "..." }
}
```
---
## Grenzwerte
| Wert | Limit |
|------|-------|
| Maximale `recent`-Einträge | 120 |
| Maximale Länge `stdout` / `stderr` / `output` | 12.000 Zeichen |
| Maximale Länge `errorMessage` / `message` | 2.000 Zeichen |
| Maximale Länge `outcome` | 40 Zeichen |
Gekürzte Ausgaben erhalten den Suffix ` ...[gekürzt]` (bei Inline-Text) bzw. `\n...[gekürzt]` (bei mehrzeiligem Output).
---
## Echtzeit-Updates
Änderungen werden automatisch als [`RUNTIME_ACTIVITY_CHANGED`](websocket.md#runtime_activity_changed) WebSocket-Event gesendet. Die Frontend-Komponente braucht `GET /api/runtime/activities` nur beim initialen Laden aufzurufen.
+424
View File
@@ -0,0 +1,424 @@
# Settings API
Endpunkte für Einstellungen, Skripte, Skript-Ketten und User-Presets.
---
## GET /api/settings
Liefert alle Einstellungen kategorisiert.
**Response (Struktur):**
```json
{
"categories": [
{
"category": "Pfade",
"settings": [
{
"key": "raw_dir",
"label": "Raw Ausgabeordner",
"type": "path",
"required": true,
"description": "...",
"defaultValue": "data/output/raw",
"options": [],
"validation": { "minLength": 1 },
"value": "data/output/raw",
"orderIndex": 100
}
]
}
]
}
```
---
## PUT /api/settings/:key
Aktualisiert eine einzelne Einstellung.
**Request:**
```json
{ "value": "/mnt/storage/raw" }
```
**Response:**
```json
{
"setting": {
"key": "raw_dir",
"value": "/mnt/storage/raw"
},
"reviewRefresh": {
"triggered": false,
"reason": "not_ready"
}
}
```
`reviewRefresh` ist `null` oder ein Objekt mit Status der optionalen Review-Neuberechnung.
---
## PUT /api/settings
Aktualisiert mehrere Einstellungen atomar.
**Request:**
```json
{
"settings": {
"raw_dir": "/mnt/storage/raw",
"movie_dir": "/mnt/storage/movies",
"handbrake_preset_bluray": "H.264 MKV 1080p30"
}
}
```
**Response:**
```json
{
"changes": [
{ "key": "raw_dir", "value": "/mnt/storage/raw" },
{ "key": "movie_dir", "value": "/mnt/storage/movies" }
],
"reviewRefresh": {
"triggered": true,
"jobId": 42,
"relevantKeys": ["handbrake_preset_bluray"]
}
}
```
Bei Validierungsfehlern kommt `400` mit `error.details[]`.
---
## GET /api/settings/handbrake-presets
Liest Preset-Liste via `HandBrakeCLI -z` (mit Fallback auf konfigurierte Presets).
**Response (Beispiel):**
```json
{
"source": "handbrake-cli",
"message": null,
"options": [
{ "label": "General/", "value": "__group__general", "disabled": true, "category": "General" },
{ "label": " Fast 1080p30", "value": "Fast 1080p30", "category": "General" }
]
}
```
---
## POST /api/settings/pushover/test
Sendet Testnachricht über aktuelle PushOver-Settings.
**Request (optional):**
```json
{
"title": "Test",
"message": "Ripster Test"
}
```
**Response:**
```json
{
"result": {
"sent": true,
"eventKey": "test",
"requestId": "..."
}
}
```
Wenn PushOver deaktiviert ist oder Credentials fehlen, kommt i. d. R. ebenfalls `200` mit `sent: false` + `reason`.
---
## Skripte
Basis: `/api/settings/scripts`
### GET /api/settings/scripts
```json
{ "scripts": [ { "id": 1, "name": "...", "scriptBody": "...", "orderIndex": 1, "createdAt": "...", "updatedAt": "..." } ] }
```
### POST /api/settings/scripts
```json
{ "name": "Move", "scriptBody": "mv \"$RIPSTER_OUTPUT_PATH\" /mnt/movies/" }
```
Response: `201` mit `{ "script": { ... } }`
### PUT /api/settings/scripts/:id
Body wie `POST`, Response `{ "script": { ... } }`.
### DELETE /api/settings/scripts/:id
Response `{ "removed": { ... } }`.
### POST /api/settings/scripts/reorder
```json
{ "orderedScriptIds": [3, 1, 2] }
```
Response `{ "scripts": [ ... ] }`.
### POST /api/settings/scripts/:id/test
Führt Skript als Testlauf aus.
```json
{
"result": {
"scriptId": 1,
"scriptName": "Move",
"success": true,
"exitCode": 0,
"signal": null,
"timedOut": false,
"durationMs": 120,
"stdout": "...",
"stderr": "...",
"stdoutTruncated": false,
"stderrTruncated": false
}
}
```
### Umgebungsvariablen für Skripte
Diese Variablen werden beim Ausführen gesetzt:
- `RIPSTER_SCRIPT_RUN_AT`
- `RIPSTER_JOB_ID`
- `RIPSTER_JOB_TITLE`
- `RIPSTER_MODE`
- `RIPSTER_INPUT_PATH`
- `RIPSTER_OUTPUT_PATH`
- `RIPSTER_RAW_PATH`
- `RIPSTER_SCRIPT_ID`
- `RIPSTER_SCRIPT_NAME`
- `RIPSTER_SCRIPT_SOURCE`
---
## Skript-Ketten
Basis: `/api/settings/script-chains`
Eine Kette hat Schritte vom Typ:
- `script` (`scriptId` erforderlich)
- `wait` (`waitSeconds` 1..3600)
### GET /api/settings/script-chains
Response `{ "chains": [ ... ] }` (inkl. `steps[]`).
### GET /api/settings/script-chains/:id
Response `{ "chain": { ... } }`.
### POST /api/settings/script-chains
```json
{
"name": "After Encode",
"steps": [
{ "stepType": "script", "scriptId": 1 },
{ "stepType": "wait", "waitSeconds": 15 },
{ "stepType": "script", "scriptId": 2 }
]
}
```
Response: `201` mit `{ "chain": { ... } }`
### PUT /api/settings/script-chains/:id
Body wie `POST`, Response `{ "chain": { ... } }`.
### DELETE /api/settings/script-chains/:id
Response `{ "removed": { ... } }`.
### POST /api/settings/script-chains/reorder
```json
{ "orderedChainIds": [2, 1, 3] }
```
Response `{ "chains": [ ... ] }`.
### POST /api/settings/script-chains/:id/test
Response:
```json
{
"result": {
"chainId": 2,
"chainName": "After Encode",
"steps": 3,
"succeeded": 3,
"failed": 0,
"aborted": false,
"results": []
}
}
```
---
## User-Presets
Basis: `/api/settings/user-presets`
### GET /api/settings/user-presets
Optionaler Query-Parameter: `media_type=bluray|dvd|other|all`
```json
{
"presets": [
{
"id": 1,
"name": "Blu-ray HQ",
"mediaType": "bluray",
"handbrakePreset": "H.264 MKV 1080p30",
"extraArgs": "--encoder-preset slow",
"description": "...",
"createdAt": "...",
"updatedAt": "..."
}
]
}
```
### POST /api/settings/user-presets
```json
{
"name": "Blu-ray HQ",
"mediaType": "bluray",
"handbrakePreset": "H.264 MKV 1080p30",
"extraArgs": "--encoder-preset slow",
"description": "optional"
}
```
Response: `201` mit `{ "preset": { ... } }`
### PUT /api/settings/user-presets/:id
Body mit beliebigen Feldern aus `POST`, Response `{ "preset": { ... } }`.
### DELETE /api/settings/user-presets/:id
Response `{ "removed": { ... } }`.
---
## Weitere Endpunkte
### GET /api/settings/effective-paths
Liefert aufgelöste, effektiv genutzte Pfade aus den Settings (für UI- und Diagnosezwecke).
### POST /api/settings/coverart/recover
Startet eine manuelle Coverart-Recovery.
**Response (Beispiel):**
```json
{
"result": { "started": true },
"scheduler": { "enabled": true }
}
```
---
## Activation Bytes (AAX)
### GET /api/settings/activation-bytes
Liest gecachte AAX-Activation-Bytes.
### POST /api/settings/activation-bytes
Speichert Activation-Bytes.
**Request:**
```json
{
"checksum": "abc123...",
"activationBytes": "1a2b3c4d"
}
```
---
## Laufwerke
### GET /api/settings/drives
Liefert erkannte optische Laufwerke (`/dev/...`) inkl. MakeMKV-Disc-Index.
### POST /api/settings/drives/force-unlock
Erzwingt Entsperren aktiver Laufwerkslocks.
Hinweis: Expertenmodus (`ui_expert_mode`) ist erforderlich, sonst `403`.
**Request (ein Laufwerk):**
```json
{ "devicePath": "/dev/sr0" }
```
**Request (alle):**
```json
{ "all": true }
```
---
## UI Preferences
### GET /api/settings/prefs/:key
Liest einen persistierten UI-Preference-Wert.
### PUT /api/settings/prefs/:key
Speichert einen persistierten UI-Preference-Wert.
**Request:**
```json
{ "value": "{\"columns\":[\"id\",\"status\"]}" }
```
+294
View File
@@ -0,0 +1,294 @@
# WebSocket Events
Ripster sendet Echtzeit-Updates über `/ws`.
---
## Verbindung
```js
const ws = new WebSocket('ws://localhost:3001/ws');
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(msg.type, msg.payload);
};
```
---
## Nachrichtenformat
Die meisten Broadcasts haben dieses Schema:
```json
{
"type": "EVENT_TYPE",
"payload": {},
"timestamp": "2026-03-10T09:00:00.000Z"
}
```
Ausnahme: `WS_CONNECTED` beim Verbindungsaufbau enthält kein `timestamp`.
---
## Event-Typen
### WS_CONNECTED
Sofort nach erfolgreicher Verbindung.
```json
{
"type": "WS_CONNECTED",
"payload": {
"connectedAt": "2026-03-10T09:00:00.000Z"
}
}
```
### PIPELINE_STATE_CHANGED
Neuer Pipeline-Snapshot.
```json
{
"type": "PIPELINE_STATE_CHANGED",
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 62.5,
"eta": "00:12:34",
"statusText": "ENCODING 62.50%",
"context": {},
"jobProgress": {
"42": {
"state": "ENCODING",
"progress": 62.5,
"eta": "00:12:34",
"statusText": "ENCODING 62.50%"
}
},
"queue": {
"maxParallelJobs": 1,
"runningCount": 1,
"queuedCount": 2,
"runningJobs": [],
"queuedJobs": []
}
}
}
```
### PIPELINE_PROGRESS
Laufende Fortschrittsupdates.
```json
{
"type": "PIPELINE_PROGRESS",
"payload": {
"state": "ENCODING",
"activeJobId": 42,
"progress": 62.5,
"eta": "00:12:34",
"statusText": "ENCODING 62.50%"
}
}
```
### PIPELINE_QUEUE_CHANGED
Queue-Snapshot aktualisiert.
### DISC_DETECTED / DISC_REMOVED
Disc-Insertion/-Removal.
```json
{
"type": "DISC_DETECTED",
"payload": {
"device": {
"path": "/dev/sr0",
"discLabel": "INCEPTION",
"model": "ASUS BW-16D1HT",
"fstype": "udf",
"mountpoint": null,
"mediaProfile": "bluray"
}
}
}
```
`mediaProfile`: `bluray` | `dvd` | `other` | `null`
### HARDWARE_MONITOR_UPDATE
Snapshot aus Hardware-Monitoring.
```json
{
"type": "HARDWARE_MONITOR_UPDATE",
"payload": {
"enabled": true,
"intervalMs": 5000,
"updatedAt": "2026-03-10T09:00:00.000Z",
"sample": {
"cpu": {},
"memory": {},
"gpu": {},
"storage": {}
},
"error": null
}
}
```
### PIPELINE_ERROR
Fehler bei Disc-Event-Verarbeitung in Pipeline.
### DISK_DETECTION_ERROR
Fehler in Laufwerkserkennung.
### SETTINGS_UPDATED
Einzelnes Setting wurde gespeichert.
### SETTINGS_BULK_UPDATED
Bulk-Settings gespeichert.
```json
{
"type": "SETTINGS_BULK_UPDATED",
"payload": {
"count": 3,
"keys": ["raw_dir", "movie_dir", "handbrake_preset_bluray"]
}
}
```
### SETTINGS_SCRIPTS_UPDATED
Skript geändert (`created|updated|deleted|reordered`).
### SETTINGS_SCRIPT_CHAINS_UPDATED
Skript-Kette geändert (`created|updated|deleted|reordered`).
### USER_PRESETS_UPDATED
User-Preset geändert (`created|updated|deleted`).
### CRON_JOBS_UPDATED
Cron-Config geändert (`created|updated|deleted`).
### CRON_JOB_UPDATED
Laufzeitstatus eines Cron-Jobs geändert.
```json
{
"type": "CRON_JOB_UPDATED",
"payload": {
"id": 1,
"lastRunStatus": "running",
"lastRunAt": "2026-03-10T10:00:00.000Z",
"nextRunAt": null
}
}
```
### 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).
### CONVERTER_SCAN_UPDATE
Wird nach einem Scan des Converter-Eingangsordners gesendet (manuell oder per Polling).
```json
{
"type": "CONVERTER_SCAN_UPDATE",
"payload": {
"entryCount": 12
}
}
```
### DOWNLOADS_UPDATED
Wird bei jeder Statusänderung in der Download-Queue gesendet.
```json
{
"type": "DOWNLOADS_UPDATED",
"payload": {
"reason": "ready",
"item": {
"id": "dl-1",
"jobId": 42,
"status": "ready",
"archiveName": "Job_42_movie.zip",
"createdAt": "2026-03-30T10:00:00.000Z"
},
"summary": {
"total": 3,
"pending": 1,
"processing": 0,
"ready": 1,
"failed": 1
}
}
}
```
Mögliche `reason`-Werte: `queued`, `processing`, `ready`, `failed`, `deleted`.
---
## Reconnect-Verhalten
`useWebSocket` verbindet bei Abbruch automatisch neu:
- Retry-Intervall: `1500ms`
- Wiederverbindung bis Komponente unmounted wird