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 {
id: string;
mandateId: string;
featureInstanceId: string;
label: string;
template: string | object;
placeholders: Record<string, string>;
@ -51,6 +52,7 @@ export interface CreateAutomationRequest {
schedule?: string;
active?: boolean;
mandateId?: string;
featureInstanceId?: string;
}
export interface UpdateAutomationRequest {

View file

@ -56,91 +56,33 @@ export function useAutomations() {
const { request, isLoading: loading, error } = useApiRequest<null, Automation[]>();
const { checkPermission } = usePermissions();
// Fallback attributes for automation form
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
// Fetch attributes from backend - no fallback, errors should be visible
const fetchAttributes = useCallback(async () => {
try {
const response = await api.get('/api/attributes/AutomationDefinition');
const response = await api.get('/api/automations/attributes');
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;
} else if (Array.isArray(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) {
attrs = fallbackAttributes;
console.warn('No attributes returned from backend for AutomationDefinition');
}
setAttributes(attrs);
return attrs;
} catch (error: any) {
console.error('Error fetching automation attributes, using fallback:', error);
setAttributes(fallbackAttributes);
return fallbackAttributes;
console.error('Error fetching automation attributes:', error);
setAttributes([]);
return [];
}
}, []);
@ -322,13 +264,24 @@ export function useAutomationOperations() {
setCreateError(null);
try {
// Get mandateId from session storage
const currentUserJson = sessionStorage.getItem('currentUser');
if (currentUserJson) {
const currentUser = JSON.parse(currentUserJson);
if (currentUser.mandateId) {
data.mandateId = currentUser.mandateId;
// Validate required fields - mandateId and featureInstanceId must be provided
if (!data.mandateId || !data.featureInstanceId) {
throw new Error('mandateId and featureInstanceId are required');
}
// 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);

View file

@ -127,6 +127,11 @@ export function useCanViewFeatureView(viewCode: string): boolean {
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;
}

View file

@ -9,9 +9,10 @@ import React, { useState, useMemo, useEffect, useCallback, useRef } from 'react'
import { useAutomations, useAutomationOperations, AutomationTemplate, Automation } from '../../hooks/useAutomations';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
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 { useApiRequest } from '../../hooks/useApi';
import { useFeatureStore } from '../../stores/featureStore';
import styles from '../admin/Admin.module.css';
@ -25,6 +26,15 @@ interface WorkflowLog {
}
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
const {
data: automations,
@ -147,11 +157,24 @@ export const AutomationsPage: React.FC = () => {
// Handle create submit
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) {
setShowCreateModal(false);
showSuccess('Automatisierung erstellt');
refetch();
await refetch();
}
};
@ -162,7 +185,7 @@ export const AutomationsPage: React.FC = () => {
if (success) {
setEditingAutomation(null);
showSuccess('Automatisierung aktualisiert');
refetch();
await refetch();
}
};
@ -172,7 +195,7 @@ export const AutomationsPage: React.FC = () => {
const success = await handleAutomationDelete(automation.id);
if (success) {
showSuccess('Automatisierung gelöscht');
refetch();
await refetch();
}
}
};
@ -199,11 +222,35 @@ export const AutomationsPage: React.FC = () => {
const handleTemplateSelect = async (template: AutomationTemplate) => {
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> = {
mandateId: mandateId,
featureInstanceId: featureInstanceId,
label: template.template?.overview || 'Neue Automatisierung',
template: JSON.stringify(template.template, null, 2),
placeholders: template.parameters || {},
placeholders: convertedPlaceholders,
active: false,
schedule: '0 */4 * * *',
};
@ -212,7 +259,7 @@ export const AutomationsPage: React.FC = () => {
const result = await handleAutomationCreate(prefillData as any);
if (result) {
showSuccess('Automatisierung aus Vorlage erstellt');
refetch();
await refetch();
}
};
@ -530,14 +577,14 @@ export const AutomationsPage: React.FC = () => {
customActions={[
{
id: 'execute',
icon: <FaPlay />,
icon: <FaRocket />,
onClick: handleExecute,
title: 'Ausführen',
loading: (row: any) => executingAutomations.has(row.id),
},
{
id: 'toggleActive',
icon: (row: any) => row.active ? <FaToggleOn /> : <FaToggleOff />,
icon: (row: any) => row.active ? <FaPauseCircle /> : <FaPlayCircle />,
onClick: handleToggleActive,
title: (row: any) => row.active ? 'Deaktivieren' : 'Aktivieren',
} as any,