# Automation Templates – Frontend Implementation Concept **Basis:** `doc_automation_templates_db_and_editor_concept.md` --- ## ✅ STATUS: IMPLEMENTIERT (2026-02-03) **Backend ist vollständig implementiert:** - ✅ API: `GET/POST/PUT/DELETE /api/automation-templates` - ✅ Model: `AutomationTemplate` mit `TextMultilingual` Feldern - ✅ Navigation: `/workflows/automation-templates` in mainSystem.py - ✅ API: `GET /api/automations/actions` - Actions-Katalog **Frontend implementiert:** | Komponente | Status | |------------|--------| | `automationApi.ts` - Typen + API-Funktionen | ✅ Implementiert | | `useAutomations.ts` - Hooks (useWorkflowActions) | ✅ Implementiert | | `AutomationTemplatesPage.tsx` | ✅ Implementiert | | `AutomationEditor.tsx` | ✅ **NEU** - Full-screen Editor | | `ActionsPanel.tsx` | ✅ Implementiert | | `App.tsx` - Route | ✅ Implementiert | | `pageRegistry.tsx` - Icon | ✅ Implementiert | **Neue Dateien:** - `src/components/AutomationEditor/AutomationEditor.tsx` - `src/components/AutomationEditor/AutomationEditor.module.css` - `src/components/AutomationEditor/index.ts` --- ## WICHTIGE HINWEISE ### FormGenerator: Automatische Multilingual-Unterstützung | Komponente | TextMultilingual-Handling | |------------|---------------------------| | **FormGeneratorTable** | ✅ **Automatisch** - erkennt TextMultilingual und rendert in User-Sprache | | **FormGeneratorForm** | ✅ **Automatisch** - mit `type: 'multilingual'` in Attribut-Definition | **Kein manueller Code nötig!** Einfach Daten mit TextMultilingual-Feldern übergeben. ### Bestehende vs. Neue API-Typen | Typ | Quelle | Verwendung | |-----|--------|------------| | `AutomationTemplate` | `subAutomationTemplates.py` (Legacy) | Alte hardcoded Templates | | `AutomationTemplateDB` | DB (neu) | Neue datenbankbasierte Templates | Die bestehende `fetchAutomationTemplates()` Funktion lädt Legacy-Templates. Für die neuen DB-Templates wird `fetchAutomationTemplatesDB()` verwendet. --- ## 1. Übersicht der Komponenten | Komponente | Zweck | Status | |------------|-------|--------| | `automationApi.ts` | API-Funktionen für Templates + Actions | Erweitern | | `useAutomations.ts` | Hooks für Templates + Actions | Erweitern | | `AutomationsPage.tsx` | Liste der Definitions; öffnet Editor | Erweitern | | `AutomationTemplatesPage.tsx` | Liste der Templates (CRUD) | **Neu** | | `AutomationEditor.tsx` | Editor mit Modus-Flag (Definition/Template) | **Neu** | | `ActionsPanel.tsx` | Actions-Katalog mit Copy/Paste | **Neu** | --- ## 2. API-Erweiterungen (automationApi.ts) ### 2.1 Neuer Typ: AutomationTemplate (DB-Version, MultiLanguage) ```typescript // Multilingual text type (matches backend TextMultilingual) export interface TextMultilingual { en: string; ge?: string; fr?: string; it?: string; } // NEW: AutomationTemplate from DB (not the old in-memory structure) export interface AutomationTemplateDB { id: string; label: TextMultilingual; overview?: TextMultilingual; template: string; // JSON string with {{KEY:...}} placeholders _createdAt?: number; _createdBy?: string; _createdByUserName?: string; } // Action definition from backend export interface WorkflowAction { method: string; action: string; actionId: string; description: string; category?: string; parameters: WorkflowActionParameter[]; exampleJson: { execMethod: string; execAction: string; execParameters: Record; execResultLabel: string; }; } export interface WorkflowActionParameter { name: string; type: string; frontendType: string; required: boolean; default?: any; description: string; frontendOptions?: string | string[]; } ``` ### 2.2 Neue API-Funktionen ```typescript // ============================================================================ // AUTOMATION TEMPLATES (DB) API // ============================================================================ /** * Fetch all automation templates (RBAC-filtered: own templates) * Endpoint: GET /api/automation-templates */ export async function fetchAutomationTemplatesDB( request: ApiRequestFunction ): Promise { const data = await request({ url: '/api/automation-templates', method: 'get' }); if (data?.items && Array.isArray(data.items)) { return data.items; } return Array.isArray(data) ? data : []; } /** * Fetch single template by ID * Endpoint: GET /api/automation-templates/{id} */ export async function fetchAutomationTemplateById( request: ApiRequestFunction, templateId: string ): Promise { try { return await request({ url: `/api/automation-templates/${templateId}`, method: 'get' }); } catch (error) { console.error('Error fetching template:', error); return null; } } /** * Create new automation template * Endpoint: POST /api/automation-templates */ export async function createAutomationTemplateApi( request: ApiRequestFunction, templateData: Omit ): Promise { return await request({ url: '/api/automation-templates', method: 'post', data: templateData }); } /** * Update automation template * Endpoint: PUT /api/automation-templates/{id} */ export async function updateAutomationTemplateApi( request: ApiRequestFunction, templateId: string, templateData: Partial ): Promise { return await request({ url: `/api/automation-templates/${templateId}`, method: 'put', data: templateData }); } /** * Delete automation template * Endpoint: DELETE /api/automation-templates/{id} */ export async function deleteAutomationTemplateApi( request: ApiRequestFunction, templateId: string ): Promise { await request({ url: `/api/automation-templates/${templateId}`, method: 'delete' }); } // ============================================================================ // WORKFLOW ACTIONS API // ============================================================================ /** * Fetch available workflow actions (RBAC-filtered) * Endpoint: GET /api/automations/actions */ export async function fetchWorkflowActions( request: ApiRequestFunction ): Promise { const data = await request({ url: '/api/automations/actions', method: 'get' }); return data?.actions || []; } ``` --- ## 3. Hooks-Erweiterungen (useAutomations.ts) ### 3.1 Neuer Hook: useAutomationTemplates ```typescript /** * Hook for managing AutomationTemplates (DB) */ export function useAutomationTemplates() { const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const { request } = useApiRequest(); const { checkPermission } = usePermissions(); const [permissions, setPermissions] = useState(null); const fetchTemplates = useCallback(async () => { setLoading(true); try { const data = await fetchAutomationTemplatesDB(request); setTemplates(data); } catch (e: any) { setError(e.message); setTemplates([]); } finally { setLoading(false); } }, [request]); const fetchPermissions = useCallback(async () => { const perms = await checkPermission('DATA', 'AutomationTemplate'); setPermissions(perms); return perms; }, [checkPermission]); const createTemplate = useCallback(async (data: Omit) => { return await createAutomationTemplateApi(request, data); }, [request]); const updateTemplate = useCallback(async (id: string, data: Partial) => { return await updateAutomationTemplateApi(request, id, data); }, [request]); const deleteTemplate = useCallback(async (id: string) => { await deleteAutomationTemplateApi(request, id); }, [request]); return { templates, loading, error, permissions, fetchTemplates, fetchPermissions, createTemplate, updateTemplate, deleteTemplate, }; } ``` ### 3.2 Neuer Hook: useWorkflowActions ```typescript /** * Hook for fetching available workflow actions (RBAC-filtered) */ export function useWorkflowActions() { const [actions, setActions] = useState([]); const [loading, setLoading] = useState(false); const { request } = useApiRequest(); const fetchActions = useCallback(async () => { setLoading(true); try { const data = await fetchWorkflowActions(request); setActions(data); } catch (e) { console.error('Error fetching actions:', e); setActions([]); } finally { setLoading(false); } }, [request]); return { actions, loading, fetchActions }; } ``` --- ## 4. Neue Seite: AutomationTemplatesPage.tsx ### 4.1 Imports ```typescript import React, { useState, useMemo, useEffect } from 'react'; import { FaSync, FaPlus, FaFileAlt } from 'react-icons/fa'; import { useLanguage } from '../../providers/language/LanguageContext'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { useAutomationTemplates, AutomationTemplateDB, TextMultilingual } from '../../hooks/useAutomations'; import { AutomationEditor } from '../../components/AutomationEditor'; // Neu import styles from '../admin/Admin.module.css'; ``` ### 4.2 Struktur ```typescript /** * AutomationTemplatesPage * * CRUD für AutomationTemplates (eigene Templates verwalten). * Nutzt FormGeneratorTable wie AutomationsPage. */ export const AutomationTemplatesPage: React.FC = () => { const { templates, loading, permissions, fetchTemplates, fetchPermissions, createTemplate, updateTemplate, deleteTemplate, } = useAutomationTemplates(); const [showEditor, setShowEditor] = useState(false); const [editingTemplate, setEditingTemplate] = useState(null); useEffect(() => { fetchTemplates(); fetchPermissions(); }, []); // Check permissions const canCreate = permissions?.create !== 'n'; const canUpdate = permissions?.update !== 'n'; const canDelete = permissions?.delete !== 'n'; // Open Editor in "Template mode" const handleEdit = (template: AutomationTemplateDB) => { setEditingTemplate(template); setShowEditor(true); }; const handleCreate = () => { setEditingTemplate(null); // New template setShowEditor(true); }; const handleSave = async (data: Partial) => { if (editingTemplate) { await updateTemplate(editingTemplate.id, data); } else { await createTemplate(data as any); } setShowEditor(false); fetchTemplates(); }; // Columns - FormGeneratorTable rendert TextMultilingual automatisch in User-Sprache! const columns = useMemo(() => [ { key: 'label', label: 'Label', type: 'string' as const, sortable: true, searchable: true }, { key: 'overview', label: 'Beschreibung', type: 'string' as const, width: 300 }, { key: '_createdByUserName', label: 'Erstellt von', type: 'string' as const }, ], []); // Handle delete const handleDelete = async (template: AutomationTemplateDB) => { await deleteTemplate(template.id); fetchTemplates(); }; return (

Automation-Vorlagen

Verwalten Sie Ihre Workflow-Vorlagen

{canCreate && ( )}
{showEditor && ( setShowEditor(false)} /> )}
); }; ``` --- ## 5. Neue Komponente: AutomationEditor.tsx ### 5.1 Konzept: Saubere Lösung via FormGeneratorForm **Kernidee:** FormGeneratorForm nutzen für ALLE Felder inkl. Multilingual! ```typescript /** * Attribute-Definitionen je nach Modus * FormGeneratorForm rendert type:'multilingual' automatisch mit Sprach-Tabs */ function getEditorAttributes(mode: 'definition' | 'template'): AttributeDefinition[] { const baseAttributes: AttributeDefinition[] = [ { name: 'template', label: 'Template JSON', type: 'textarea', required: true, minRows: 15, maxRows: 30, description: 'Workflow-Definition als JSON' } ]; if (mode === 'template') { return [ { name: 'label', label: 'Label', type: 'multilingual', required: true }, { name: 'overview', label: 'Beschreibung', type: 'multilingual' }, ...baseAttributes ]; } else { return [ { name: 'label', label: 'Label', type: 'text', required: true }, { name: 'schedule', label: 'Schedule (Cron)', type: 'text' }, { name: 'active', label: 'Aktiv', type: 'checkbox' }, ...baseAttributes, { name: 'placeholders', label: 'Platzhalter', type: 'textarea', description: 'JSON-Objekt' } ]; } } ``` ### 5.2 Komponente ```typescript interface AutomationEditorProps { mode: 'definition' | 'template'; initialData?: AutomationTemplateDB | Automation | null; onSave: (data: any) => Promise; onCancel: () => void; } export const AutomationEditor: React.FC = ({ mode, initialData, onSave, onCancel, }) => { const [showActionsPanel, setShowActionsPanel] = useState(false); // Daten für FormGeneratorForm vorbereiten const formData = useMemo(() => { if (!initialData) return {}; return { ...initialData, template: typeof initialData.template === 'string' ? initialData.template : JSON.stringify(initialData.template, null, 2) }; }, [initialData]); return (

{mode === 'template' ? 'Template bearbeiten' : 'Automation bearbeiten'}

{/* Main Form - FormGeneratorForm macht alles! */}
{/* Actions Panel (optional, für Copy/Paste) */}
{showActionsPanel && }
); }; ``` **Vorteile dieser Lösung:** - Kein manueller Code für Multilingual-Inputs - FormGeneratorForm validiert automatisch - Konsistent mit dem Rest der Anwendung - Weniger Code, weniger Bugs --- ## 6. Neue Komponente: ActionsPanel.tsx ### 6.1 Struktur ```typescript interface ActionsPanelProps { onInsert: (actionJson: string) => void; onCopy?: (actionJson: string) => void; } export const ActionsPanel: React.FC = ({ onInsert, onCopy }) => { const { actions, loading, fetchActions } = useWorkflowActions(); const [filter, setFilter] = useState(''); const [expandedAction, setExpandedAction] = useState(null); useEffect(() => { fetchActions(); }, []); const filteredActions = useMemo(() => { if (!filter) return actions; const lower = filter.toLowerCase(); return actions.filter(a => a.method.toLowerCase().includes(lower) || a.action.toLowerCase().includes(lower) || a.description.toLowerCase().includes(lower) ); }, [actions, filter]); // Group by method const groupedActions = useMemo(() => { const groups: Record = {}; filteredActions.forEach(action => { if (!groups[action.method]) { groups[action.method] = []; } groups[action.method].push(action); }); return groups; }, [filteredActions]); const handleCopy = (action: WorkflowAction) => { const json = JSON.stringify(action.exampleJson, null, 2); navigator.clipboard.writeText(json); onCopy?.(json); }; const handleInsert = (action: WorkflowAction) => { const json = JSON.stringify(action.exampleJson, null, 2); onInsert(json); }; return (

Verfügbare Actions

setFilter(e.target.value)} /> {loading ? (
Lade Actions...
) : (
{Object.entries(groupedActions).map(([method, methodActions]) => (

{method}

{methodActions.map(action => (
setExpandedAction( expandedAction === action.actionId ? null : action.actionId )} > {action.action} {action.description}
{expandedAction === action.actionId && (
Parameter:
    {action.parameters.map(p => (
  • {p.name} {p.required && *} : {p.description}
  • ))}
Beispiel JSON:
{JSON.stringify(action.exampleJson, null, 2)}
)}
))}
))}
)}
); }; ``` --- ## 7. Anpassungen in AutomationsPage.tsx ### 7.1 Editor-Integration Bestehende `handleEditClick` und Template-Auswahl anpassen: ```typescript // State for editor const [showEditor, setShowEditor] = useState(false); const [editorMode, setEditorMode] = useState<'definition' | 'template'>('definition'); const [editorInitialData, setEditorInitialData] = useState(null); // Edit existing definition const handleEditClick = async (automation: Automation) => { const fullAutomation = await fetchAutomationById(automation.id); setEditorInitialData(fullAutomation); setEditorMode('definition'); setShowEditor(true); }; // Create from template const handleCreateFromTemplate = async (template: AutomationTemplateDB) => { // Extract label in user's language const userLang = getUserLanguage(); // e.g., 'de' or 'en' const label = template.label[userLang] || template.label.en || 'New Automation'; // Pre-fill new definition const newDefinition: Partial = { label: label, template: template.template, placeholders: {}, schedule: '0 22 * * *', active: false, mandateId: mandateId, featureInstanceId: featureInstanceId, }; setEditorInitialData(newDefinition as Automation); setEditorMode('definition'); setShowEditor(true); }; // Save handler from editor const handleEditorSave = async (data: any) => { if (editorMode === 'definition') { if (editorInitialData?.id) { await handleAutomationUpdate(editorInitialData.id, data); } else { await handleAutomationCreate(data); } } // Template save is handled in AutomationTemplatesPage setShowEditor(false); refetch(); }; ``` ### 7.2 Template-Auswahl Modal anpassen Bisherige Template-Auswahl nutzt jetzt `AutomationTemplateDB` statt altes Format: ```typescript const [templatesDB, setTemplatesDB] = useState([]); const loadTemplates = async () => { setLoadingTemplates(true); const data = await fetchAutomationTemplatesDB(request); setTemplatesDB(data); setLoadingTemplates(false); }; // In Template-Modal: Display label in current language // Nutze inline-Zugriff oder FormGeneratorTable für Liste const { currentLanguage } = useLanguage(); const getLang = (ml: TextMultilingual | undefined) => ml?.[currentLanguage as keyof TextMultilingual] || ml?.en || ''; {templatesDB.map(template => (
handleCreateFromTemplate(template)}>

{getLang(template.label)}

{getLang(template.overview)}

))} ``` --- ## 8. Navigation / Routing ### Architektur-Hinweis: ObjectKey-Formate | Kontext | Format | Beispiel | Verwendung | |---------|--------|----------|------------| | Backend Navigation | `ui.system.xxx` | `ui.system.automations` | NAVIGATION_SECTIONS in mainSystem.py | | Frontend Icons | `page.system.xxx` | `page.system.automations` | pageRegistry.tsx PAGE_ICONS | | RBAC DATA | `data.automation.xxx` | `data.automation.AutomationTemplate` | Tabellen-Zugriffsregeln | | RBAC UI | `ui.feature.automation.xxx` | `ui.feature.automation.templates` | Feature-UI-Objekte | Die Sidebar-Navigation kommt vom **Backend** (`/api/navigation`), nicht aus pageRegistry. PageRegistry liefert nur die **Icons** zu den vom Backend gelieferten Items. --- ### 8.1 Backend: mainSystem.py (NAVIGATION_SECTIONS) **WICHTIG:** Die Navigation wird vom Backend gesteuert! In `gateway/modules/system/mainSystem.py` unter `NAVIGATION_SECTIONS` im Abschnitt "workflows" hinzufügen: ```python { "id": "automation-templates", "objectKey": "ui.system.automation-templates", "label": {"en": "Templates", "de": "Vorlagen", "fr": "Modèles"}, "icon": "FaFileAlt", # Oder FaCopy für Vorlagen "path": "/workflows/automation-templates", "order": 35, # Nach automations (30) "public": True, }, ``` ### 8.2 Frontend: App.tsx Route innerhalb des `` Blocks hinzufügen: ```typescript // In App.tsx, im workflows-Block: } /> } /> } /> } /> {/* NEU */} ``` **Import hinzufügen:** ```typescript import { AutomationTemplatesPage } from './pages/workflows/AutomationTemplatesPage'; ``` ### 8.3 Frontend: pageRegistry.tsx **WICHTIG:** Format ist `page.system.xxx`, nicht `ui.system.xxx`! ```typescript // Import hinzufügen (FaFileAlt ist bereits importiert): // import { ..., FaFileAlt, ... } from 'react-icons/fa'; // In PAGE_ICONS hinzufügen: 'page.system.automation-templates': , ``` ### 8.4 Frontend: pages/workflows/index.ts Export hinzufügen: ```typescript export { PlaygroundPage } from './PlaygroundPage'; export { WorkflowsPage } from './WorkflowsPage'; export { AutomationsPage } from './AutomationsPage'; export { AutomationTemplatesPage } from './AutomationTemplatesPage'; // NEU ``` ### 8.5 Locales (de.ts, en.ts, fr.ts) Übersetzungen hinzufügen: **de.ts:** ```typescript 'nav.automation-templates': 'Vorlagen', 'automation-templates.title': 'Automation-Vorlagen', 'automation-templates.create': 'Neue Vorlage', 'automation-templates.edit': 'Vorlage bearbeiten', 'automation-templates.empty': 'Keine Vorlagen vorhanden', ``` **en.ts:** ```typescript 'nav.automation-templates': 'Templates', 'automation-templates.title': 'Automation Templates', 'automation-templates.create': 'New Template', 'automation-templates.edit': 'Edit Template', 'automation-templates.empty': 'No templates available', ``` --- ## 9. Hilfsfunktionen > **Hinweis:** `getMultilingualText()` ist NICHT nötig - FormGeneratorTable rendert TextMultilingual automatisch in der User-Sprache via `formatTextMultilingual()` intern. ### 9.1 extractPlaceholdersFromJson ```typescript /** * Extract {{KEY:name}} placeholders from JSON string */ export function extractPlaceholdersFromJson(jsonString: string): string[] { const regex = /\{\{KEY:(\w+)\}\}/g; const keys: string[] = []; let match; while ((match = regex.exec(jsonString)) !== null) { if (!keys.includes(match[1])) { keys.push(match[1]); } } return keys; } ``` --- ## 10. Zusammenfassung der Dateien | Datei | Änderung | |-------|----------| | `api/automationApi.ts` | Neue Typen + API-Funktionen für Templates (DB) und Actions | | `hooks/useAutomations.ts` | Neue Hooks: `useAutomationTemplates`, `useWorkflowActions` | | `pages/workflows/AutomationsPage.tsx` | Editor-Integration, Template-Auswahl mit DB-Templates | | `pages/workflows/AutomationTemplatesPage.tsx` | **Neu**: CRUD für eigene Templates | | `components/AutomationEditor/AutomationEditor.tsx` | **Neu**: Editor mit Modus-Flag | | `components/AutomationEditor/ActionsPanel.tsx` | **Neu**: Actions-Katalog | | `App.tsx` | Neue Route für AutomationTemplatesPage | | `config/pageRegistry.tsx` | Icon für neue Seite | --- ## 11. Implementierungsreihenfolge 1. **API-Typen und Funktionen** (`automationApi.ts`) 2. **Hooks** (`useAutomations.ts` erweitern) 3. **ActionsPanel** (unabhängig testbar) 4. **AutomationEditor** (Kern-Komponente) 5. **AutomationTemplatesPage** (CRUD für Templates) 6. **AutomationsPage anpassen** (Editor-Integration) 7. **Routing** (App.tsx, Navigation)