fixed automation and trustee

This commit is contained in:
ValueOn AG 2026-01-24 02:06:54 +01:00
parent cc8770dec3
commit 41e02b5a2c
4 changed files with 93 additions and 86 deletions

View file

@ -7,6 +7,7 @@ import { ApiRequestOptions } from '../hooks/useApi';
export interface Automation { export interface Automation {
id: string; id: string;
mandateId: string; mandateId: string;
featureInstanceId: string;
label: string; label: string;
template: string | object; template: string | object;
placeholders: Record<string, string>; placeholders: Record<string, string>;
@ -51,6 +52,7 @@ export interface CreateAutomationRequest {
schedule?: string; schedule?: string;
active?: boolean; active?: boolean;
mandateId?: string; mandateId?: string;
featureInstanceId?: string;
} }
export interface UpdateAutomationRequest { export interface UpdateAutomationRequest {

View file

@ -56,91 +56,33 @@ export function useAutomations() {
const { request, isLoading: loading, error } = useApiRequest<null, Automation[]>(); const { request, isLoading: loading, error } = useApiRequest<null, Automation[]>();
const { checkPermission } = usePermissions(); const { checkPermission } = usePermissions();
// Fallback attributes for automation form // Fetch attributes from backend - no fallback, errors should be visible
const fallbackAttributes: AttributeDefinition[] = [
{
name: 'label',
type: 'text',
label: 'Name',
required: true,
editable: true,
visible: true,
sortable: true,
searchable: true,
width: 200,
},
{
name: 'schedule',
type: 'text',
label: 'Zeitplan (Cron)',
required: false,
editable: true,
visible: true,
description: 'z.B. "0 8 * * *" für täglich 8:00 Uhr',
width: 150,
},
{
name: 'template',
type: 'textarea',
label: 'Template',
required: false,
editable: true,
visible: true,
width: 200,
},
{
name: 'active',
type: 'checkbox',
label: 'Aktiv',
required: false,
editable: true,
visible: true,
default: true,
width: 80,
},
{
name: 'status',
type: 'text',
label: 'Status',
required: false,
editable: false,
visible: true,
readonly: true,
width: 100,
},
];
// Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
try { try {
const response = await api.get('/api/attributes/AutomationDefinition'); const response = await api.get('/api/automations/attributes');
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { // Backend returns: { attributes: { model: "...", attributes: [...] } }
// So we need to access response.data.attributes.attributes
if (response.data?.attributes?.attributes && Array.isArray(response.data.attributes.attributes)) {
attrs = response.data.attributes.attributes;
} else if (response.data?.attributes && Array.isArray(response.data.attributes)) {
// Fallback: if attributes is directly an array
attrs = response.data.attributes; attrs = response.data.attributes;
} else if (Array.isArray(response.data)) { } else if (Array.isArray(response.data)) {
attrs = response.data; attrs = response.data;
} else if (response.data && typeof response.data === 'object') {
const keys = Object.keys(response.data);
for (const key of keys) {
if (Array.isArray(response.data[key])) {
attrs = response.data[key];
break;
}
}
} }
// Use fallback if no attributes returned
if (attrs.length === 0) { if (attrs.length === 0) {
attrs = fallbackAttributes; console.warn('No attributes returned from backend for AutomationDefinition');
} }
setAttributes(attrs); setAttributes(attrs);
return attrs; return attrs;
} catch (error: any) { } catch (error: any) {
console.error('Error fetching automation attributes, using fallback:', error); console.error('Error fetching automation attributes:', error);
setAttributes(fallbackAttributes); setAttributes([]);
return fallbackAttributes; return [];
} }
}, []); }, []);
@ -322,13 +264,24 @@ export function useAutomationOperations() {
setCreateError(null); setCreateError(null);
try { try {
// Get mandateId from session storage // Validate required fields - mandateId and featureInstanceId must be provided
const currentUserJson = sessionStorage.getItem('currentUser'); if (!data.mandateId || !data.featureInstanceId) {
if (currentUserJson) { throw new Error('mandateId and featureInstanceId are required');
const currentUser = JSON.parse(currentUserJson); }
if (currentUser.mandateId) {
data.mandateId = currentUser.mandateId; // Convert placeholders to ensure all values are strings
if (data.placeholders) {
const convertedPlaceholders: Record<string, string> = {};
for (const [key, value] of Object.entries(data.placeholders)) {
if (value === null || value === undefined) {
convertedPlaceholders[key] = '';
} else if (typeof value === 'object') {
convertedPlaceholders[key] = JSON.stringify(value);
} else {
convertedPlaceholders[key] = String(value);
}
} }
data.placeholders = convertedPlaceholders;
} }
const newAutomation = await createAutomationApi(request, data); const newAutomation = await createAutomationApi(request, data);

View file

@ -127,6 +127,11 @@ export function useCanViewFeatureView(viewCode: string): boolean {
return false; return false;
} }
// Check for wildcard "_all" permission first (item=None in backend = all views)
if (instance.permissions.views["_all"]) {
return true;
}
return instance.permissions.views[viewCode] ?? false; return instance.permissions.views[viewCode] ?? false;
} }

View file

@ -9,9 +9,10 @@ import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'
import { useAutomations, useAutomationOperations, AutomationTemplate, Automation } from '../../hooks/useAutomations'; import { useAutomations, useAutomationOperations, AutomationTemplate, Automation } from '../../hooks/useAutomations';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm'; import { FormGeneratorForm } from '../../components/FormGenerator/FormGeneratorForm';
import { FaSync, FaRobot, FaPlay, FaPlus, FaToggleOn, FaToggleOff, FaFileAlt, FaStop, FaList, FaTimes, FaCheck, FaExclamationCircle, FaSpinner } from 'react-icons/fa'; import { FaSync, FaRobot, FaRocket, FaPlus, FaPauseCircle, FaPlayCircle, FaFileAlt, FaStop, FaList, FaTimes, FaCheck, FaExclamationCircle, FaSpinner } from 'react-icons/fa';
import { useToast } from '../../contexts/ToastContext'; import { useToast } from '../../contexts/ToastContext';
import { useApiRequest } from '../../hooks/useApi'; import { useApiRequest } from '../../hooks/useApi';
import { useFeatureStore } from '../../stores/featureStore';
import styles from '../admin/Admin.module.css'; import styles from '../admin/Admin.module.css';
@ -25,6 +26,15 @@ interface WorkflowLog {
} }
export const AutomationsPage: React.FC = () => { export const AutomationsPage: React.FC = () => {
// Get mandate and feature instance from store (first chatbot instance or first available)
const { getAllInstances } = useFeatureStore();
const instances = getAllInstances();
// Find first chatbot instance, or fall back to first available instance
const chatbotInstance = instances.find(i => i.featureCode === 'chatbot') || instances[0];
const mandateId = chatbotInstance?.mandateId;
const featureInstanceId = chatbotInstance?.id;
// Data hook // Data hook
const { const {
data: automations, data: automations,
@ -147,11 +157,24 @@ export const AutomationsPage: React.FC = () => {
// Handle create submit // Handle create submit
const handleCreateSubmit = async (data: Partial<Automation>) => { const handleCreateSubmit = async (data: Partial<Automation>) => {
const result = await handleAutomationCreate(data as any); // Validate context - mandateId and featureInstanceId are required
if (!mandateId || !featureInstanceId) {
showError('Fehler: Kein aktiver Mandant oder Feature-Instanz gefunden');
return;
}
// Add required fields from context
const createData = {
...data,
mandateId: mandateId,
featureInstanceId: featureInstanceId,
};
const result = await handleAutomationCreate(createData as any);
if (result) { if (result) {
setShowCreateModal(false); setShowCreateModal(false);
showSuccess('Automatisierung erstellt'); showSuccess('Automatisierung erstellt');
refetch(); await refetch();
} }
}; };
@ -162,7 +185,7 @@ export const AutomationsPage: React.FC = () => {
if (success) { if (success) {
setEditingAutomation(null); setEditingAutomation(null);
showSuccess('Automatisierung aktualisiert'); showSuccess('Automatisierung aktualisiert');
refetch(); await refetch();
} }
}; };
@ -172,7 +195,7 @@ export const AutomationsPage: React.FC = () => {
const success = await handleAutomationDelete(automation.id); const success = await handleAutomationDelete(automation.id);
if (success) { if (success) {
showSuccess('Automatisierung gelöscht'); showSuccess('Automatisierung gelöscht');
refetch(); await refetch();
} }
} }
}; };
@ -199,11 +222,35 @@ export const AutomationsPage: React.FC = () => {
const handleTemplateSelect = async (template: AutomationTemplate) => { const handleTemplateSelect = async (template: AutomationTemplate) => {
setShowTemplateModal(false); setShowTemplateModal(false);
// Pre-fill form with template data // Validate context - mandateId and featureInstanceId are required
if (!mandateId || !featureInstanceId) {
showError('Fehler: Kein aktiver Mandant oder Feature-Instanz gefunden');
return;
}
// Convert placeholder values to strings (backend expects Dict[str, str])
// Arrays and objects are converted to JSON strings
const convertedPlaceholders: Record<string, string> = {};
const templateParams = template.parameters || {};
for (const [key, value] of Object.entries(templateParams)) {
if (value === null || value === undefined) {
convertedPlaceholders[key] = '';
} else if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
// Convert complex structures to JSON strings
convertedPlaceholders[key] = JSON.stringify(value);
} else {
// Keep primitive values as strings
convertedPlaceholders[key] = String(value);
}
}
// Pre-fill form with template data including required fields
const prefillData: Partial<Automation> = { const prefillData: Partial<Automation> = {
mandateId: mandateId,
featureInstanceId: featureInstanceId,
label: template.template?.overview || 'Neue Automatisierung', label: template.template?.overview || 'Neue Automatisierung',
template: JSON.stringify(template.template, null, 2), template: JSON.stringify(template.template, null, 2),
placeholders: template.parameters || {}, placeholders: convertedPlaceholders,
active: false, active: false,
schedule: '0 */4 * * *', schedule: '0 */4 * * *',
}; };
@ -212,7 +259,7 @@ export const AutomationsPage: React.FC = () => {
const result = await handleAutomationCreate(prefillData as any); const result = await handleAutomationCreate(prefillData as any);
if (result) { if (result) {
showSuccess('Automatisierung aus Vorlage erstellt'); showSuccess('Automatisierung aus Vorlage erstellt');
refetch(); await refetch();
} }
}; };
@ -530,14 +577,14 @@ export const AutomationsPage: React.FC = () => {
customActions={[ customActions={[
{ {
id: 'execute', id: 'execute',
icon: <FaPlay />, icon: <FaRocket />,
onClick: handleExecute, onClick: handleExecute,
title: 'Ausführen', title: 'Ausführen',
loading: (row: any) => executingAutomations.has(row.id), loading: (row: any) => executingAutomations.has(row.id),
}, },
{ {
id: 'toggleActive', id: 'toggleActive',
icon: (row: any) => row.active ? <FaToggleOn /> : <FaToggleOff />, icon: (row: any) => row.active ? <FaPauseCircle /> : <FaPlayCircle />,
onClick: handleToggleActive, onClick: handleToggleActive,
title: (row: any) => row.active ? 'Deaktivieren' : 'Aktivieren', title: (row: any) => row.active ? 'Deaktivieren' : 'Aktivieren',
} as any, } as any,