Eviworx
Docs

CronJobs API

Die CronJobs API steuert geplante Automatisierung. Ein separater job-worker-Container führt die Jobs aus (Cron/Interval, Distributed Locking, Multi-Instance) — von einfachen Automatisierungen (Ticket erstellen/aktualisieren, Webhook, Zuweisung) bis zu den System-Monitoren (SLA-Monitor, Eskalations- und Cleanup-Jobs). Es gibt 28 Action-Typen und 23 Built-in-Templates.

🚀
Funktionen
✓ 28 Action-Typen (Automatisierung + Monitore)
✓ SLA-Monitor alle 2 Minuten (runOnStartup)
✓ Trigger (interval, cron, condition)
✓ Separater Worker-Container (eingeschränkter DB-Zugriff)
✓ Distributed Lock und Multi-Instance (Redis)
✓ Ausführungs-Historie (Status, Dauer, Ergebnis)
✓ Retry fehlgeschlagener Läufe (FAILED)
✓ Dry-Run ohne Änderungen
✓ Worker pausieren/fortsetzen (mit Begründung)
✓ Webhook mit SSRF-Schutz und Circuit Breaker

Architektur

Backend API (/api/cronjobs)         CRUD, RBAC, Audit, Config        │  persistiert CronJob/JobExecution in Postgres        ▼
job-worker (separater Container)
  • Scheduler             — Cron/Interval  • Queue (BullMQ)        — Ausführungs-Warteschlange  • 28 Action-Typen  • Distributed Lock (Redis): cronjob:lock:{jobId}
  • Heartbeat (15s)       — Erkennung mehrerer Instanzen / Worker-Status  • Eingeschränkter DB-Zugriff: nur CronJob/JobExecution        │
        ▼  Mutationen über Backend-Internal-API, Notifications über notification-worker

Endpoints

Job-Verwaltung /api/cronjobs

MethodEndpointPermission
GET/cronjobs.view
GET/:idcronjobs.view
POST/cronjobs.create
PUT/:idcronjobs.edit
DELETE/:idcronjobs.delete (kritisch)
POST/:id/restorecronjobs.restore + cronjobs.viewDeleted
GET/stats/summarycronjobs.view
GET/templates/listcronjobs.view
GET/activitycronjobs.view

GET / und GET /:id akzeptieren zusätzlich ?includeDeleted=true, um soft-gelöschte Jobs einzuschließen — das erfordert die eigene Permission cronjobs.viewDeleted (sonst 403).

Wiederherstellen verlangt zwei Rechte: cronjobs.restore für die Aktion und cronjobs.viewDeleted für den Zugriff auf den Papierkorb — wer den Papierkorb nicht sehen darf, holt auch nichts daraus zurück. Auf einem gelöschten Job wirkt sonst keine Mutation: Aktualisieren, Aktivieren/Deaktivieren, manuelles Ausführen, Dry-Run, Bulk-Aktionen und das Wiederholen einer Ausführung antworten mit 404, solange er im Papierkorb liegt. Die Ausführungs-Historie bleibt beim Löschen erhalten — sichtbar, aber nicht wiederholbar.

Ausführung & History

MethodEndpointPermission
PATCH/:id/togglecronjobs.enableDisable
POST/:id/executecronjobs.executeManually
POST/:id/dry-runcronjobs.dryRun
GET/executions/listcronjobs.view
GET/executions/:idcronjobs.view
POST/executions/:id/retrycronjobs.retry (kritisch)
POST/bulk/enable · /bulk/disablecronjobs.enableDisable
POST/bulk/deletecronjobs.delete

Worker & Config

MethodEndpointPermission
GET/workers/statuscronjobs.view
POST/workers/pause-all · /workers/resume-allcronjobs.pauseWorkers (kritisch)
POST/workers/:instanceId/pause · /resumecronjobs.pauseWorkers
POST/workers/:instanceId/hide · /unhidecronjobs.hideWorkers
GET / PUT/api/cronjobs/configcronjobs.view / cronjobs.edit

Datenmodell

CronJob {
  id, name (unique),
  category: ESCALATION | NOTIFICATION | REPORTING | MAINTENANCE | MONITORING | WORKFLOW | CUSTOM,
  status:   ENABLED | DISABLED | RUNNING | ERROR,
  trigger:  Json,            // { type, schedule }
  actions:  Json,            // [{ type, parameters }]
  filters:  Json,            // entity scope (e.g. ticketStatuses)
  runConditions: Json,       // additional conditions
  dependencies: String[],    // job IDs that must succeed first
  timeoutMinutes, maxRetries, runOnStartup,
  lastExecutedAt, nextExecutionAt, executionCount, failureCount, avgDurationMs,
  createdById, deletedAt     // Soft-Delete
}

JobExecution {
  id, cronJobId,
  status:      PENDING | RUNNING | COMPLETED | FAILED | CANCELLED,
  triggeredBy: SCHEDULE | MANUAL | EVENT | CONDITION,
  startedAt, completedAt, durationMs, retryCount,
  results: Json,             // [{ action, status, metadata }]
  errorMessage?, workerId
}

Trigger

typeBeschreibung
cronschedule.cronExpression (z.B. "0 8 * * 1-5")
intervalschedule.intervalMinutes oder intervalDays
conditionbedingungsbasiert (runConditions, s.u.)
// Cron
{ "trigger": { "type": "cron", "schedule": { "cronExpression": "*/30 * * * *" } } }
// Interval
{ "trigger": { "type": "interval", "schedule": { "intervalMinutes": 5 } } }

Action-Typen (28)

Automatisierung

typeBeschreibung
create_ticketTicket erstellen (z.B. wiederkehrende Wartung)
update_ticketTickets nach Filter aktualisieren (Status/Priority)
assign_agentAgent zuweisen (Strategie „specific": ein fest benannter Agent)
assign_groupGruppe zuweisen
webhookHTTP-Request an externe URL (SSRF-Schutz, Circuit Breaker, Retry)

Monitore & Eskalation

typeBeschreibung
sla_monitorSLA-Deadlines prüfen, Warnungen/Breach/Eskalation
lifecycle_stale_entity_reminderInaktivitäts-Reminder für TICKET/PROBLEM/INCIDENT (Assignee→Lead→Manager, nur an Empfänger, die den Vorgang sehen dürfen) — ändert NIE Status oder SLA
ticket_hold_reminder_checkOn-Hold-Tickets mit fälliger Wiedervorlage reaktivieren
stale_cascading_reminderHängende Resolution-Ketten (Incident/Problem resolved, Kind offen)
major_incident_update_reminderMajor Incidents mit überfälligem nextUpdateETA
data_breach_deadline_checkDSGVO Art. 33: 72h-Frist (Reminder 48h, Eskalation 72h)
inventory_due_monitorInventur-Fristen: Vorwarnung 3 und 1 Tag vorher, danach überfällig (je Meilenstein einmal)
expiry_monitorAblauf von Assets/Lizenzen/Verträgen (Meilensteine 30/7/3/0 Tage)
handover_return_reminderAsset-Rückgabe-Erinnerungen (24h/1h/overdue)
pending_assignment_retryAuto-Zuweisung erneut versuchen (Gruppe ohne Agent, z.B. Kapazität voll)
workload_syncAgent-Workload-Zähler neu berechnen (für Assignment-Strategien)
lifecycle_auto_closeFällige RESOLVED-Tickets zeitgesteuert schließen (mit Vorwarnung) — opt-in je Entität
lifecycle_wc_auto_resolveUnbeantwortete WAITING_CUSTOMER-Tickets nach Frist auf RESOLVED (Vor-Stufe zu Auto-Close) — nur Ticket. Ein Elternticket mit offenen Sub-Tickets bleibt stehen und wird im Lauf als übersprungen gezählt (siehe Tickets-API).
lifecycle_reopen_escalationEskalation bei zu häufigem Reopen (reopenCount ≥ Schwelle), nur an Empfänger, die den Vorgang sehen dürfen

Wartung / Cleanup

typeBeschreibung
holiday_autoimportDeutsche Feiertage (aktuelles + nächstes Jahr) für alle genutzten Business-Hours-Konfigurationen anlegen — berechnet inkl. beweglicher Feiertage, idempotent (keine Dubletten)
asset_model_clusteringÄhnliche Hersteller/Modell-Schreibweisen clustern (Admin-Review)
attachment_cleanupHängende Scans, Datei-Aufbewahrung, verwaiste und infizierte Dateien (Dateisystem und Virenquarantäne)
retention_purgeDSGVO-Aufbewahrung gebündelt: setzt alle zeitbasierten Aufbewahrungsfristen der Datenbank in EINEM Lauf durch, von Audit-Events (zweistufig) bis zur Auto-Anonymisierung archivierter User. Ziele und Fristen: siehe Privacy & DSGVO.
audit_chain_verifyNächtliche Voll-Verifikation der Audit-Hash-Ketten, die Manipulationen erkennbar machen (Ketten-Kontinuität je Org, Purge-Anker, Purge-Plausibilität); geprüft wird abschnittsweise, Parameter windowSize (Events je Abschnitt, Standard 100.000, erlaubt 1.000–250.000); bei jedem Fund CRITICAL-Alert an alle audit.enterpriseView-Träger
digest_dispatchVersendet fällige E-Mail-Digests (Zustellmodus gebündelt — stündlich/täglich/wöchentlich, je Empfänger EINE Sammelmail in dessen Zeitzone) und räumt verwaiste Bulk-Batch-Items ab; No-op ohne Digest-Opt-ins (siehe Notifications-Seite)
report_schedule_checkStartet fällige Report-Zeitpläne (je Exportformat eine Ausführung) und schließt fertige Läufe mit der Abschluss-Mail ab — ohne diesen Job laufen geplante Reports NICHT
push_retryFehlgeschlagene WebPush erneut senden
entra_id_syncBenutzer der Entra-ID-Basisgruppe abgleichen: anlegen, aktualisieren, Rollen zuordnen, Konten sperren, die die Gruppe verlassen haben oder in Entra ID deaktiviert sind; ohne konfigurierte und aktive Entra-ID-Integration ohne Wirkung

Grenzregel: Die zeitbasierte Aufbewahrung von Datenbank-Einträgen läuft über retention_purge — eine Stelle für alle Fristen; alles, was Dateien oder Virenscans anfasst, bleibt bei attachment_cleanup. Eskalation läuft über sla_monitor und lifecycle_stale_entity_reminder, Kapazität über workload_sync, Rückgabe-Erinnerungen über handover_return_reminder.

Built-in Templates (23)

GET /api/cronjobs/templates/list — liefert vorkonfigurierte Vorlagen (Felder: id, name, description, category, isBuiltIn, tags, template). Zu jeder Vorlage legt der Backend-Start den passenden Built-in-Job an, falls er fehlt — ENABLED, mit Ausnahme von Asset Model Deduplication, die DISABLED startet, weil sie vom genutzten Funktionsumfang abhängt. Die Lifecycle-Jobs wirken erst, wenn der Admin sie je Entität in der Lifecycle-Config aktiviert; der Digest-Job wirkt erst, wenn Benutzer den Digest gewählt haben; der Entra-ID-Sync wirkt erst, wenn die Entra-ID-Integration konfiguriert und aktiv ist. Schedules:

TemplateKategorieactionSchedule
SLA MonitorESCALATIONsla_monitoralle 2 Min, runOnStartup
Stale Entity ReminderESCALATIONlifecycle_stale_entity_reminder01:30
Ticket Hold Reminder (Wiedervorlage)MONITORINGticket_hold_reminder_checkalle 5 Min
Major Incident Update ReminderMONITORINGmajor_incident_update_reminderalle 5 Min
Stale Cascading Resolution ReminderMONITORINGstale_cascading_reminder08:00
DSGVO Data Breach Deadline MonitorMONITORINGdata_breach_deadline_checkalle 30 Min
Inventory Due MonitoringESCALATIONinventory_due_monitorstündlich
Expiry MonitorMONITORINGexpiry_monitor07:00
Asset Return RemindersMONITORINGhandover_return_reminderalle 30 Min
Pending Assignment RetryMAINTENANCEpending_assignment_retryalle 5 Min
Agent Workload SyncMAINTENANCEworkload_syncalle 5 Min
WebPush RetryMAINTENANCEpush_retryalle 5 Min
Notification Digest DispatchNOTIFICATIONdigest_dispatchalle 15 Min
Report Schedule CheckMAINTENANCEreport_schedule_checkjede Minute
Retention Purge (DSGVO)MAINTENANCEretention_purge03:00
Audit Chain VerifyMONITORINGaudit_chain_verify04:15 (nach retention_purge)
Attachment CleanupMAINTENANCEattachment_cleanup03:00
Asset Model DeduplicationMAINTENANCEasset_model_clusteringSo 02:00 · DISABLED
Holiday Auto-ImportMAINTENANCEholiday_autoimportjährlich 01.11. 04:00, runOnStartup
Entra ID User SyncMAINTENANCEentra_id_sync01:00
Lifecycle Auto-CloseMAINTENANCElifecycle_auto_close02:30
Lifecycle WC-Auto-ResolveMAINTENANCElifecycle_wc_auto_resolve02:00
Reopen EscalationESCALATIONlifecycle_reopen_escalation03:00

Built-in-Jobs: automatische Anlage beim Start

Die Built-in-Jobs werden bei jedem Backend-Start abgeglichen: Fehlende Built-in-Jobs werden anhand ihrer Vorlage angelegt (ENABLED oder DISABLED je Vorgabe, siehe oben), bestehende Jobs werden NIE verändert. Von Admins geänderte Zeitpläne, Parameter und der Aktiv-Status bleiben also erhalten. Ein Update bringt neue Standard-Jobs ohne manuellen Schritt mit.

Job erstellen

POST /api/cronjobs
{
  "name": "SLA Monitor - All Entities",
  "category": "ESCALATION",
  "description": "Check SLA deadlines and trigger escalations",
  "trigger": { "type": "interval", "schedule": { "intervalMinutes": 2 } },
  "actions": [ { "type": "sla_monitor", "parameters": { "entityType": null, "batchSize": 500 } } ],
  "timeoutMinutes": 5,
  "maxRetries": 3,
  "runOnStartup": true
}

batchSize ist die SEITENGRÖSSE, nicht die Obergrenze eines Laufs: der Monitor liest den fälligen Bestand seitenweise durch, bis nichts mehr folgt. Bricht er dennoch ab (Schutz gegen Endlosläufe), steht das im Job-Ergebnis und im Log.

// Response 201
{
  "id": "clx...",
  "name": "SLA Monitor - All Entities",
  "category": "ESCALATION",
  "status": "DISABLED",
  "trigger": { "type": "interval", "schedule": { "intervalMinutes": 2 } },
  "nextExecutionAt": null,
  "createdAt": "2026-01-27T23:00:00.000Z"
}

Neue Jobs starten als DISABLED — Aktivieren über PATCH /:id/toggle (cronjobs.enableDisable).

Ausführung, Dry-Run & Retry

# Run manually (cronjobs.executeManually)
POST /api/cronjobs/:id/execute        { "reason": "Testing config" }   # -> 202 { executionId }

# Dry-Run: shows affected entities, makes NO changes
POST /api/cronjobs/:id/dry-run

# Retry a failed execution (only status FAILED)
POST /api/cronjobs/executions/:id/retry   { "reason": "Network issue resolved" }

# History (filter jobId/status, pagination)
GET /api/cronjobs/executions/list?status=FAILED&limit=20

Status-Werte

Job (status)Execution (status)
ENABLED — aktiv, läuft nach SchedulePENDING — in Queue
DISABLED — deaktiviertRUNNING
RUNNING — läuft geradeCOMPLETED
ERROR — letzte Ausführung fehlgeschlagenFAILED — retry-bar
CANCELLED — verworfen/terminiert (z.B. verwaiste PENDING-Execution)

Selbstheilung

Jobs sind (teil-)selbstheilend — ein Job bleibt nicht dauerhaft hängen:

  • ERROR ist nicht terminal: Status ERROR bedeutet nur „letzter Lauf fehlgeschlagen". Der Job bleibt aktiv, wird weiter nach Schedule eingeplant (nextRunAt wird neu berechnet) und heilt beim nächsten erfolgreichen Lauf von selbst zurück.
  • Kein Hängenbleiben in RUNNING: Bei Fehler/Timeout wird der Distributed-Lock freigegeben; ein Job bleibt nicht fälschlich in RUNNING hängen.
  • Catch-up beim (Neu-)Start: Beim Start des job-workers werden überfällige Jobs (nextRunAt in der Vergangenheit) erkannt, als PENDING-Execution nachgeholt und neu terminiert — sie hängen nicht „Overdue für immer". Ein Distributed-Lock stellt sicher, dass nur EINE Instanz den Catch-up macht.
  • runOnStartup: Jobs mit diesem Flag (z.B. SLA Monitor) erhalten bei jedem Worker-Start eine Ausführung.
  • Orphan-Cleanup: In der Queue (BullMQ) vorhandene, in der DB aber gelöschte Jobs werden beim Start entfernt.

„Teil-selbstheilend": Die zugrunde liegende Fehlerursache wird nicht automatisch behoben — der Job versucht es lediglich beim nächsten Schedule (bzw. innerhalb eines Laufs bis maxRetries) erneut und verlässt den ERROR-Zustand bei Erfolg. Dauerhafte Fehler sollten über die Execution-History/Activity geprüft werden.

Worker-Management

Mehrere job-worker-Instanzen melden sich per Heartbeat (instanceId). GET /workers/status liefert pro Instanz health (null bei offline) und executions; die Queue-Zähler stehen als eigenes Feld queue unter health — waiting, active, completed, failed, delayed — und sind null, solange die Queue der Instanz noch nicht initialisiert ist. Für Wartung lassen sich Worker pausieren (global oder pro Instanz). Pause/Resume ist eine kritische, auditierte Aktion und verlangt einen Grund (reason, min. 10 Zeichen).

POST /api/cronjobs/workers/pause-all   { "reason": "Database maintenance window" }
# -> { "pausedWorkers": ["job-worker-abc123"], "failedWorkers": [] }

Filter & Run-Conditions

filters schränken die Ziel-Entities ein; runConditions sind zusätzliche Voraussetzungen. Ein runCondition trägt genau type + duration (Mindestalter in Minuten) — type ist ticket_age oder change_pending_approval.

{
  "filters": { "ticketStatuses": ["OPEN", "IN_PROGRESS"], "ticketPriorities": ["LOW", "MEDIUM"] },
  "runConditions": [ { "type": "ticket_age", "duration": 4320 } ]
}

In ticketStatuses und ticketPriorities sind nur gültige Ticket-Status bzw. -Prioritäten erlaubt; eine leere Liste wirkt wie kein Filter, ein unbekannter Wert lässt die Bedingungsprüfung fehlschlagen — der Job wird dann nicht ausgelöst. ticket_age zählt Tickets, die älter als duration sind und den Filtern entsprechen; change_pending_approval zählt Changes im Status PENDING_APPROVAL und wertet die Filter nicht aus. Beide Bedingungen sind erfüllt, sobald mindestens ein Datensatz zutrifft.

Webhook-Sicherheit

  • SSRF: blockiert localhost, private IPs (10/172.16-31/192.168), Link-Local (169.254), Cloud-Metadata (169.254.169.254)
  • Circuit Breaker + Rate-Limit + Retry (Exponential Backoff) + konfigurierbares Timeout

Fehlercodes

ErrorHTTP
CRONJOB_NOT_FOUND404
JOB_NAME_EXISTS409
JOB_CURRENTLY_RUNNING409
CANNOT_DELETE_RUNNING_JOB400
CAN_ONLY_RETRY_FAILED_EXECUTIONS400
JOB_EXECUTION_NOT_FOUND404
GLOBAL_PAUSE_ACTIVE409

GLOBAL_PAUSE_ACTIVE: Solange eine globale Worker-Pause aktiv ist, lässt sich keine einzelne Instanz fortsetzen — die globale Pause muss zuerst als Ganzes aufgehoben werden (resume-all).

🚀
Kernprinzipien
  • ✓ Separater job-worker, Distributed Lock
  • ✓ 28 Actions, 23 Built-in-Templates
  • ✓ SLA-Monitor als Job, alle 2 Minuten
  • ✓ Built-in-Jobs entstehen beim Start automatisch, Admin-Änderungen bleiben erhalten
🔐
Berechtigungen (RBAC)
  • cronjobs.view / create / edit / delete / restore / viewDeleted
  • cronjobs.enableDisable / executeManually / dryRun / retry
  • cronjobs.pauseWorkers / hideWorkers

Auth-/Rollenmodell: Permissions & RBAC

Verwandte Dokumentation