A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
915 lines
24 KiB
Python
915 lines
24 KiB
Python
"""
|
|
BreakPilot Studio - Workflow/BPMN Module
|
|
|
|
BPMN 2.0 Prozess-Editor mit bpmn-js Integration.
|
|
Ermoeglicht das Modellieren, Speichern und Deployen von Geschaeftsprozessen.
|
|
"""
|
|
|
|
|
|
class WorkflowModule:
|
|
"""BPMN Workflow Editor Modul fuer BreakPilot Studio."""
|
|
|
|
@staticmethod
|
|
def get_css() -> str:
|
|
"""CSS fuer das Workflow/BPMN Panel."""
|
|
return """
|
|
/* ==========================================
|
|
WORKFLOW/BPMN MODULE
|
|
========================================== */
|
|
|
|
#panel-workflow {
|
|
display: none;
|
|
flex-direction: column;
|
|
padding: 24px;
|
|
min-height: calc(100vh - 104px);
|
|
height: calc(100vh - 104px);
|
|
}
|
|
|
|
#panel-workflow.active {
|
|
display: flex;
|
|
}
|
|
|
|
/* Header */
|
|
.workflow-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.workflow-title {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.workflow-subtitle {
|
|
font-size: 14px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Toolbar */
|
|
.workflow-toolbar {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
padding: 12px 16px;
|
|
background: var(--bp-surface-elevated);
|
|
border-radius: 8px;
|
|
border: 1px solid var(--bp-border);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.workflow-toolbar-group {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.workflow-toolbar-separator {
|
|
width: 1px;
|
|
height: 24px;
|
|
background: var(--bp-border);
|
|
margin: 0 8px;
|
|
}
|
|
|
|
.workflow-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 14px;
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
border: 1px solid var(--bp-border);
|
|
background: var(--bp-surface);
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.workflow-btn:hover {
|
|
background: var(--bp-surface-elevated);
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.workflow-btn.primary {
|
|
background: var(--bp-primary);
|
|
border-color: var(--bp-primary);
|
|
color: white;
|
|
}
|
|
|
|
.workflow-btn.primary:hover {
|
|
background: var(--bp-primary-hover);
|
|
}
|
|
|
|
.workflow-btn.success {
|
|
background: var(--bp-success);
|
|
border-color: var(--bp-success);
|
|
color: white;
|
|
}
|
|
|
|
.workflow-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Canvas Container */
|
|
.workflow-canvas-container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
background: white;
|
|
min-height: 400px;
|
|
}
|
|
|
|
.workflow-canvas {
|
|
flex: 1;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
/* bpmn-js Overrides for Dark Theme Support */
|
|
.bjs-powered-by {
|
|
display: none !important;
|
|
}
|
|
|
|
.djs-palette {
|
|
background: var(--bp-surface) !important;
|
|
border-color: var(--bp-border) !important;
|
|
}
|
|
|
|
.djs-palette-entries .entry {
|
|
color: var(--bp-text) !important;
|
|
}
|
|
|
|
/* Status Bar */
|
|
.workflow-status-bar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 16px;
|
|
background: var(--bp-surface);
|
|
border-top: 1px solid var(--bp-border);
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.workflow-status-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.workflow-status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--bp-text-muted);
|
|
}
|
|
|
|
.workflow-status-dot.connected {
|
|
background: var(--bp-success);
|
|
}
|
|
|
|
.workflow-status-dot.disconnected {
|
|
background: var(--bp-danger);
|
|
}
|
|
|
|
/* Process List Panel */
|
|
.workflow-processes-panel {
|
|
position: fixed;
|
|
top: 56px;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 320px;
|
|
background: var(--bp-surface);
|
|
border-left: 1px solid var(--bp-border);
|
|
transform: translateX(100%);
|
|
transition: transform 0.3s ease;
|
|
z-index: 60;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.workflow-processes-panel.open {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.workflow-processes-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 16px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.workflow-processes-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.workflow-processes-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 20px;
|
|
color: var(--bp-text-muted);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.workflow-processes-list {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 16px;
|
|
}
|
|
|
|
.workflow-process-item {
|
|
padding: 12px;
|
|
background: var(--bp-surface-elevated);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 8px;
|
|
margin-bottom: 8px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.workflow-process-item:hover {
|
|
border-color: var(--bp-primary);
|
|
}
|
|
|
|
.workflow-process-name {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.workflow-process-meta {
|
|
font-size: 12px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
/* Task Inbox Panel */
|
|
.workflow-tasks-panel {
|
|
margin-top: 16px;
|
|
background: var(--bp-surface-elevated);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 12px;
|
|
max-height: 300px;
|
|
overflow: hidden;
|
|
display: none;
|
|
}
|
|
|
|
.workflow-tasks-panel.visible {
|
|
display: block;
|
|
}
|
|
|
|
.workflow-tasks-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--bp-border);
|
|
}
|
|
|
|
.workflow-tasks-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--bp-text);
|
|
}
|
|
|
|
.workflow-tasks-count {
|
|
padding: 2px 8px;
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
border-radius: 999px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.workflow-tasks-list {
|
|
max-height: 240px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.workflow-task-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--bp-border-subtle);
|
|
}
|
|
|
|
.workflow-task-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.workflow-task-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.workflow-task-name {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--bp-text);
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.workflow-task-process {
|
|
font-size: 11px;
|
|
color: var(--bp-text-muted);
|
|
}
|
|
|
|
.workflow-task-action {
|
|
padding: 6px 12px;
|
|
background: var(--bp-primary);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Loading Overlay */
|
|
.workflow-loading {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 100;
|
|
}
|
|
|
|
.workflow-loading-spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 3px solid var(--bp-border);
|
|
border-top-color: var(--bp-primary);
|
|
border-radius: 50%;
|
|
animation: workflow-spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes workflow-spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Toast Notifications */
|
|
.workflow-toast {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 24px;
|
|
padding: 12px 20px;
|
|
background: var(--bp-surface);
|
|
border: 1px solid var(--bp-border);
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
z-index: 200;
|
|
animation: workflow-toast-in 0.3s ease;
|
|
}
|
|
|
|
.workflow-toast.success {
|
|
border-color: var(--bp-success);
|
|
}
|
|
|
|
.workflow-toast.error {
|
|
border-color: var(--bp-danger);
|
|
}
|
|
|
|
@keyframes workflow-toast-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_html() -> str:
|
|
"""HTML fuer das Workflow/BPMN Panel."""
|
|
return """
|
|
<!-- WORKFLOW/BPMN PANEL -->
|
|
<div id="panel-workflow" class="module-panel">
|
|
<!-- Header -->
|
|
<div class="workflow-header">
|
|
<div>
|
|
<h1 class="workflow-title">BPMN Workflow Editor</h1>
|
|
<p class="workflow-subtitle">Geschaeftsprozesse modellieren und automatisieren (Camunda 7)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toolbar -->
|
|
<div class="workflow-toolbar">
|
|
<div class="workflow-toolbar-group">
|
|
<button class="workflow-btn primary" onclick="workflowNewDiagram()" title="Neues Diagramm">
|
|
<span>➕</span> Neu
|
|
</button>
|
|
<button class="workflow-btn" onclick="workflowOpenFile()" title="BPMN-Datei oeffnen">
|
|
<span>📂</span> Oeffnen
|
|
</button>
|
|
<input type="file" id="workflow-file-input" accept=".bpmn,.xml" style="display:none" onchange="workflowLoadFile(event)">
|
|
</div>
|
|
|
|
<div class="workflow-toolbar-separator"></div>
|
|
|
|
<div class="workflow-toolbar-group">
|
|
<button class="workflow-btn" onclick="workflowSaveXML()" title="Als XML speichern">
|
|
<span>💾</span> XML
|
|
</button>
|
|
<button class="workflow-btn" onclick="workflowSaveSVG()" title="Als SVG exportieren">
|
|
<span>🎨</span> SVG
|
|
</button>
|
|
</div>
|
|
|
|
<div class="workflow-toolbar-separator"></div>
|
|
|
|
<div class="workflow-toolbar-group">
|
|
<button class="workflow-btn success" onclick="workflowDeploy()" title="In Camunda deployen">
|
|
<span>🚀</span> Deployen
|
|
</button>
|
|
<button class="workflow-btn" onclick="workflowShowProcesses()" title="Deployments anzeigen">
|
|
<span>📋</span> Prozesse
|
|
</button>
|
|
<button class="workflow-btn" onclick="workflowToggleTasks()" title="Offene Tasks anzeigen">
|
|
<span>📝</span> Tasks
|
|
</button>
|
|
</div>
|
|
|
|
<div class="workflow-toolbar-separator"></div>
|
|
|
|
<div class="workflow-toolbar-group">
|
|
<button class="workflow-btn" onclick="workflowZoomIn()" title="Vergroessern">🔍+</button>
|
|
<button class="workflow-btn" onclick="workflowZoomOut()" title="Verkleinern">🔍-</button>
|
|
<button class="workflow-btn" onclick="workflowZoomFit()" title="Einpassen">◼</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- BPMN Canvas -->
|
|
<div class="workflow-canvas-container">
|
|
<div class="workflow-canvas" id="workflow-canvas"></div>
|
|
<div class="workflow-status-bar">
|
|
<div class="workflow-status-item">
|
|
<span class="workflow-status-dot" id="workflow-camunda-status"></span>
|
|
<span id="workflow-camunda-status-text">Camunda: Pruefe...</span>
|
|
</div>
|
|
<div class="workflow-status-item">
|
|
<span id="workflow-element-count">Elemente: 0</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Task Inbox (toggleable) -->
|
|
<div class="workflow-tasks-panel" id="workflow-tasks-panel">
|
|
<div class="workflow-tasks-header">
|
|
<span class="workflow-tasks-title">Offene Tasks</span>
|
|
<span class="workflow-tasks-count" id="workflow-tasks-count">0</span>
|
|
</div>
|
|
<div class="workflow-tasks-list" id="workflow-tasks-list">
|
|
<div class="workflow-task-item" style="color: var(--bp-text-muted); text-align: center;">
|
|
Keine offenen Tasks
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Processes Side Panel -->
|
|
<div class="workflow-processes-panel" id="workflow-processes-panel">
|
|
<div class="workflow-processes-header">
|
|
<span class="workflow-processes-title">Deployed Processes</span>
|
|
<button class="workflow-processes-close" onclick="workflowHideProcesses()">×</button>
|
|
</div>
|
|
<div class="workflow-processes-list" id="workflow-processes-list">
|
|
<div style="text-align: center; color: var(--bp-text-muted); padding: 20px;">
|
|
Lade Prozesse...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- bpmn-js CDN Scripts -->
|
|
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@17.11.1/dist/assets/diagram-js.css">
|
|
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@17.11.1/dist/assets/bpmn-js.css">
|
|
<link rel="stylesheet" href="https://unpkg.com/bpmn-js@17.11.1/dist/assets/bpmn-font/css/bpmn-embedded.css">
|
|
<script src="https://unpkg.com/bpmn-js@17.11.1/dist/bpmn-modeler.production.min.js"></script>
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_js() -> str:
|
|
"""JavaScript fuer das Workflow/BPMN Panel."""
|
|
return """
|
|
// ==========================================
|
|
// WORKFLOW/BPMN MODULE
|
|
// ==========================================
|
|
|
|
console.log('Workflow Module loaded');
|
|
|
|
let workflowModeler = null;
|
|
let workflowCamundaConnected = false;
|
|
|
|
// Default empty BPMN diagram
|
|
const WORKFLOW_EMPTY_BPMN = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
|
|
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
|
|
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
|
|
xmlns:camunda="http://camunda.org/schema/1.0/bpmn"
|
|
id="Definitions_1"
|
|
targetNamespace="http://bpmn.io/schema/bpmn">
|
|
<bpmn:process id="Process_1" name="Neuer Prozess" isExecutable="true">
|
|
<bpmn:startEvent id="StartEvent_1" name="Start" />
|
|
</bpmn:process>
|
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
|
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
|
|
<dc:Bounds x="180" y="160" width="36" height="36" />
|
|
<bpmndi:BPMNLabel>
|
|
<dc:Bounds x="186" y="203" width="24" height="14" />
|
|
</bpmndi:BPMNLabel>
|
|
</bpmndi:BPMNShape>
|
|
</bpmndi:BPMNPlane>
|
|
</bpmndi:BPMNDiagram>
|
|
</bpmn:definitions>`;
|
|
|
|
// Initialize BPMN Modeler
|
|
async function initWorkflowModule() {
|
|
console.log('Initializing Workflow Module...');
|
|
|
|
const container = document.getElementById('workflow-canvas');
|
|
if (!container) {
|
|
console.error('Workflow canvas container not found');
|
|
return;
|
|
}
|
|
|
|
// Check if BpmnJS is loaded
|
|
if (typeof BpmnJS === 'undefined') {
|
|
console.error('bpmn-js not loaded');
|
|
workflowShowToast('bpmn-js konnte nicht geladen werden', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create modeler instance
|
|
workflowModeler = new BpmnJS({
|
|
container: container,
|
|
keyboard: {
|
|
bindTo: document
|
|
}
|
|
});
|
|
|
|
// Load empty diagram
|
|
await workflowModeler.importXML(WORKFLOW_EMPTY_BPMN);
|
|
|
|
// Center the diagram
|
|
const canvas = workflowModeler.get('canvas');
|
|
canvas.zoom('fit-viewport');
|
|
|
|
// Update element count on changes
|
|
workflowModeler.on('elements.changed', workflowUpdateElementCount);
|
|
workflowUpdateElementCount();
|
|
|
|
console.log('Workflow Modeler initialized');
|
|
|
|
// Check Camunda connection
|
|
workflowCheckCamundaStatus();
|
|
|
|
} catch (err) {
|
|
console.error('Error initializing workflow modeler:', err);
|
|
workflowShowToast('Fehler beim Initialisieren des Editors', 'error');
|
|
}
|
|
}
|
|
|
|
// Check Camunda connection status
|
|
async function workflowCheckCamundaStatus() {
|
|
const statusDot = document.getElementById('workflow-camunda-status');
|
|
const statusText = document.getElementById('workflow-camunda-status-text');
|
|
|
|
try {
|
|
const response = await fetch('/api/bpmn/health');
|
|
const data = await response.json();
|
|
|
|
if (data.connected) {
|
|
statusDot.classList.add('connected');
|
|
statusDot.classList.remove('disconnected');
|
|
statusText.textContent = 'Camunda: Verbunden';
|
|
workflowCamundaConnected = true;
|
|
} else {
|
|
throw new Error('Not connected');
|
|
}
|
|
} catch (err) {
|
|
statusDot.classList.add('disconnected');
|
|
statusDot.classList.remove('connected');
|
|
statusText.textContent = 'Camunda: Nicht verbunden';
|
|
workflowCamundaConnected = false;
|
|
}
|
|
}
|
|
|
|
// Create new diagram
|
|
async function workflowNewDiagram() {
|
|
if (!workflowModeler) return;
|
|
|
|
try {
|
|
await workflowModeler.importXML(WORKFLOW_EMPTY_BPMN);
|
|
workflowModeler.get('canvas').zoom('fit-viewport');
|
|
workflowUpdateElementCount();
|
|
workflowShowToast('Neues Diagramm erstellt', 'success');
|
|
} catch (err) {
|
|
console.error('Error creating new diagram:', err);
|
|
workflowShowToast('Fehler beim Erstellen', 'error');
|
|
}
|
|
}
|
|
|
|
// Open file dialog
|
|
function workflowOpenFile() {
|
|
document.getElementById('workflow-file-input').click();
|
|
}
|
|
|
|
// Load file from input
|
|
async function workflowLoadFile(event) {
|
|
const file = event.target.files[0];
|
|
if (!file) return;
|
|
|
|
try {
|
|
const xml = await file.text();
|
|
await workflowModeler.importXML(xml);
|
|
workflowModeler.get('canvas').zoom('fit-viewport');
|
|
workflowUpdateElementCount();
|
|
workflowShowToast('Datei geladen: ' + file.name, 'success');
|
|
} catch (err) {
|
|
console.error('Error loading file:', err);
|
|
workflowShowToast('Fehler beim Laden der Datei', 'error');
|
|
}
|
|
|
|
// Reset input
|
|
event.target.value = '';
|
|
}
|
|
|
|
// Save as XML
|
|
async function workflowSaveXML() {
|
|
if (!workflowModeler) return;
|
|
|
|
try {
|
|
const { xml } = await workflowModeler.saveXML({ format: true });
|
|
workflowDownload(xml, 'process.bpmn', 'application/xml');
|
|
workflowShowToast('XML exportiert', 'success');
|
|
} catch (err) {
|
|
console.error('Error saving XML:', err);
|
|
workflowShowToast('Fehler beim Speichern', 'error');
|
|
}
|
|
}
|
|
|
|
// Save as SVG
|
|
async function workflowSaveSVG() {
|
|
if (!workflowModeler) return;
|
|
|
|
try {
|
|
const { svg } = await workflowModeler.saveSVG();
|
|
workflowDownload(svg, 'process.svg', 'image/svg+xml');
|
|
workflowShowToast('SVG exportiert', 'success');
|
|
} catch (err) {
|
|
console.error('Error saving SVG:', err);
|
|
workflowShowToast('Fehler beim Speichern', 'error');
|
|
}
|
|
}
|
|
|
|
// Deploy to Camunda
|
|
async function workflowDeploy() {
|
|
if (!workflowModeler) return;
|
|
|
|
if (!workflowCamundaConnected) {
|
|
workflowShowToast('Camunda nicht verbunden', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { xml } = await workflowModeler.saveXML({ format: true });
|
|
|
|
// Create form data
|
|
const formData = new FormData();
|
|
formData.append('deployment-name', 'BreakPilot-Process-' + Date.now());
|
|
formData.append('data', new Blob([xml], { type: 'application/octet-stream' }), 'process.bpmn');
|
|
|
|
const response = await fetch('/api/bpmn/deployment/create', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
workflowShowToast('Deployment erfolgreich: ' + result.name, 'success');
|
|
console.log('Deployment result:', result);
|
|
} else {
|
|
const error = await response.text();
|
|
throw new Error(error);
|
|
}
|
|
} catch (err) {
|
|
console.error('Error deploying:', err);
|
|
workflowShowToast('Deployment fehlgeschlagen', 'error');
|
|
}
|
|
}
|
|
|
|
// Show deployed processes panel
|
|
async function workflowShowProcesses() {
|
|
const panel = document.getElementById('workflow-processes-panel');
|
|
const list = document.getElementById('workflow-processes-list');
|
|
|
|
panel.classList.add('open');
|
|
|
|
try {
|
|
const response = await fetch('/api/bpmn/process-definition');
|
|
const processes = await response.json();
|
|
|
|
if (processes.length === 0) {
|
|
list.innerHTML = '<div style="text-align: center; color: var(--bp-text-muted); padding: 20px;">Keine Prozesse deployed</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = processes.map(p => `
|
|
<div class="workflow-process-item" onclick="workflowLoadProcess('${p.id}')">
|
|
<div class="workflow-process-name">${p.name || p.key}</div>
|
|
<div class="workflow-process-meta">Version ${p.version} | ${p.key}</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (err) {
|
|
console.error('Error loading processes:', err);
|
|
list.innerHTML = '<div style="text-align: center; color: var(--bp-danger); padding: 20px;">Fehler beim Laden</div>';
|
|
}
|
|
}
|
|
|
|
// Hide processes panel
|
|
function workflowHideProcesses() {
|
|
document.getElementById('workflow-processes-panel').classList.remove('open');
|
|
}
|
|
|
|
// Load process definition XML
|
|
async function workflowLoadProcess(definitionId) {
|
|
try {
|
|
const response = await fetch('/api/bpmn/process-definition/' + definitionId + '/xml');
|
|
const data = await response.json();
|
|
|
|
if (data.bpmn20Xml) {
|
|
await workflowModeler.importXML(data.bpmn20Xml);
|
|
workflowModeler.get('canvas').zoom('fit-viewport');
|
|
workflowUpdateElementCount();
|
|
workflowHideProcesses();
|
|
workflowShowToast('Prozess geladen', 'success');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error loading process:', err);
|
|
workflowShowToast('Fehler beim Laden des Prozesses', 'error');
|
|
}
|
|
}
|
|
|
|
// Toggle tasks panel
|
|
function workflowToggleTasks() {
|
|
const panel = document.getElementById('workflow-tasks-panel');
|
|
panel.classList.toggle('visible');
|
|
|
|
if (panel.classList.contains('visible')) {
|
|
workflowLoadTasks();
|
|
}
|
|
}
|
|
|
|
// Load pending tasks
|
|
async function workflowLoadTasks() {
|
|
const list = document.getElementById('workflow-tasks-list');
|
|
const count = document.getElementById('workflow-tasks-count');
|
|
|
|
try {
|
|
const response = await fetch('/api/bpmn/tasks/pending');
|
|
const tasks = await response.json();
|
|
|
|
count.textContent = tasks.length;
|
|
|
|
if (tasks.length === 0) {
|
|
list.innerHTML = '<div class="workflow-task-item" style="color: var(--bp-text-muted); text-align: center; justify-content: center;">Keine offenen Tasks</div>';
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = tasks.map(t => `
|
|
<div class="workflow-task-item">
|
|
<div class="workflow-task-info">
|
|
<div class="workflow-task-name">${t.name}</div>
|
|
<div class="workflow-task-process">${t.processDefinitionId || 'Prozess'}</div>
|
|
</div>
|
|
<button class="workflow-task-action" onclick="workflowCompleteTask('${t.id}')">Erledigen</button>
|
|
</div>
|
|
`).join('');
|
|
|
|
} catch (err) {
|
|
console.error('Error loading tasks:', err);
|
|
list.innerHTML = '<div class="workflow-task-item" style="color: var(--bp-danger);">Fehler beim Laden</div>';
|
|
}
|
|
}
|
|
|
|
// Complete a task
|
|
async function workflowCompleteTask(taskId) {
|
|
try {
|
|
const response = await fetch('/api/bpmn/task/' + taskId + '/complete', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({})
|
|
});
|
|
|
|
if (response.ok) {
|
|
workflowShowToast('Task abgeschlossen', 'success');
|
|
workflowLoadTasks();
|
|
} else {
|
|
throw new Error('Failed to complete task');
|
|
}
|
|
} catch (err) {
|
|
console.error('Error completing task:', err);
|
|
workflowShowToast('Fehler beim Abschliessen', 'error');
|
|
}
|
|
}
|
|
|
|
// Zoom controls
|
|
function workflowZoomIn() {
|
|
if (!workflowModeler) return;
|
|
const canvas = workflowModeler.get('canvas');
|
|
canvas.zoom(canvas.zoom() * 1.2);
|
|
}
|
|
|
|
function workflowZoomOut() {
|
|
if (!workflowModeler) return;
|
|
const canvas = workflowModeler.get('canvas');
|
|
canvas.zoom(canvas.zoom() / 1.2);
|
|
}
|
|
|
|
function workflowZoomFit() {
|
|
if (!workflowModeler) return;
|
|
workflowModeler.get('canvas').zoom('fit-viewport');
|
|
}
|
|
|
|
// Update element count
|
|
function workflowUpdateElementCount() {
|
|
if (!workflowModeler) return;
|
|
|
|
const elementRegistry = workflowModeler.get('elementRegistry');
|
|
const count = elementRegistry.getAll().length;
|
|
document.getElementById('workflow-element-count').textContent = 'Elemente: ' + count;
|
|
}
|
|
|
|
// Download helper
|
|
function workflowDownload(content, filename, contentType) {
|
|
const blob = new Blob([content], { type: contentType });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
// Toast notification
|
|
function workflowShowToast(message, type = 'info') {
|
|
const toast = document.createElement('div');
|
|
toast.className = 'workflow-toast ' + type;
|
|
toast.textContent = message;
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.remove();
|
|
}, 3000);
|
|
}
|
|
|
|
// Module loader function
|
|
function loadWorkflowModule() {
|
|
console.log('Loading Workflow Module...');
|
|
// Small delay to ensure DOM is ready
|
|
setTimeout(initWorkflowModule, 100);
|
|
}
|
|
|
|
// Show panel function for module loader
|
|
function showWorkflowPanel() {
|
|
loadWorkflowModule();
|
|
}
|
|
|
|
// Expose globally
|
|
window.loadWorkflowModule = loadWorkflowModule;
|
|
window.workflowNewDiagram = workflowNewDiagram;
|
|
window.workflowOpenFile = workflowOpenFile;
|
|
window.workflowLoadFile = workflowLoadFile;
|
|
window.workflowSaveXML = workflowSaveXML;
|
|
window.workflowSaveSVG = workflowSaveSVG;
|
|
window.workflowDeploy = workflowDeploy;
|
|
window.workflowShowProcesses = workflowShowProcesses;
|
|
window.workflowHideProcesses = workflowHideProcesses;
|
|
window.workflowLoadProcess = workflowLoadProcess;
|
|
window.workflowToggleTasks = workflowToggleTasks;
|
|
window.workflowCompleteTask = workflowCompleteTask;
|
|
window.workflowZoomIn = workflowZoomIn;
|
|
window.workflowZoomOut = workflowZoomOut;
|
|
window.workflowZoomFit = workflowZoomFit;
|
|
"""
|