final dev
This commit is contained in:
@@ -3463,16 +3463,31 @@ class PipelineService extends EventEmitter {
|
||||
pushCandidate(path.join(baseDir, variantFolderName));
|
||||
}
|
||||
}
|
||||
const existingDirectories = [];
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
||||
existingDirectories.push(candidate);
|
||||
}
|
||||
} catch (_error) {
|
||||
// ignore fs errors
|
||||
}
|
||||
}
|
||||
if (existingDirectories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const candidate of existingDirectories) {
|
||||
try {
|
||||
if (hasBluRayBackupStructure(candidate) || findPreferredRawInput(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
} catch (_error) {
|
||||
// ignore fs errors
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return existingDirectories[0];
|
||||
}
|
||||
|
||||
async migrateRawFolderNamingOnStartup(db) {
|
||||
@@ -9113,10 +9128,23 @@ class PipelineService extends EventEmitter {
|
||||
|| findPreferredRawInput(resolvedReviewRawPath)
|
||||
);
|
||||
if (!hasRawInput) {
|
||||
let hasAnyRawEntries = false;
|
||||
try {
|
||||
hasAnyRawEntries = fs.readdirSync(resolvedReviewRawPath).length > 0;
|
||||
} catch (_error) {
|
||||
hasAnyRawEntries = false;
|
||||
}
|
||||
if (!hasAnyRawEntries) {
|
||||
const error = new Error('Review-Neustart nicht möglich: keine Mediendateien im RAW-Pfad gefunden. Disc muss zuerst gerippt werden.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`Review-Neustart: keine direkten Mediendateien erkannt, versuche Analyse trotzdem mit RAW-Pfad ${resolvedReviewRawPath}.`
|
||||
);
|
||||
}
|
||||
|
||||
const currentStatus = String(sourceJob.status || '').trim().toUpperCase();
|
||||
if (['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(currentStatus)) {
|
||||
|
||||
235
docs/api/runtime-activities.md
Normal file
235
docs/api/runtime-activities.md
Normal 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/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/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/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/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/activities` nur beim initialen Laden aufzurufen.
|
||||
Reference in New Issue
Block a user