""" 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 """

BPMN Workflow Editor

Geschaeftsprozesse modellieren und automatisieren (Camunda 7)

Camunda: Pruefe...
Elemente: 0
Offene Tasks 0
Keine offenen Tasks
Deployed Processes
Lade Prozesse...
""" @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 = ` `; // 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 = '
Keine Prozesse deployed
'; return; } list.innerHTML = processes.map(p => `
${p.name || p.key}
Version ${p.version} | ${p.key}
`).join(''); } catch (err) { console.error('Error loading processes:', err); list.innerHTML = '
Fehler beim Laden
'; } } // 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 = '
Keine offenen Tasks
'; return; } list.innerHTML = tasks.map(t => `
${t.name}
${t.processDefinitionId || 'Prozess'}
`).join(''); } catch (err) { console.error('Error loading tasks:', err); list.innerHTML = '
Fehler beim Laden
'; } } // 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; """