Merge branch 'int'
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 51s
All checks were successful
Deploy Nyla Frontend to Production / deploy (push) Successful in 51s
This commit is contained in:
commit
5f47dd395c
468 changed files with 4361 additions and 19424 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -31,4 +31,7 @@ dist-ssr
|
||||||
.cursorignore
|
.cursorignore
|
||||||
|
|
||||||
# Keep environment files in config/ (naming: env-<workflow>.env)
|
# Keep environment files in config/ (naming: env-<workflow>.env)
|
||||||
!config/env-*.env
|
!config/env-*.env
|
||||||
|
|
||||||
|
tsc-errors.txt
|
||||||
|
scripts/i18n_missing_report.md
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Configuration — reads mandatory env vars set by .env (copied from config/env-*.env by CI).
|
* Configuration — reads mandatory env vars set by .env (copied from config/env-*.env by CI).
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { getApiBaseUrl, getAppName } from './config';
|
export { getApiBaseUrl, getAppName } from './config';
|
||||||
|
|
|
||||||
2
env.d.ts
vendored
2
env.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
15
src/App.tsx
15
src/App.tsx
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* App.tsx
|
* App.tsx
|
||||||
*
|
*
|
||||||
|
|
@ -43,7 +45,7 @@ import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandat
|
||||||
import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
|
import { AdminMandateWizardPage, AdminInvitationWizardPage } from './pages/admin/wizards';
|
||||||
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata';
|
||||||
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
|
import { BillingDataView, BillingAdmin, BillingMandateView, AdminSubscriptionsPage } from './pages/billing';
|
||||||
import { AutomationsDashboardPage } from './pages/AutomationsDashboardPage';
|
import { WorkflowAutomationPage } from './pages/workflowAutomation/WorkflowAutomationHubPage';
|
||||||
import { RagInventoryPage } from './pages/RagInventoryPage';
|
import { RagInventoryPage } from './pages/RagInventoryPage';
|
||||||
import { ComplianceAuditPage } from './pages/ComplianceAuditPage';
|
import { ComplianceAuditPage } from './pages/ComplianceAuditPage';
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -124,9 +126,9 @@ function App() {
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* AUTOMATIONS DASHBOARD */}
|
{/* WORKFLOW AUTOMATION (System-Komponente) */}
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
<Route path="automations" element={<AutomationsDashboardPage />} />
|
<Route path="workflow-automation" element={<WorkflowAutomationPage />} />
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* RAG INVENTORY */}
|
{/* RAG INVENTORY */}
|
||||||
|
|
@ -137,6 +139,7 @@ function App() {
|
||||||
<Route path="pek" element={<Navigate to="/" replace />} />
|
<Route path="pek" element={<Navigate to="/" replace />} />
|
||||||
<Route path="speech" element={<Navigate to="/" replace />} />
|
<Route path="speech" element={<Navigate to="/" replace />} />
|
||||||
|
|
||||||
|
|
||||||
{/* ============================================== */}
|
{/* ============================================== */}
|
||||||
{/* FEATURE-INSTANZ ROUTES */}
|
{/* FEATURE-INSTANZ ROUTES */}
|
||||||
{/* /mandates/:mandateId/:featureCode/:instanceId */}
|
{/* /mandates/:mandateId/:featureCode/:instanceId */}
|
||||||
|
|
@ -170,13 +173,9 @@ function App() {
|
||||||
<Route path="templates" element={<FeatureViewPage view="templates" />} />
|
<Route path="templates" element={<FeatureViewPage view="templates" />} />
|
||||||
<Route path="logs" element={<FeatureViewPage view="logs" />} />
|
<Route path="logs" element={<FeatureViewPage view="logs" />} />
|
||||||
|
|
||||||
{/* Workspace + Automation2 Editor */}
|
{/* Workspace Editor */}
|
||||||
<Route path="editor" element={<FeatureViewPage view="editor" />} />
|
<Route path="editor" element={<FeatureViewPage view="editor" />} />
|
||||||
|
|
||||||
{/* Automation2: legacy workflows URL → editor */}
|
|
||||||
<Route path="workflows" element={<Navigate to="../editor" replace />} />
|
|
||||||
<Route path="workflows-tasks" element={<FeatureViewPage view="workflows-tasks" />} />
|
|
||||||
|
|
||||||
{/* Teams Bot Feature Views */}
|
{/* Teams Bot Feature Views */}
|
||||||
<Route path="sessions" element={<FeatureViewPage view="sessions" />} />
|
<Route path="sessions" element={<FeatureViewPage view="sessions" />} />
|
||||||
<Route path="settings" element={<FeatureViewPage view="settings" />} />
|
<Route path="settings" element={<FeatureViewPage view="settings" />} />
|
||||||
|
|
|
||||||
64
src/api.ts
64
src/api.ts
|
|
@ -1,25 +1,10 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
// api.ts
|
// api.ts
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from './utils/csrfUtils';
|
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from './utils/csrfUtils';
|
||||||
import { clearUserDataCache, getUserDataCache } from './utils/userCache';
|
import { clearUserDataCache, getUserDataCache } from './utils/userCache';
|
||||||
|
|
||||||
// Utility function to resolve hostname to IP address
|
|
||||||
const resolveHostnameToIP = async (hostname: string): Promise<string | null> => {
|
|
||||||
try {
|
|
||||||
// For localhost, return as is
|
|
||||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For production domains, we can't directly resolve IP due to CORS
|
|
||||||
// But we can show the hostname which is more useful anyway
|
|
||||||
return hostname;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Could not resolve hostname to IP:', error);
|
|
||||||
return hostname;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract mandate/instance context from current URL.
|
* Extract mandate/instance context from current URL.
|
||||||
* URL pattern: /mandates/:mandateId/:featureCode/:instanceId/...
|
* URL pattern: /mandates/:mandateId/:featureCode/:instanceId/...
|
||||||
|
|
@ -44,52 +29,25 @@ const getContextFromUrl = (): { mandateId?: string; instanceId?: string } => {
|
||||||
|
|
||||||
import { getApiBaseUrl } from '../config/config';
|
import { getApiBaseUrl } from '../config/config';
|
||||||
|
|
||||||
|
const _baseUrl = getApiBaseUrl();
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log(`[api] Backend: ${_baseUrl} | env: ${import.meta.env.MODE}`);
|
||||||
|
}
|
||||||
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: getApiBaseUrl(),
|
baseURL: _baseUrl,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
// FastAPI expects repeat-style array query params (``?ids=1&ids=2``).
|
|
||||||
// Axios v1.x default would render ``?ids[]=1&ids[]=2``, which FastAPI
|
|
||||||
// silently drops -- e.g. ``trackerIds`` filters on the Redmine stats
|
|
||||||
// endpoint never reach the route. Setting ``indexes: null`` switches
|
|
||||||
// the URLSearchParams visitor to repeat format. Applies globally so
|
|
||||||
// every endpoint with array query params gets it for free.
|
|
||||||
paramsSerializer: { indexes: null },
|
paramsSerializer: { indexes: null },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add a request interceptor to add the auth token, context headers, and log backend IP
|
// Add a request interceptor to add the auth token, context headers
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
async (config) => {
|
async (config) => {
|
||||||
// Log backend information
|
// Add auth token if available (otherwise httpOnly cookies are used automatically)
|
||||||
const backendUrl = config.baseURL || getApiBaseUrl();
|
|
||||||
console.log(`🌐 Communicating with backend: ${backendUrl}`);
|
|
||||||
|
|
||||||
// Try to resolve and log the IP address
|
|
||||||
if (backendUrl) {
|
|
||||||
try {
|
|
||||||
const url = new URL(backendUrl);
|
|
||||||
const hostname = url.hostname;
|
|
||||||
const resolvedIP = await resolveHostnameToIP(hostname);
|
|
||||||
|
|
||||||
console.log(`📍 Backend hostname: ${hostname}`);
|
|
||||||
console.log(`🔗 Full backend URL: ${backendUrl}`);
|
|
||||||
console.log(`🌍 Resolved address: ${resolvedIP}`);
|
|
||||||
|
|
||||||
// Log environment info
|
|
||||||
console.log(`🏗️ Environment: ${import.meta.env.MODE}`);
|
|
||||||
console.log(`⚙️ API Base URL: ${getApiBaseUrl()}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Could not parse backend URL:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for auth token in localStorage and add to headers
|
|
||||||
const authToken = localStorage.getItem('authToken');
|
const authToken = localStorage.getItem('authToken');
|
||||||
if (authToken && config.headers) {
|
if (authToken && config.headers) {
|
||||||
config.headers.Authorization = `Bearer ${authToken}`;
|
config.headers.Authorization = `Bearer ${authToken}`;
|
||||||
console.log('🔑 Using Bearer token for authentication');
|
|
||||||
} else {
|
|
||||||
// Fallback: httpOnly cookies
|
|
||||||
console.log('🍪 Using httpOnly cookies for authentication (automatic)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send app language to backend so i18n labels match the UI
|
// Send app language to backend so i18n labels match the UI
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
import type { AttributeType } from '../utils/attributeTypeMapper';
|
import type { AttributeType } from '../utils/attributeTypeMapper';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { addCSRFTokenToHeaders } from '../utils/csrfUtils';
|
import { addCSRFTokenToHeaders } from '../utils/csrfUtils';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
135
src/api/clickupApi.ts
Normal file
135
src/api/clickupApi.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
|
/**
|
||||||
|
* ClickUp API — ClickUp-specific functions for the workflow automation flow editor.
|
||||||
|
*
|
||||||
|
* Extracted from the legacy workflowApi.ts re-export shim so each integration
|
||||||
|
* lives in its own module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ApiRequestFunction } from './workflowAutomationApi';
|
||||||
|
|
||||||
|
function _encodedConnectionId(connectionId: string): string {
|
||||||
|
return encodeURIComponent(connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ClickUp GET /task/{taskId} — list.id for resolving list-scoped fields when only task id is known. */
|
||||||
|
export async function fetchClickupTask(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
taskId: string
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const data = await request({
|
||||||
|
url: `/api/clickup/${_encodedConnectionId(connectionId)}/tasks/${encodeURIComponent(taskId)}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ClickUp list metadata (statuses, etc.) — GET /api/clickup/{connectionId}/lists/{listId}. */
|
||||||
|
export async function fetchClickupList(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
listId: string
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const data = await request({
|
||||||
|
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ClickUp workspace/team (members for assignees) — GET /api/clickup/{connectionId}/teams/{teamId}. */
|
||||||
|
export async function fetchClickupTeam(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
teamId: string
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const data = await request({
|
||||||
|
url: `/api/clickup/${_encodedConnectionId(connectionId)}/teams/${encodeURIComponent(teamId)}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ClickUp list custom fields (GET /api/clickup/{connectionId}/lists/{listId}/fields). */
|
||||||
|
export async function fetchClickupListFields(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
listId: string
|
||||||
|
): Promise<{ fields?: unknown[] } & Record<string, unknown>> {
|
||||||
|
const data = await request({
|
||||||
|
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}/fields`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
return (data && typeof data === 'object' ? data : {}) as { fields?: unknown[] } & Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ClickUp GET /list/{id}/task page (tasks in a list for relationship dropdowns). */
|
||||||
|
export interface ClickupListTaskItem {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchClickupListTasks(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
listId: string,
|
||||||
|
options?: { page?: number; includeClosed?: boolean }
|
||||||
|
): Promise<
|
||||||
|
{ tasks?: ClickupListTaskItem[]; last_page?: boolean } & Record<string, unknown>
|
||||||
|
> {
|
||||||
|
const data = await request({
|
||||||
|
url: `/api/clickup/${_encodedConnectionId(connectionId)}/lists/${encodeURIComponent(listId)}/tasks`,
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
page: options?.page ?? 0,
|
||||||
|
include_closed: options?.includeClosed ?? false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (data && typeof data === 'object' ? data : {}) as {
|
||||||
|
tasks?: ClickupListTaskItem[];
|
||||||
|
last_page?: boolean;
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Paginated tasks in a list — for ClickUp relationship dropdowns and input.form „ClickUp-Aufgabe". */
|
||||||
|
export async function loadClickupListTasksForDropdown(
|
||||||
|
request: ApiRequestFunction,
|
||||||
|
connectionId: string,
|
||||||
|
listId: string
|
||||||
|
): Promise<Array<{ id: string; name: string }>> {
|
||||||
|
const acc: Array<{ id: string; name: string }> = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const maxPages = 12;
|
||||||
|
const pageSizeHint = 100;
|
||||||
|
for (let page = 0; page < maxPages; page++) {
|
||||||
|
const data = await fetchClickupListTasks(request, connectionId, listId, {
|
||||||
|
page,
|
||||||
|
includeClosed: false,
|
||||||
|
});
|
||||||
|
if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) {
|
||||||
|
const err = (data as { error?: unknown }).error;
|
||||||
|
const body = (data as { body?: string }).body;
|
||||||
|
throw new Error(
|
||||||
|
typeof err === 'string' ? err + (body ? `: ${body.slice(0, 200)}` : '') : 'ClickUp API error'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const tasks = Array.isArray(data.tasks) ? data.tasks : [];
|
||||||
|
for (const t of tasks) {
|
||||||
|
const id = t?.id != null ? String(t.id) : '';
|
||||||
|
if (!id || seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
acc.push({ id, name: String(t.name ?? id) });
|
||||||
|
}
|
||||||
|
const rawLast = (data as Record<string, unknown>).last_page;
|
||||||
|
const last =
|
||||||
|
rawLast === true ||
|
||||||
|
rawLast === 'true' ||
|
||||||
|
tasks.length === 0 ||
|
||||||
|
tasks.length < pageSizeHint;
|
||||||
|
if (last) break;
|
||||||
|
}
|
||||||
|
acc.sort((a, b) => a.name.localeCompare(b.name, 'de'));
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
import { addCSRFTokenToHeaders, getCSRFToken, generateAndStoreCSRFToken } from '../utils/csrfUtils';
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Features API
|
* Features API
|
||||||
*
|
*
|
||||||
|
|
@ -170,19 +172,11 @@ export async function fetchMyFeatures(): Promise<FeaturesMyResponse> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('📡 featuresApi: Fetching /api/features/my');
|
|
||||||
const response = await api.get<FeaturesMyResponse>('/api/features/my');
|
const response = await api.get<FeaturesMyResponse>('/api/features/my');
|
||||||
|
|
||||||
// Get the actual data (response.data contains the FeaturesMyResponse)
|
// Get the actual data (response.data contains the FeaturesMyResponse)
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
|
||||||
console.log('✅ featuresApi: Loaded features:', {
|
|
||||||
mandateCount: data?.mandates?.length || 0,
|
|
||||||
totalInstances: data?.mandates
|
|
||||||
?.flatMap(m => m.features)
|
|
||||||
?.flatMap(f => f.instances)
|
|
||||||
?.length || 0,
|
|
||||||
});
|
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ featuresApi: Error fetching features:', error);
|
console.error('❌ featuresApi: Error fetching features:', error);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Neutralization API
|
* Neutralization API
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import type { ApiRequestOptions } from '../hooks/useApi';
|
import type { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Redmine API
|
* Redmine API
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Store API
|
* Store API
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
export interface TableListViewRow {
|
export interface TableListViewRow {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import type { VoiceOption } from './voiceCatalogApi';
|
import type { VoiceOption } from './voiceCatalogApi';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Trustee API
|
* Trustee API
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { ApiRequestOptions } from '../hooks/useApi';
|
import { ApiRequestOptions } from '../hooks/useApi';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Voice / Language Catalog API.
|
* Voice / Language Catalog API.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest';
|
|
||||||
|
|
||||||
/** Mirrors _encodedConnectionId in workflowApi.ts for browse/services URL paths. */
|
|
||||||
function encodedConnectionId(connectionId: string): string {
|
|
||||||
return encodeURIComponent(connectionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('connection path encoding for workflow browse', () => {
|
|
||||||
it('encodes spaces and colons in connection:clickup:username references', () => {
|
|
||||||
const ref = 'connection:clickup:Stephan Schellworth';
|
|
||||||
const segment = encodedConnectionId(ref);
|
|
||||||
expect(segment).toBe('connection%3Aclickup%3AStephan%20Schellworth');
|
|
||||||
const url = `/api/workflows/inst/connections/${segment}/browse`;
|
|
||||||
expect(url).not.toContain(' ');
|
|
||||||
expect(url).toContain('%20');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* AccessLevelSelect
|
* AccessLevelSelect
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* AccessRulesEditor
|
* AccessRulesEditor
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* AccessRulesTable
|
* AccessRulesTable
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* AccessRules Components
|
* AccessRules Components
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* AddConnectionWizard
|
* AddConnectionWizard
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* ChatInput -- Shared chat input component.
|
* ChatInput -- Shared chat input component.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* ChatMessageList -- Shared chat message display component.
|
* ChatMessageList -- Shared chat message display component.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { ChatMessageList } from './ChatMessageList';
|
export { ChatMessageList } from './ChatMessageList';
|
||||||
export type { ChatMessage } from './ChatMessageList';
|
export type { ChatMessage } from './ChatMessageList';
|
||||||
export { ChatInput } from './ChatInput';
|
export { ChatInput } from './ChatInput';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { IoIosDownload, IoIosCopy } from 'react-icons/io';
|
import { IoIosDownload, IoIosCopy } from 'react-icons/io';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { IoIosDownload } from 'react-icons/io';
|
import { IoIosDownload } from 'react-icons/io';
|
||||||
import { Popup, PopupAction } from '../UiComponents/Popup/Popup';
|
import { Popup, PopupAction } from '../UiComponents/Popup/Popup';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { ContentPreview } from './ContentPreview';
|
export { ContentPreview } from './ContentPreview';
|
||||||
export type { ContentPreviewProps } from './ContentPreview';
|
export type { ContentPreviewProps } from './ContentPreview';
|
||||||
export { UrlContentPreview } from './UrlContentPreview';
|
export { UrlContentPreview } from './UrlContentPreview';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
interface ApplicationRendererProps {
|
interface ApplicationRendererProps {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
interface HtmlRendererProps {
|
interface HtmlRendererProps {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
interface ImageRendererProps {
|
interface ImageRendererProps {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as pdfjsLib from 'pdfjs-dist';
|
import * as pdfjsLib from 'pdfjs-dist';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { IoIosWarning } from 'react-icons/io';
|
import { IoIosWarning } from 'react-icons/io';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import styles from '../ContentPreview.module.css';
|
import styles from '../ContentPreview.module.css';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { renderAsync } from 'docx-preview';
|
import { renderAsync } from 'docx-preview';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { JsonRenderer } from './JsonRenderer';
|
export { JsonRenderer } from './JsonRenderer';
|
||||||
export { ImageRenderer } from './ImageRenderer';
|
export { ImageRenderer } from './ImageRenderer';
|
||||||
export { TextRenderer } from './TextRenderer';
|
export { TextRenderer } from './TextRenderer';
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor - Data flow context for Data Picker and DynamicValueField.
|
* Workflow Flow Editor - Data flow context for Data Picker and DynamicValueField.
|
||||||
* Extended with portTypeCatalog and systemVariables for the Typed Port System.
|
* Extended with portTypeCatalog and systemVariables for the Typed Port System.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, useContext, useMemo } from 'react';
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
|
import type { CanvasNode, CanvasConnection } from '../editor/FlowCanvas';
|
||||||
import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
|
import { getAvailableSources } from '../nodes/shared/dataFlowGraph';
|
||||||
import type { ApiRequestFunction, ConditionOperatorDef, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowApi';
|
import type { ApiRequestFunction, ConditionOperatorDef, FormFieldType, NodeType, PortField, PortSchema, SystemVariable } from '../../../api/workflowAutomationApi';
|
||||||
|
|
||||||
export interface Automation2DataFlowContextValue {
|
export interface WorkflowDataFlowContextValue {
|
||||||
currentNodeId: string;
|
currentNodeId: string;
|
||||||
nodes: CanvasNode[];
|
nodes: CanvasNode[];
|
||||||
connections: CanvasConnection[];
|
connections: CanvasConnection[];
|
||||||
|
|
@ -30,13 +32,13 @@ export interface Automation2DataFlowContextValue {
|
||||||
parseGraphDefinedSchema: (parameterKey: string) => PortSchema | null;
|
parseGraphDefinedSchema: (parameterKey: string) => PortSchema | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Automation2DataFlowContext = createContext<Automation2DataFlowContextValue | null>(null);
|
const WorkflowDataFlowContext = createContext<WorkflowDataFlowContextValue | null>(null);
|
||||||
|
|
||||||
export function useAutomation2DataFlow(): Automation2DataFlowContextValue | null {
|
export function useWorkflowDataFlow(): WorkflowDataFlowContextValue | null {
|
||||||
return useContext(Automation2DataFlowContext);
|
return useContext(WorkflowDataFlowContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Automation2DataFlowProviderProps {
|
interface WorkflowDataFlowProviderProps {
|
||||||
node: CanvasNode | null;
|
node: CanvasNode | null;
|
||||||
nodes: CanvasNode[];
|
nodes: CanvasNode[];
|
||||||
connections: CanvasConnection[];
|
connections: CanvasConnection[];
|
||||||
|
|
@ -52,7 +54,7 @@ interface Automation2DataFlowProviderProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderProps> = ({
|
export const WorkflowDataFlowProvider: React.FC<WorkflowDataFlowProviderProps> = ({
|
||||||
node,
|
node,
|
||||||
nodes,
|
nodes,
|
||||||
connections,
|
connections,
|
||||||
|
|
@ -67,7 +69,7 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
request,
|
request,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const value = useMemo((): Automation2DataFlowContextValue | null => {
|
const value = useMemo((): WorkflowDataFlowContextValue | null => {
|
||||||
if (!node) return null;
|
if (!node) return null;
|
||||||
const formTypeToPort: Record<string, string> = Object.fromEntries(
|
const formTypeToPort: Record<string, string> = Object.fromEntries(
|
||||||
formFieldTypes.map((f) => [f.id, f.portType])
|
formFieldTypes.map((f) => [f.id, f.portType])
|
||||||
|
|
@ -135,8 +137,8 @@ export const Automation2DataFlowProvider: React.FC<Automation2DataFlowProviderPr
|
||||||
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, conditionOperatorCatalog, instanceId, request]);
|
}, [node, nodes, connections, nodeOutputsPreview, nodeTypes, language, portTypeCatalog, systemVariables, formFieldTypes, conditionOperatorCatalog, instanceId, request]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Automation2DataFlowContext.Provider value={value}>
|
<WorkflowDataFlowContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
</Automation2DataFlowContext.Provider>
|
</WorkflowDataFlowContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* CanvasHeader - Workflow controls, version selector, and execute result.
|
* CanvasHeader - Workflow controls, version selector, and execute result.
|
||||||
*/
|
*/
|
||||||
|
|
@ -26,8 +28,8 @@ import {
|
||||||
HiOutlineChatBubbleLeftEllipsis,
|
HiOutlineChatBubbleLeftEllipsis,
|
||||||
HiOutlineSquares2X2,
|
HiOutlineSquares2X2,
|
||||||
} from 'react-icons/hi2';
|
} from 'react-icons/hi2';
|
||||||
import type { Automation2Workflow, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowApi';
|
import type { WorkflowDefinition, ExecuteGraphResponse, AutoVersion, AutoTemplateScope } from '../../../api/workflowAutomationApi';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import { getUserDataCache } from '../../../utils/userCache';
|
import { getUserDataCache } from '../../../utils/userCache';
|
||||||
|
|
@ -60,7 +62,7 @@ export interface CanvasHeaderCanvasEditProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CanvasHeaderProps {
|
interface CanvasHeaderProps {
|
||||||
workflows: Automation2Workflow[];
|
workflows: WorkflowDefinition[];
|
||||||
currentWorkflowId: string | null;
|
currentWorkflowId: string | null;
|
||||||
onWorkflowSelect: (workflowId: string | null) => void;
|
onWorkflowSelect: (workflowId: string | null) => void;
|
||||||
onNew: () => void;
|
onNew: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* EditorChatPanel
|
* EditorChatPanel
|
||||||
*
|
*
|
||||||
* AI Chat sidebar for the GraphicalEditor.
|
* AI Chat sidebar for the WorkflowAutomation editor.
|
||||||
* Streams responses via SSE (same pattern as Workspace chat).
|
* Streams responses via SSE (same pattern as Workspace chat).
|
||||||
* File & data-source attachment UX mirrors WorkspaceInput:
|
* File & data-source attachment UX mirrors WorkspaceInput:
|
||||||
* - Files: drag & drop from FilesTab (UDB) onto input area, or click in UDB
|
* - Files: drag & drop from FilesTab (UDB) onto input area, or click in UDB
|
||||||
|
|
@ -87,7 +89,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
|
||||||
|
|
||||||
// Load persisted chat history from the backend whenever the workflow changes.
|
// Load persisted chat history from the backend whenever the workflow changes.
|
||||||
// The chat is stored in `ChatWorkflow.linkedWorkflowId == workflowId` and is
|
// The chat is stored in `ChatWorkflow.linkedWorkflowId == workflowId` and is
|
||||||
// returned by `GET /api/workflows/{instanceId}/{workflowId}/chat/messages`.
|
// returned by `GET /api/workflow-automation/{workflowId}/chat/messages`.
|
||||||
// For an unsaved workflow (workflowId == null) we just clear the panel.
|
// For an unsaved workflow (workflowId == null) we just clear the panel.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workflowId) {
|
if (!workflowId) {
|
||||||
|
|
@ -99,7 +101,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
|
||||||
setHistoryLoading(true);
|
setHistoryLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await api.get<PersistedEditorChatResponse>(
|
const res = await api.get<PersistedEditorChatResponse>(
|
||||||
`/api/workflows/${instanceId}/${workflowId}/chat/messages`,
|
`/api/workflow-automation/${workflowId}/chat/messages`,
|
||||||
);
|
);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
const persisted = (res.data?.messages || []).map((m): ChatMessage => ({
|
const persisted = (res.data?.messages || []).map((m): ChatMessage => ({
|
||||||
|
|
@ -166,7 +168,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
|
||||||
|
|
||||||
const baseURL = api.defaults.baseURL || '';
|
const baseURL = api.defaults.baseURL || '';
|
||||||
const cleanup = startSseStream({
|
const cleanup = startSseStream({
|
||||||
url: `${baseURL}/api/workflows/${instanceId}/${workflowId}/chat/stream`,
|
url: `${baseURL}/api/workflow-automation/${workflowId}/chat/stream`,
|
||||||
body,
|
body,
|
||||||
handlers: {
|
handlers: {
|
||||||
onChunk: (event) => {
|
onChunk: (event) => {
|
||||||
|
|
@ -227,7 +229,7 @@ export const EditorChatPanel: React.FC<EditorChatPanelProps> = ({ instanceId,
|
||||||
: m));
|
: m));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await api.post(`/api/workflows/${instanceId}/${workflowId}/chat/stop`);
|
await api.post(`/api/workflow-automation/${workflowId}/chat/stop`);
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
abortRef.current?.();
|
abortRef.current?.();
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* EditorWorkflowChatList
|
* EditorWorkflowChatList
|
||||||
*
|
*
|
||||||
* UDB "Chats" tab content for the GraphicalEditor: each AutoWorkflow is treated
|
* UDB "Chats" tab content for the WorkflowAutomation editor: each AutoWorkflow
|
||||||
* as one editor chat session. Lists workflows already loaded by the parent
|
* is treated as one editor chat session. Lists workflows already loaded by the
|
||||||
* editor (no extra fetch), supports search and "+ Neu" to start a fresh
|
* parent editor (no extra fetch), supports search and "+ Neu" to start a fresh
|
||||||
* workflow chat. Mirrors the spirit of the Workspace ChatsTab but uses
|
* workflow chat. Mirrors the spirit of the Workspace ChatsTab but uses
|
||||||
* GraphicalEditor data instead of the workspace endpoint.
|
* WorkflowAutomation data instead of the workspace endpoint.
|
||||||
*/
|
*/
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import type { Automation2Workflow } from '../../../api/workflowApi';
|
import type { WorkflowDefinition } from '../../../api/workflowAutomationApi';
|
||||||
|
|
||||||
interface EditorWorkflowChatListProps {
|
interface EditorWorkflowChatListProps {
|
||||||
workflows: Automation2Workflow[];
|
workflows: WorkflowDefinition[];
|
||||||
currentWorkflowId: string | null;
|
currentWorkflowId: string | null;
|
||||||
onSelect: (workflowId: string | null) => void;
|
onSelect: (workflowId: string | null) => void;
|
||||||
onNew: () => void;
|
onNew: () => void;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* FlowCanvas - Workflow graph canvas with nodes and connection lines.
|
* FlowCanvas - Workflow graph canvas with nodes and connection lines.
|
||||||
* Nodes have 4 connection handles (one per side), drag nodes to add, connect with arrows.
|
* Nodes have 4 connection handles (one per side), drag nodes to add, connect with arrows.
|
||||||
|
|
@ -13,8 +15,8 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type { GraphDefinedSchemaRef, NodeType } from '../../../api/workflowApi';
|
import type { GraphDefinedSchemaRef, NodeType } from '../../../api/workflowAutomationApi';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import { AiBadge } from '../nodes/shared/AiBadge';
|
import { AiBadge } from '../nodes/shared/AiBadge';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* NodeConfigPanel - Generic parameter renderer for all node types.
|
* NodeConfigPanel - Generic parameter renderer for all node types.
|
||||||
* Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType.
|
* Renders each parameter using FRONTEND_TYPE_RENDERERS based on frontendType.
|
||||||
|
|
@ -5,15 +7,15 @@
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||||
import type { CanvasNode } from './FlowCanvas';
|
import type { CanvasNode } from './FlowCanvas';
|
||||||
import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } from '../../../api/workflowApi';
|
import type { GraphDefinedSchemaRef, NodeType, NodeTypeParameter, PortSchema } from '../../../api/workflowAutomationApi';
|
||||||
import type { ApiRequestFunction } from '../../../api/workflowApi';
|
import type { ApiRequestFunction } from '../../../api/workflowAutomationApi';
|
||||||
import { getLabel } from '../nodes/shared/utils';
|
import { getLabel } from '../nodes/shared/utils';
|
||||||
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
|
import { FRONTEND_TYPE_RENDERERS } from '../nodes/frontendTypeRenderers';
|
||||||
import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer';
|
import { ContextBuilderRenderer } from '../nodes/frontendTypeRenderers/ContextBuilderRenderer';
|
||||||
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
|
import { RequiredAttributePicker } from '../nodes/shared/RequiredAttributePicker';
|
||||||
import { findRequiredErrors } from '../nodes/shared/paramValidation';
|
import { findRequiredErrors } from '../nodes/shared/paramValidation';
|
||||||
import { useAutomation2DataFlow } from '../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../context/WorkflowDataFlowContext';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import { AccordionList } from '../../UiComponents/AccordionList';
|
import { AccordionList } from '../../UiComponents/AccordionList';
|
||||||
|
|
@ -210,7 +212,7 @@ export const NodeConfigPanel: React.FC<NodeConfigPanelProps> = ({ node,
|
||||||
[onParametersChange]
|
[onParametersChange]
|
||||||
);
|
);
|
||||||
|
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const portTypeCatalog: Record<string, PortSchema> = (dataFlow?.portTypeCatalog as Record<string, PortSchema> | undefined) ?? {};
|
const portTypeCatalog: Record<string, PortSchema> = (dataFlow?.portTypeCatalog as Record<string, PortSchema> | undefined) ?? {};
|
||||||
|
|
||||||
// Phase-4 Schicht-4 — Pflicht-Params zuerst sortieren, damit der User
|
// Phase-4 Schicht-4 — Pflicht-Params zuerst sortieren, damit der User
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* NodeListItem - Draggable node type item for the sidebar.
|
* NodeListItem - Draggable node type item for the sidebar.
|
||||||
* Used in both regular categories and I/O sub-groups.
|
* Used in both regular categories and I/O sub-groups.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { NodeType } from '../../../api/workflowApi';
|
import type { NodeType } from '../../../api/workflowAutomationApi';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
import { getCategoryIcon } from '../nodes/shared/utils';
|
import { getCategoryIcon } from '../nodes/shared/utils';
|
||||||
import type { GetLabelFn } from '../nodes/shared/utils';
|
import type { GetLabelFn } from '../nodes/shared/utils';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
import { AiBadge } from '../nodes/shared/AiBadge';
|
import { AiBadge } from '../nodes/shared/AiBadge';
|
||||||
|
|
||||||
interface NodeListItemProps {
|
interface NodeListItemProps {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* NodeSidebar - Sidebar with searchable, collapsible node list.
|
* NodeSidebar - Sidebar with searchable, collapsible node list.
|
||||||
* Groups node types by category (start, input, flow, data, ai, email, sharepoint).
|
* Groups node types by category (start, input, flow, data, ai, email, sharepoint).
|
||||||
|
|
@ -5,11 +7,11 @@
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { FaChevronDown, FaChevronRight } from 'react-icons/fa';
|
import { FaChevronDown, FaChevronRight } from 'react-icons/fa';
|
||||||
import type { NodeType, NodeTypeCategory } from '../../../api/workflowApi';
|
import type { NodeType, NodeTypeCategory } from '../../../api/workflowAutomationApi';
|
||||||
import { CATEGORY_ORDER, HIDDEN_NODE_IDS } from '../nodes/shared/constants';
|
import { CATEGORY_ORDER, HIDDEN_NODE_IDS } from '../nodes/shared/constants';
|
||||||
import { getLabel } from '../nodes/shared/utils';
|
import { getLabel } from '../nodes/shared/utils';
|
||||||
import { NodeListItem } from './NodeListItem';
|
import { NodeListItem } from './NodeListItem';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* RunTracingPanel
|
* RunTracingPanel
|
||||||
*
|
*
|
||||||
|
|
@ -7,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { useApiRequest } from '../../../hooks/useApi';
|
import { useApiRequest } from '../../../hooks/useApi';
|
||||||
import type { AutoStepLog } from '../../../api/workflowApi';
|
import type { AutoStepLog } from '../../../api/workflowAutomationApi';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -98,7 +100,7 @@ export const RunTracingPanel: React.FC<RunTracingPanelProps> = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const data = await request({
|
const data = await request({
|
||||||
url: `/api/workflows/${instanceId}/runs/${runId}/steps`,
|
url: `/api/workflow-automation/runs/${runId}/steps`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
});
|
});
|
||||||
setSteps(data?.steps || []);
|
setSteps(data?.steps || []);
|
||||||
|
|
@ -115,7 +117,7 @@ export const RunTracingPanel: React.FC<RunTracingPanelProps> = ({
|
||||||
loadSteps();
|
loadSteps();
|
||||||
|
|
||||||
const baseUrl = api.defaults.baseURL || '';
|
const baseUrl = api.defaults.baseURL || '';
|
||||||
const url = `${baseUrl}/api/workflows/${instanceId}/runs/${runId}/stream`;
|
const url = `${baseUrl}/api/workflow-automation/runs/${runId}/stream`;
|
||||||
const es = new EventSource(url, { withCredentials: true });
|
const es = new EventSource(url, { withCredentials: true });
|
||||||
eventSourceRef.current = es;
|
eventSourceRef.current = es;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* TemplatePicker - modal to browse and select a workflow template for creating a new workflow.
|
* TemplatePicker - modal to browse and select a workflow template for creating a new workflow.
|
||||||
*/
|
*/
|
||||||
|
|
@ -9,8 +11,8 @@ import {
|
||||||
type AutoWorkflowTemplate,
|
type AutoWorkflowTemplate,
|
||||||
type AutoTemplateScope,
|
type AutoTemplateScope,
|
||||||
type ApiRequestFunction,
|
type ApiRequestFunction,
|
||||||
} from '../../../api/workflowApi';
|
} from '../../../api/workflowAutomationApi';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
interface TemplatePickerProps {
|
interface TemplatePickerProps {
|
||||||
|
|
@ -50,7 +52,7 @@ export const TemplatePicker: React.FC<TemplatePickerProps> = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const scope = activeScope === 'all' ? undefined : activeScope;
|
const scope = activeScope === 'all' ? undefined : activeScope;
|
||||||
const result = await fetchTemplates(request, instanceId, scope);
|
const result = await fetchTemplates(request, scope);
|
||||||
setTemplates(Array.isArray(result) ? result : result.items);
|
setTemplates(Array.isArray(result) ? result : result.items);
|
||||||
} catch {
|
} catch {
|
||||||
setTemplates([]);
|
setTemplates([]);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor Styles
|
* Workflow Flow Editor Styles
|
||||||
* Sidebar with node list + canvas area.
|
* Sidebar with node list + canvas area.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2FlowEditor
|
* WorkflowFlowEditor
|
||||||
*
|
*
|
||||||
* n8n-style flow builder with backend-driven node list and categories.
|
* n8n-style flow builder with backend-driven node list and categories.
|
||||||
* Start nodes come from the API (category `start`); invocations are synced on the server from the graph.
|
* Start nodes come from the API (category `start`); invocations are synced on the server from the graph.
|
||||||
|
|
@ -23,15 +25,16 @@ import {
|
||||||
createTemplateFromWorkflow,
|
createTemplateFromWorkflow,
|
||||||
copyTemplate,
|
copyTemplate,
|
||||||
importWorkflowFromFile,
|
importWorkflowFromFile,
|
||||||
|
WORKFLOW_FILE_EXTENSION,
|
||||||
type NodeType,
|
type NodeType,
|
||||||
type NodeTypeCategory,
|
type NodeTypeCategory,
|
||||||
type Automation2Graph,
|
type WorkflowGraph,
|
||||||
type Automation2Workflow,
|
type WorkflowDefinition,
|
||||||
type ExecuteGraphResponse,
|
type ExecuteGraphResponse,
|
||||||
type WorkflowEntryPoint,
|
type WorkflowEntryPoint,
|
||||||
type AutoVersion,
|
type AutoVersion,
|
||||||
type AutoTemplateScope,
|
type AutoTemplateScope,
|
||||||
} from '../../../api/workflowApi';
|
} from '../../../api/workflowAutomationApi';
|
||||||
import {
|
import {
|
||||||
FlowCanvas,
|
FlowCanvas,
|
||||||
type CanvasNode,
|
type CanvasNode,
|
||||||
|
|
@ -49,7 +52,7 @@ import { fromApiGraph, toApiGraph, switchOutputCountFromCases, trimConnectionsFo
|
||||||
import { buildNodeOutputsPreview, setPortTypeCatalog as setRegistryCatalog } from '../nodes/shared/outputPreviewRegistry';
|
import { buildNodeOutputsPreview, setPortTypeCatalog as setRegistryCatalog } from '../nodes/shared/outputPreviewRegistry';
|
||||||
import { findGraphErrors } from '../nodes/shared/paramValidation';
|
import { findGraphErrors } from '../nodes/shared/paramValidation';
|
||||||
import { getLabel as getParamLabel } from '../nodes/shared/utils';
|
import { getLabel as getParamLabel } from '../nodes/shared/utils';
|
||||||
import { Automation2DataFlowProvider } from '../context/Automation2DataFlowContext';
|
import { WorkflowDataFlowProvider } from '../context/WorkflowDataFlowContext';
|
||||||
import { usePrompt } from '../../../hooks/usePrompt';
|
import { usePrompt } from '../../../hooks/usePrompt';
|
||||||
import { EditorChatPanel } from './EditorChatPanel';
|
import { EditorChatPanel } from './EditorChatPanel';
|
||||||
import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel';
|
import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel';
|
||||||
|
|
@ -57,12 +60,12 @@ import { EditorWorkflowChatList } from './EditorWorkflowChatList';
|
||||||
import { RunTracingPanel } from './RunTracingPanel';
|
import { RunTracingPanel } from './RunTracingPanel';
|
||||||
import { UnifiedDataBar } from '../../../components/UnifiedDataBar';
|
import { UnifiedDataBar } from '../../../components/UnifiedDataBar';
|
||||||
import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
|
import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
|
||||||
import styles from './Automation2FlowEditor.module.css';
|
import styles from './WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
||||||
const LOG = '[Automation2]';
|
const LOG = '[WorkflowEditor]';
|
||||||
|
|
||||||
const CANVAS_HISTORY_MAX = 50;
|
const CANVAS_HISTORY_MAX = 50;
|
||||||
|
|
||||||
|
|
@ -78,7 +81,7 @@ function cloneCanvasSnapshot(nodes: CanvasNode[], connections: CanvasConnection[
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Automation2FlowEditorProps {
|
interface WorkflowFlowEditorProps {
|
||||||
instanceId: string;
|
instanceId: string;
|
||||||
mandateId?: string;
|
mandateId?: string;
|
||||||
language?: string;
|
language?: string;
|
||||||
|
|
@ -92,7 +95,7 @@ interface Automation2FlowEditorProps {
|
||||||
onSourcesChanged?: () => void;
|
onSourcesChanged?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ instanceId,
|
export const WorkflowFlowEditor: React.FC<WorkflowFlowEditorProps> = ({ instanceId,
|
||||||
mandateId,
|
mandateId,
|
||||||
language = 'de',
|
language = 'de',
|
||||||
initialWorkflowId,
|
initialWorkflowId,
|
||||||
|
|
@ -110,9 +113,9 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
|
const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
|
||||||
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
|
const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
|
||||||
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
|
const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
|
||||||
const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowApi').FormFieldType[]>([]);
|
const [formFieldTypes, setFormFieldTypes] = useState<import('../../../api/workflowAutomationApi').FormFieldType[]>([]);
|
||||||
const [conditionOperatorCatalog, setConditionOperatorCatalog] = useState<
|
const [conditionOperatorCatalog, setConditionOperatorCatalog] = useState<
|
||||||
Record<string, import('../../../api/workflowApi').ConditionOperatorDef[]>
|
Record<string, import('../../../api/workflowAutomationApi').ConditionOperatorDef[]>
|
||||||
>({});
|
>({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
@ -137,7 +140,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
const [canvasStickyNotes, setCanvasStickyNotes] = useState<CanvasStickyNote[]>([]);
|
const [canvasStickyNotes, setCanvasStickyNotes] = useState<CanvasStickyNote[]>([]);
|
||||||
const [executing, setExecuting] = useState(false);
|
const [executing, setExecuting] = useState(false);
|
||||||
const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null);
|
const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null);
|
||||||
const [workflows, setWorkflows] = useState<Automation2Workflow[]>([]);
|
const [workflows, setWorkflows] = useState<WorkflowDefinition[]>([]);
|
||||||
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
|
const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
|
||||||
const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null);
|
const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
@ -153,7 +156,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
instanceId,
|
instanceId,
|
||||||
mandateId: mandateId || '',
|
mandateId: mandateId || '',
|
||||||
featureInstanceId: instanceId,
|
featureInstanceId: instanceId,
|
||||||
surface: 'graphEditor',
|
surface: 'workflowAutomation',
|
||||||
}), [instanceId, mandateId]);
|
}), [instanceId, mandateId]);
|
||||||
const [versions, setVersions] = useState<AutoVersion[]>([]);
|
const [versions, setVersions] = useState<AutoVersion[]>([]);
|
||||||
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
|
const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
|
||||||
|
|
@ -301,7 +304,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
|
|
||||||
const applyGraphWithSync = useCallback(
|
const applyGraphWithSync = useCallback(
|
||||||
(
|
(
|
||||||
graph: Automation2Graph | null | undefined,
|
graph: WorkflowGraph | null | undefined,
|
||||||
wfInvocations: WorkflowEntryPoint[] | undefined,
|
wfInvocations: WorkflowEntryPoint[] | undefined,
|
||||||
opts?: { skipHistory?: boolean }
|
opts?: { skipHistory?: boolean }
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -309,7 +312,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
pushCanvasHistoryPastFromCurrent();
|
pushCanvasHistoryPastFromCurrent();
|
||||||
}
|
}
|
||||||
setInvocations(wfInvocations ?? []);
|
setInvocations(wfInvocations ?? []);
|
||||||
const g: Automation2Graph = graph ?? { nodes: [], connections: [] };
|
const g: WorkflowGraph = graph ?? { nodes: [], connections: [] };
|
||||||
const { nodes, connections } = fromApiGraph(g, nodeTypes);
|
const { nodes, connections } = fromApiGraph(g, nodeTypes);
|
||||||
setCanvasNodes(nodes);
|
setCanvasNodes(nodes);
|
||||||
setCanvasConnections(connections);
|
setCanvasConnections(connections);
|
||||||
|
|
@ -318,7 +321,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFromApiGraph = useCallback(
|
const handleFromApiGraph = useCallback(
|
||||||
(graph: Automation2Graph, wfInvocations?: WorkflowEntryPoint[]) => {
|
(graph: WorkflowGraph, wfInvocations?: WorkflowEntryPoint[]) => {
|
||||||
applyGraphWithSync(graph, wfInvocations);
|
applyGraphWithSync(graph, wfInvocations);
|
||||||
},
|
},
|
||||||
[applyGraphWithSync]
|
[applyGraphWithSync]
|
||||||
|
|
@ -354,7 +357,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setExecuteResult(null);
|
setExecuteResult(null);
|
||||||
try {
|
try {
|
||||||
const ep = currentWorkflowId ? invocations[0]?.id : undefined;
|
const ep = currentWorkflowId ? invocations[0]?.id : undefined;
|
||||||
const result = await executeGraph(request, instanceId, graph, currentWorkflowId ?? undefined, {
|
const result = await executeGraph(request, graph, currentWorkflowId ?? undefined, {
|
||||||
...(ep ? { entryPointId: ep } : {}),
|
...(ep ? { entryPointId: ep } : {}),
|
||||||
});
|
});
|
||||||
setExecuteResult(result);
|
setExecuteResult(result);
|
||||||
|
|
@ -403,7 +406,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
if (currentWorkflowId) {
|
if (currentWorkflowId) {
|
||||||
const updated = await updateWorkflow(request, instanceId, currentWorkflowId, {
|
const updated = await updateWorkflow(request, currentWorkflowId, {
|
||||||
graph,
|
graph,
|
||||||
invocations,
|
invocations,
|
||||||
targetFeatureInstanceId,
|
targetFeatureInstanceId,
|
||||||
|
|
@ -420,11 +423,12 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const created = await createWorkflow(request, instanceId, {
|
const created = await createWorkflow(request, {
|
||||||
label: label.trim() || t('Neuer Workflow'),
|
label: label.trim() || t('Neuer Workflow'),
|
||||||
graph,
|
graph,
|
||||||
invocations,
|
invocations,
|
||||||
targetFeatureInstanceId,
|
targetFeatureInstanceId,
|
||||||
|
mandateId,
|
||||||
});
|
});
|
||||||
setCurrentWorkflowId(created.id);
|
setCurrentWorkflowId(created.id);
|
||||||
setInvocations(created.invocations ?? []);
|
setInvocations(created.invocations ?? []);
|
||||||
|
|
@ -436,12 +440,12 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
}
|
}
|
||||||
}, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations, t, nodeErrors, targetFeatureInstanceId, hasCanvasStartNode]);
|
}, [request, mandateId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations, t, nodeErrors, targetFeatureInstanceId, hasCanvasStartNode]);
|
||||||
|
|
||||||
const handleLoad = useCallback(
|
const handleLoad = useCallback(
|
||||||
async (workflowId: string) => {
|
async (workflowId: string) => {
|
||||||
try {
|
try {
|
||||||
const wf = await fetchWorkflow(request, instanceId, workflowId);
|
const wf = await fetchWorkflow(request, workflowId);
|
||||||
if (wf.graph) {
|
if (wf.graph) {
|
||||||
handleFromApiGraph(wf.graph, wf.invocations);
|
handleFromApiGraph(wf.graph, wf.invocations);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -463,7 +467,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setExecuteResult(null);
|
setExecuteResult(null);
|
||||||
applyGraphWithSync({ nodes: [], connections: [] }, []);
|
applyGraphWithSync({ nodes: [], connections: [] }, []);
|
||||||
try {
|
try {
|
||||||
const result = await fetchWorkflows(request, instanceId);
|
const result = await fetchWorkflows(request);
|
||||||
setWorkflows(Array.isArray(result) ? result : result.items);
|
setWorkflows(Array.isArray(result) ? result : result.items);
|
||||||
} catch (refreshErr) {
|
} catch (refreshErr) {
|
||||||
console.error(`${LOG} workflows refresh failed`, refreshErr);
|
console.error(`${LOG} workflows refresh failed`, refreshErr);
|
||||||
|
|
@ -476,7 +480,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, handleFromApiGraph, applyGraphWithSync, t]
|
[request, handleFromApiGraph, applyGraphWithSync, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleWorkflowSelect = useCallback(
|
const handleWorkflowSelect = useCallback(
|
||||||
|
|
@ -544,11 +548,10 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadNodeTypes = useCallback(async () => {
|
const loadNodeTypes = useCallback(async () => {
|
||||||
if (!instanceId) return;
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const data = await fetchNodeTypes(request, instanceId, language);
|
const data = await fetchNodeTypes(request, mandateId || '', language);
|
||||||
setNodeTypes(data.nodeTypes);
|
setNodeTypes(data.nodeTypes);
|
||||||
setCategories(data.categories);
|
setCategories(data.categories);
|
||||||
if (data.portTypeCatalog) {
|
if (data.portTypeCatalog) {
|
||||||
|
|
@ -565,17 +568,16 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [instanceId, language, request]);
|
}, [language, request]);
|
||||||
|
|
||||||
const loadWorkflows = useCallback(async () => {
|
const loadWorkflows = useCallback(async () => {
|
||||||
if (!instanceId) return;
|
|
||||||
try {
|
try {
|
||||||
const result = await fetchWorkflows(request, instanceId);
|
const result = await fetchWorkflows(request, { mandateId: mandateId || undefined });
|
||||||
setWorkflows(Array.isArray(result) ? result : result.items);
|
setWorkflows(Array.isArray(result) ? result : result.items);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`${LOG} loadWorkflows failed`, e);
|
console.error(`${LOG} loadWorkflows failed`, e);
|
||||||
}
|
}
|
||||||
}, [instanceId, request]);
|
}, [request, mandateId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadNodeTypes();
|
loadNodeTypes();
|
||||||
|
|
@ -665,17 +667,17 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadVersions = useCallback(async () => {
|
const loadVersions = useCallback(async () => {
|
||||||
if (!instanceId || !currentWorkflowId) {
|
if (!currentWorkflowId) {
|
||||||
setVersions([]);
|
setVersions([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const v = await fetchVersions(request, instanceId, currentWorkflowId);
|
const v = await fetchVersions(request, currentWorkflowId);
|
||||||
setVersions(v);
|
setVersions(v);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`${LOG} loadVersions failed`, e);
|
console.error(`${LOG} loadVersions failed`, e);
|
||||||
}
|
}
|
||||||
}, [instanceId, currentWorkflowId, request]);
|
}, [currentWorkflowId, request]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadVersions();
|
loadVersions();
|
||||||
|
|
@ -696,10 +698,9 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
|
|
||||||
const handlePublishVersion = useCallback(
|
const handlePublishVersion = useCallback(
|
||||||
async (versionId: string) => {
|
async (versionId: string) => {
|
||||||
if (!instanceId) return;
|
|
||||||
setVersionLoading(true);
|
setVersionLoading(true);
|
||||||
try {
|
try {
|
||||||
await publishVersion(request, instanceId, versionId);
|
await publishVersion(request, versionId);
|
||||||
await loadVersions();
|
await loadVersions();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
||||||
|
|
@ -707,15 +708,14 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setVersionLoading(false);
|
setVersionLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, loadVersions]
|
[request, loadVersions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleUnpublishVersion = useCallback(
|
const handleUnpublishVersion = useCallback(
|
||||||
async (versionId: string) => {
|
async (versionId: string) => {
|
||||||
if (!instanceId) return;
|
|
||||||
setVersionLoading(true);
|
setVersionLoading(true);
|
||||||
try {
|
try {
|
||||||
await unpublishVersion(request, instanceId, versionId);
|
await unpublishVersion(request, versionId);
|
||||||
await loadVersions();
|
await loadVersions();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
||||||
|
|
@ -723,15 +723,14 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setVersionLoading(false);
|
setVersionLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, loadVersions]
|
[request, loadVersions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleArchiveVersion = useCallback(
|
const handleArchiveVersion = useCallback(
|
||||||
async (versionId: string) => {
|
async (versionId: string) => {
|
||||||
if (!instanceId) return;
|
|
||||||
setVersionLoading(true);
|
setVersionLoading(true);
|
||||||
try {
|
try {
|
||||||
await archiveVersion(request, instanceId, versionId);
|
await archiveVersion(request, versionId);
|
||||||
await loadVersions();
|
await loadVersions();
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
||||||
|
|
@ -739,14 +738,14 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setVersionLoading(false);
|
setVersionLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, loadVersions]
|
[request, loadVersions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreateDraft = useCallback(async () => {
|
const handleCreateDraft = useCallback(async () => {
|
||||||
if (!instanceId || !currentWorkflowId) return;
|
if (!currentWorkflowId) return;
|
||||||
setVersionLoading(true);
|
setVersionLoading(true);
|
||||||
try {
|
try {
|
||||||
const draft = await createDraftVersion(request, instanceId, currentWorkflowId);
|
const draft = await createDraftVersion(request, currentWorkflowId);
|
||||||
await loadVersions();
|
await loadVersions();
|
||||||
setCurrentVersionId(draft.id);
|
setCurrentVersionId(draft.id);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
|
@ -754,16 +753,16 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
} finally {
|
} finally {
|
||||||
setVersionLoading(false);
|
setVersionLoading(false);
|
||||||
}
|
}
|
||||||
}, [request, instanceId, currentWorkflowId, loadVersions]);
|
}, [request, currentWorkflowId, loadVersions]);
|
||||||
|
|
||||||
// Template: save current workflow as template
|
// Template: save current workflow as template
|
||||||
const [templateSaving, setTemplateSaving] = useState(false);
|
const [templateSaving, setTemplateSaving] = useState(false);
|
||||||
const handleSaveAsTemplate = useCallback(
|
const handleSaveAsTemplate = useCallback(
|
||||||
async (scope: AutoTemplateScope) => {
|
async (scope: AutoTemplateScope) => {
|
||||||
if (!instanceId || !currentWorkflowId) return;
|
if (!currentWorkflowId) return;
|
||||||
setTemplateSaving(true);
|
setTemplateSaving(true);
|
||||||
try {
|
try {
|
||||||
await createTemplateFromWorkflow(request, instanceId, currentWorkflowId, scope);
|
await createTemplateFromWorkflow(request, currentWorkflowId, scope);
|
||||||
setExecuteResult({ success: true, error: undefined } as unknown as ExecuteGraphResponse);
|
setExecuteResult({ success: true, error: undefined } as unknown as ExecuteGraphResponse);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
||||||
|
|
@ -771,16 +770,15 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setTemplateSaving(false);
|
setTemplateSaving(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, currentWorkflowId]
|
[request, currentWorkflowId]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Template: new workflow from template
|
// Template: new workflow from template
|
||||||
const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
|
const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
|
||||||
const handleNewFromTemplate = useCallback(
|
const handleNewFromTemplate = useCallback(
|
||||||
async (templateId: string) => {
|
async (templateId: string) => {
|
||||||
if (!instanceId) return;
|
|
||||||
try {
|
try {
|
||||||
const wf = await copyTemplate(request, instanceId, templateId);
|
const wf = await copyTemplate(request, templateId);
|
||||||
setWorkflows((prev) => [...prev, wf]);
|
setWorkflows((prev) => [...prev, wf]);
|
||||||
setCurrentWorkflowId(wf.id);
|
setCurrentWorkflowId(wf.id);
|
||||||
if (wf.graph) handleFromApiGraph(wf.graph, wf.invocations);
|
if (wf.graph) handleFromApiGraph(wf.graph, wf.invocations);
|
||||||
|
|
@ -789,7 +787,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[request, instanceId, handleFromApiGraph]
|
[request, handleFromApiGraph]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -947,12 +945,20 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
activeTab={udbTab as UdbTab}
|
activeTab={udbTab as UdbTab}
|
||||||
onTabChange={(tab) => setUdbTab(tab as LeftTab)}
|
onTabChange={(tab) => setUdbTab(tab as LeftTab)}
|
||||||
hideTabs={['chats']}
|
hideTabs={['chats']}
|
||||||
onFileSelect={onFileSelect}
|
onFileSelect={async (fileId, fileName) => {
|
||||||
onSourcesChanged={onSourcesChanged}
|
if (fileName?.toLowerCase().endsWith(WORKFLOW_FILE_EXTENSION)) {
|
||||||
onWorkflowImportedFromFile={async (workflowId) => {
|
try {
|
||||||
await loadWorkflows();
|
const result = await importWorkflowFromFile(request, { fileId });
|
||||||
handleWorkflowSelect(workflowId);
|
await loadWorkflows();
|
||||||
|
if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[workflowAutomation] workflow file import failed', e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onFileSelect?.(fileId, fileName);
|
||||||
}}
|
}}
|
||||||
|
onSourcesChanged={onSourcesChanged}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1024,12 +1030,12 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
stickyNotes={canvasStickyNotes}
|
stickyNotes={canvasStickyNotes}
|
||||||
onStickyNotesChange={setCanvasStickyNotes}
|
onStickyNotesChange={setCanvasStickyNotes}
|
||||||
onExternalDrop={async (mime, payload) => {
|
onExternalDrop={async (mime, payload) => {
|
||||||
if (mime !== 'application/json+workflow' || !instanceId) return false;
|
if (mime !== 'application/json+workflow') return false;
|
||||||
const p = payload as { files?: Array<{ id: string }> } | undefined;
|
const p = payload as { files?: Array<{ id: string }> } | undefined;
|
||||||
const fileId = p?.files?.[0]?.id;
|
const fileId = p?.files?.[0]?.id;
|
||||||
if (!fileId) return false;
|
if (!fileId) return false;
|
||||||
try {
|
try {
|
||||||
const result = await importWorkflowFromFile(request, instanceId, { fileId });
|
const result = await importWorkflowFromFile(request, { fileId });
|
||||||
await loadWorkflows();
|
await loadWorkflows();
|
||||||
if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id);
|
if (result?.workflow?.id) handleWorkflowSelect(result.workflow.id);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1042,7 +1048,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
</div>
|
</div>
|
||||||
{configurableSelected && selectedNode && (
|
{configurableSelected && selectedNode && (
|
||||||
<div className={styles.nodeConfigPanelWrap} data-suppress-flow-node-hotkeys="">
|
<div className={styles.nodeConfigPanelWrap} data-suppress-flow-node-hotkeys="">
|
||||||
<Automation2DataFlowProvider
|
<WorkflowDataFlowProvider
|
||||||
node={selectedNode}
|
node={selectedNode}
|
||||||
nodes={canvasNodes}
|
nodes={canvasNodes}
|
||||||
connections={canvasConnections}
|
connections={canvasConnections}
|
||||||
|
|
@ -1067,7 +1073,7 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
request={request}
|
request={request}
|
||||||
verboseSchema={verboseSchema}
|
verboseSchema={verboseSchema}
|
||||||
/>
|
/>
|
||||||
</Automation2DataFlowProvider>
|
</WorkflowDataFlowProvider>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1122,4 +1128,4 @@ export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ in
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Automation2FlowEditor;
|
export default WorkflowFlowEditor;
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export { Automation2FlowEditor, Automation2FlowEditor as FlowEditor } from './editor/Automation2FlowEditor';
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
|
export { WorkflowFlowEditor, WorkflowFlowEditor as FlowEditor } from './editor/WorkflowFlowEditor';
|
||||||
export type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './editor/EditorChatPanel';
|
export type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './editor/EditorChatPanel';
|
||||||
export { FlowCanvas, STICKY_NOTE_PALETTE, STICKY_NOTE_DEFAULT_COLOR_ID, STICKY_NOTE_DEFAULT_HEIGHT, getStickyNotePaletteEntry } from './editor/FlowCanvas';
|
export { FlowCanvas, STICKY_NOTE_PALETTE, STICKY_NOTE_DEFAULT_COLOR_ID, STICKY_NOTE_DEFAULT_HEIGHT, getStickyNotePaletteEntry } from './editor/FlowCanvas';
|
||||||
export type { CanvasNode, CanvasConnection, CanvasStickyNote, FlowCanvasHandle, FlowCanvasViewportEditState } from './editor/FlowCanvas';
|
export type { CanvasNode, CanvasConnection, CanvasStickyNote, FlowCanvasHandle, FlowCanvasViewportEditState } from './editor/FlowCanvas';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* One text field per option — the text the end user sees in the dropdown.
|
* One text field per option — the text the end user sees in the dropdown.
|
||||||
* Stored as { value, label } with the same string so payload and UI stay in sync.
|
* Stored as { value, label } with the same string so payload and UI stay in sync.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Form node config - draggable fields, types, required toggle
|
* Form node config - draggable fields, types, required toggle
|
||||||
*/
|
*/
|
||||||
|
|
@ -6,8 +8,8 @@ import React from 'react';
|
||||||
import { FaGripVertical, FaTimes } from 'react-icons/fa';
|
import { FaGripVertical, FaTimes } from 'react-icons/fa';
|
||||||
import type { FormField, NodeConfigRendererProps } from '../shared/types';
|
import type { FormField, NodeConfigRendererProps } from '../shared/types';
|
||||||
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
|
import { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
|
||||||
import {
|
import {
|
||||||
deriveFormFieldPayloadKey,
|
deriveFormFieldPayloadKey,
|
||||||
|
|
@ -19,7 +21,7 @@ import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
export const FormNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const ctx = useAutomation2DataFlow();
|
const ctx = useWorkflowDataFlow();
|
||||||
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
||||||
? ctx.formFieldTypes
|
? ctx.formFieldTypes
|
||||||
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Helpers for optional select/multiselect rows on workflow form field definitions.
|
* Helpers for optional select/multiselect rows on workflow form field definitions.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { FormNodeConfig } from './FormNodeConfig';
|
export { FormNodeConfig } from './FormNodeConfig';
|
||||||
export { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
|
export { FormFieldOptionsEditor } from './FormFieldOptionsEditor';
|
||||||
export type { FormFieldOptionRow } from './formFieldOptionsUtils';
|
export type { FormFieldOptionRow } from './formFieldOptionsUtils';
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Backend-driven case list for flow.switch (depends on value dataRef).
|
* Backend-driven case list for flow.switch (depends on value dataRef).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { isRef, type DataRef } from '../shared/dataRef';
|
import { isRef, type DataRef } from '../shared/dataRef';
|
||||||
import { toApiGraph } from '../shared/graphUtils';
|
import { toApiGraph } from '../shared/graphUtils';
|
||||||
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowApi';
|
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowAutomationApi';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
export interface SwitchCase {
|
export interface SwitchCase {
|
||||||
|
|
@ -116,7 +118,7 @@ export const CaseListEditor: React.FC<FieldRendererProps> = ({
|
||||||
allParams,
|
allParams,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const dependsOn =
|
const dependsOn =
|
||||||
param.frontendOptions && typeof param.frontendOptions === 'object'
|
param.frontendOptions && typeof param.frontendOptions === 'object'
|
||||||
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'value')
|
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'value')
|
||||||
|
|
@ -157,7 +159,7 @@ export const CaseListEditor: React.FC<FieldRendererProps> = ({
|
||||||
|
|
||||||
if (dataFlow?.instanceId && dataFlow.request) {
|
if (dataFlow?.instanceId && dataFlow.request) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetchConditionMeta(dataFlow.request, dataFlow.instanceId, {
|
fetchConditionMeta(dataFlow.request, {
|
||||||
graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
|
graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
|
||||||
nodeId: dataFlow.currentNodeId,
|
nodeId: dataFlow.currentNodeId,
|
||||||
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },
|
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* clickupList — hierarchical ClickUp list picker via connector browse API.
|
* clickupList — hierarchical ClickUp list picker via connector browse API.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import {
|
import { fetchBrowse, type BrowseEntry } from '../../../../api/workflowAutomationApi';
|
||||||
fetchBrowse,
|
import { fetchClickupList } from '../../../../api/clickupApi';
|
||||||
fetchClickupList,
|
|
||||||
type BrowseEntry,
|
|
||||||
} from '../../../../api/workflowApi';
|
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
import {
|
import {
|
||||||
clickupBrowseParentPath,
|
clickupBrowseParentPath,
|
||||||
|
|
@ -74,7 +73,7 @@ export const ClickUpListPicker: React.FC<FieldRendererProps> = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const res = await fetchBrowse(request, instanceId, connectionReference, 'clickup', path);
|
const res = await fetchBrowse(request, connectionReference, 'clickup', path);
|
||||||
setItems(res.items);
|
setItems(res.items);
|
||||||
setBrowsePath(res.path || path);
|
setBrowsePath(res.path || path);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Backend-driven condition editor for flow.ifElse (depends on Item dataRef).
|
* Backend-driven condition editor for flow.ifElse (depends on Item dataRef).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { isRef, type DataRef } from '../shared/dataRef';
|
import { isRef, type DataRef } from '../shared/dataRef';
|
||||||
import { toApiGraph } from '../shared/graphUtils';
|
import { toApiGraph } from '../shared/graphUtils';
|
||||||
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowApi';
|
import { fetchConditionMeta, type ConditionOperatorDef } from '../../../../api/workflowAutomationApi';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
export interface StructuredCondition {
|
export interface StructuredCondition {
|
||||||
|
|
@ -41,7 +43,7 @@ export const ConditionEditor: React.FC<FieldRendererProps> = ({
|
||||||
allParams,
|
allParams,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const dependsOn =
|
const dependsOn =
|
||||||
param.frontendOptions && typeof param.frontendOptions === 'object'
|
param.frontendOptions && typeof param.frontendOptions === 'object'
|
||||||
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'Item')
|
? String((param.frontendOptions as Record<string, unknown>).dependsOn ?? 'Item')
|
||||||
|
|
@ -83,7 +85,7 @@ export const ConditionEditor: React.FC<FieldRendererProps> = ({
|
||||||
|
|
||||||
if (dataFlow?.instanceId && dataFlow.request) {
|
if (dataFlow?.instanceId && dataFlow.request) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetchConditionMeta(dataFlow.request, dataFlow.instanceId, {
|
fetchConditionMeta(dataFlow.request, {
|
||||||
graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
|
graph: toApiGraph(dataFlow.nodes, dataFlow.connections),
|
||||||
nodeId: dataFlow.currentNodeId,
|
nodeId: dataFlow.currentNodeId,
|
||||||
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },
|
ref: { type: 'ref', nodeId: ref.nodeId, path: ref.path },
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* One place to configure context.setContext rows: target key, then either
|
* One place to configure context.setContext rows: target key, then either
|
||||||
* upstream picker, a fixed literal, or a human task.
|
* upstream picker, a fixed literal, or a human task.
|
||||||
|
|
@ -5,7 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { DataPicker } from '../shared/DataPicker';
|
import { DataPicker } from '../shared/DataPicker';
|
||||||
import { isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
import { isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
|
|
@ -174,7 +176,7 @@ const REMOVE_BTN: React.CSSProperties = {
|
||||||
|
|
||||||
export const ContextAssignmentsEditor: React.FC<FieldRendererProps> = ({ param, value, onChange, allParams }) => {
|
export const ContextAssignmentsEditor: React.FC<FieldRendererProps> = ({ param, value, onChange, allParams }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const rows = normalizeRows(value, allParams);
|
const rows = normalizeRows(value, allParams);
|
||||||
const [pickerRow, setPickerRow] = React.useState<number | null>(null);
|
const [pickerRow, setPickerRow] = React.useState<number | null>(null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* ContextBuilderRenderer — multi-select context binding for AI nodes.
|
* ContextBuilderRenderer — multi-select context binding for AI nodes.
|
||||||
*
|
*
|
||||||
|
|
@ -11,7 +13,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { DataPicker } from '../shared/DataPicker';
|
import { DataPicker } from '../shared/DataPicker';
|
||||||
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
|
|
@ -52,7 +54,7 @@ const REMOVE_BTN: React.CSSProperties = {
|
||||||
|
|
||||||
export const ContextBuilderRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
export const ContextBuilderRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const [pickerOpen, setPickerOpen] = React.useState(false);
|
const [pickerOpen, setPickerOpen] = React.useState(false);
|
||||||
const dragIndex = React.useRef<number | null>(null);
|
const dragIndex = React.useRef<number | null>(null);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* DataRefRenderer — Pick-not-Push attribute binding using the existing
|
* DataRefRenderer — Pick-not-Push attribute binding using the existing
|
||||||
* hierarchical DataPicker.
|
* hierarchical DataPicker.
|
||||||
|
|
@ -10,14 +12,14 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { DataPicker } from '../shared/DataPicker';
|
import { DataPicker } from '../shared/DataPicker';
|
||||||
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
import { isRef, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
|
|
||||||
export const DataRefRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
export const DataRefRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const [pickerOpen, setPickerOpen] = React.useState(false);
|
const [pickerOpen, setPickerOpen] = React.useState(false);
|
||||||
|
|
||||||
const currentRef = isRef(value) ? (value as DataRef) : null;
|
const currentRef = isRef(value) ? (value as DataRef) : null;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* FeatureInstancePicker — renderer for frontendType="featureInstance".
|
* FeatureInstancePicker — renderer for frontendType="featureInstance".
|
||||||
*
|
*
|
||||||
* Modeled on ConnectionPicker. Loads mandate-scoped FeatureInstances filtered
|
* Modeled on ConnectionPicker. Loads mandate-scoped FeatureInstances filtered
|
||||||
* by `frontendOptions.featureCode` (e.g. "trustee", "redmine") via
|
* by `frontendOptions.featureCode` (e.g. "trustee", "redmine") via
|
||||||
* GET /api/workflows/{instanceId}/options/feature.instance?featureCode=<code>
|
* GET /api/workflow-automation/options/feature.instance?featureCode=<code>
|
||||||
*
|
*
|
||||||
* Behavior matches the rest of the editor:
|
* Behavior matches the rest of the editor:
|
||||||
* - 0 results -> hint to create a feature instance for this mandate
|
* - 0 results -> hint to create a feature instance for this mandate
|
||||||
|
|
@ -42,7 +44,7 @@ export const FeatureInstancePicker: React.FC<FieldRendererProps> = ({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setLoadError(null);
|
setLoadError(null);
|
||||||
request({
|
request({
|
||||||
url: `/api/workflows/${instanceId}/options/feature.instance?featureCode=${encodeURIComponent(featureCode)}`,
|
url: `/api/workflow-automation/options/feature.instance?featureCode=${encodeURIComponent(featureCode)}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
})
|
})
|
||||||
.then((res: unknown) => {
|
.then((res: unknown) => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* TemplateTextarea — Freitext mit eingebetteten {{nodeId.path}} Tokens.
|
* TemplateTextarea — Freitext mit eingebetteten {{nodeId.path}} Tokens.
|
||||||
* Tokens werden zur Laufzeit von resolveParameterReferences aufgeloest (Gateway).
|
* Tokens werden zur Laufzeit von resolveParameterReferences aufgeloest (Gateway).
|
||||||
|
|
@ -5,11 +7,11 @@
|
||||||
|
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import type { FieldRendererProps } from './index';
|
import type { FieldRendererProps } from './index';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { DataPicker } from '../shared/DataPicker';
|
import { DataPicker } from '../shared/DataPicker';
|
||||||
import { formatRefLabel, isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
import { formatRefLabel, isRef, isSystemVar, type DataRef, type SystemVarRef } from '../shared/dataRef';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
const _TEMPLATE_TOKEN_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
const _TEMPLATE_TOKEN_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
||||||
|
|
||||||
|
|
@ -60,7 +62,7 @@ function _parseTokensInTemplate(
|
||||||
|
|
||||||
export const TemplateTextareaRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
export const TemplateTextareaRenderer: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [pickerOpen, setPickerOpen] = useState(false);
|
const [pickerOpen, setPickerOpen] = useState(false);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* userFileFolder — FormGeneratorTree embedded: combobox-style trigger + expandable tree.
|
* userFileFolder — FormGeneratorTree embedded: combobox-style trigger + expandable tree.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import {
|
import {
|
||||||
clickupBrowseParentPath,
|
clickupBrowseParentPath,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/** Parse virtual ClickUp list paths: /team/{teamId}/list/{listId} */
|
/** Parse virtual ClickUp list paths: /team/{teamId}/list/{listId} */
|
||||||
|
|
||||||
const LIST_PATH_RE = /^\/team\/([^/]+)\/list\/([^/]+)$/;
|
const LIST_PATH_RE = /^\/team\/([^/]+)\/list\/([^/]+)$/;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Generic FrontendType renderer registry.
|
* Generic FrontendType renderer registry.
|
||||||
* Maps frontendType strings to React components.
|
* Maps frontendType strings to React components.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import type { NodeTypeParameter } from '../../../../api/workflowApi';
|
import type { NodeTypeParameter } from '../../../../api/workflowAutomationApi';
|
||||||
import type { ApiRequestFunction } from '../../../../api/workflowApi';
|
import type { ApiRequestFunction } from '../../../../api/workflowAutomationApi';
|
||||||
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
import { FORM_FIELD_TYPES, FORM_FIELD_TYPE_LABELS } from '../../../../utils/attributeTypeMapper';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { FormFieldOptionsEditor } from '../form/FormFieldOptionsEditor';
|
import { FormFieldOptionsEditor } from '../form/FormFieldOptionsEditor';
|
||||||
import {
|
import {
|
||||||
deriveFormFieldPayloadKey,
|
deriveFormFieldPayloadKey,
|
||||||
|
|
@ -46,7 +48,7 @@ import {
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
import { toApiGraph } from '../shared/graphUtils';
|
import { toApiGraph } from '../shared/graphUtils';
|
||||||
import { postUpstreamPaths } from '../../../../api/workflowApi';
|
import { postUpstreamPaths } from '../../../../api/workflowAutomationApi';
|
||||||
import type { CanvasNode } from '../../editor/FlowCanvas';
|
import type { CanvasNode } from '../../editor/FlowCanvas';
|
||||||
import { DataRefRenderer } from './DataRefRenderer';
|
import { DataRefRenderer } from './DataRefRenderer';
|
||||||
import { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
import { ContextBuilderRenderer } from './ContextBuilderRenderer';
|
||||||
|
|
@ -300,7 +302,7 @@ const HiddenInput: React.FC<FieldRendererProps> = () => null;
|
||||||
|
|
||||||
const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange, instanceId, request }) => {
|
const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange, instanceId, request }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const [connections, setConnections] = React.useState<Array<{ id: string; label: string }>>([]);
|
const [connections, setConnections] = React.useState<Array<{ id: string; label: string }>>([]);
|
||||||
const [loadError, setLoadError] = React.useState<string | null>(null);
|
const [loadError, setLoadError] = React.useState<string | null>(null);
|
||||||
const [upstreamBindOptions, setUpstreamBindOptions] = React.useState<Array<{ key: string; label: string; ref: unknown }>>([]);
|
const [upstreamBindOptions, setUpstreamBindOptions] = React.useState<Array<{ key: string; label: string; ref: unknown }>>([]);
|
||||||
|
|
@ -310,7 +312,7 @@ const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange
|
||||||
if (!instanceId || !request) return;
|
if (!instanceId || !request) return;
|
||||||
const qs = authority ? `?authority=${encodeURIComponent(authority)}` : '';
|
const qs = authority ? `?authority=${encodeURIComponent(authority)}` : '';
|
||||||
setLoadError(null);
|
setLoadError(null);
|
||||||
request({ url: `/api/workflows/${instanceId}/options/user.connection${qs}`, method: 'get' })
|
request({ url: `/api/workflow-automation/options/user.connection${qs}`, method: 'get' })
|
||||||
.then((res: unknown) => {
|
.then((res: unknown) => {
|
||||||
const data = res as { options?: Array<{ value: string; label: string }> };
|
const data = res as { options?: Array<{ value: string; label: string }> };
|
||||||
setConnections((data?.options || []).map((o) => ({ id: o.value, label: o.label })));
|
setConnections((data?.options || []).map((o) => ({ id: o.value, label: o.label })));
|
||||||
|
|
@ -328,7 +330,7 @@ const ConnectionPicker: React.FC<FieldRendererProps> = ({ param, value, onChange
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const graph = toApiGraph(dataFlow.nodes as CanvasNode[], dataFlow.connections);
|
const graph = toApiGraph(dataFlow.nodes as CanvasNode[], dataFlow.connections);
|
||||||
postUpstreamPaths(request, instanceId, graph, dataFlow.currentNodeId)
|
postUpstreamPaths(request, graph, dataFlow.currentNodeId)
|
||||||
.then(({ paths }) => {
|
.then(({ paths }) => {
|
||||||
const opts = paths
|
const opts = paths
|
||||||
.filter(
|
.filter(
|
||||||
|
|
@ -643,7 +645,7 @@ const SharepointPathPicker: React.FC<FieldRendererProps> = ({ param, value, onCh
|
||||||
|
|
||||||
const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
const FieldBuilderEditor: React.FC<FieldRendererProps> = ({ param, value, onChange }) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const ctx = useAutomation2DataFlow();
|
const ctx = useWorkflowDataFlow();
|
||||||
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
const fieldTypeOptions = ctx?.formFieldTypes?.length
|
||||||
? ctx.formFieldTypes
|
? ctx.formFieldTypes
|
||||||
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
: FORM_FIELD_TYPES.map((ft) => ({ id: ft, label: FORM_FIELD_TYPE_LABELS[ft] ?? ft, portType: 'str' }));
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { ConditionEditor as IfElseNodeConfig } from '../frontendTypeRenderers/ConditionEditor';
|
export { ConditionEditor as IfElseNodeConfig } from '../frontendTypeRenderers/ConditionEditor';
|
||||||
export type { StructuredCondition } from '../frontendTypeRenderers/ConditionEditor';
|
export type { StructuredCondition } from '../frontendTypeRenderers/ConditionEditor';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Loop node config - Datenquelle für Iteration mit benutzerfreundlichen Labels.
|
* Loop node config - Datenquelle für Iteration mit benutzerfreundlichen Labels.
|
||||||
* Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche.
|
* Z.B. für jedes Formularfeld, jede Datei aus Upload, jede E-Mail aus Suche.
|
||||||
|
|
@ -7,7 +9,7 @@ import React from 'react';
|
||||||
import type { NodeConfigRendererProps } from '../shared/types';
|
import type { NodeConfigRendererProps } from '../shared/types';
|
||||||
import { LoopItemsSelect } from '../shared/LoopItemsSelect';
|
import { LoopItemsSelect } from '../shared/LoopItemsSelect';
|
||||||
import { createValue, isRef, isValue } from '../shared/dataRef';
|
import { createValue, isRef, isValue } from '../shared/dataRef';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
export const LoopNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
export const LoopNodeConfig: React.FC<NodeConfigRendererProps> = ({ params, updateParam }) => {
|
||||||
const value = params.items;
|
const value = params.items;
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
export { LoopNodeConfig } from './LoopNodeConfig';
|
export { LoopNodeConfig } from './LoopNodeConfig';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Shared mapping: file type options (accept strings) → MIME types.
|
* Shared mapping: file type options (accept strings) → MIME types.
|
||||||
* Used by Upload node config (allowed types) and IfElse node (mimeType comparison).
|
* Used by Upload node config (allowed types) and IfElse node (mimeType comparison).
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Small label for workflow nodes that consume AI credits (LLM calls).
|
* Small label for workflow nodes that consume AI credits (LLM calls).
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor - Schema-based Data Picker.
|
* Workflow Flow Editor - Schema-based Data Picker.
|
||||||
* Builds pickable paths from portTypeCatalog + node outputPorts, or from
|
* Builds pickable paths from portTypeCatalog + node outputPorts, or from
|
||||||
* outputPorts[n].dataPickOptions when the backend defines an explicit list (authoritative).
|
* outputPorts[n].dataPickOptions when the backend defines an explicit list (authoritative).
|
||||||
* Resolves Transit chains to show the real upstream schema.
|
* Resolves Transit chains to show the real upstream schema.
|
||||||
|
|
@ -9,10 +11,10 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef';
|
import { createRef, createSystemVar, type DataRef, type SystemVarRef, isCompatible } from './dataRef';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import type { DataPickOption, GraphDataSources, GraphDefinedSchemaRef, NodeType, PortField, PortSchema } from '../../../../api/workflowApi';
|
import type { DataPickOption, GraphDataSources, GraphDefinedSchemaRef, NodeType, PortField, PortSchema } from '../../../../api/workflowAutomationApi';
|
||||||
import { fetchGraphDataSources } from '../../../../api/workflowApi';
|
import { fetchGraphDataSources } from '../../../../api/workflowAutomationApi';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -268,7 +270,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
// Default: when the consumer declares an expected type, show only compatible
|
// Default: when the consumer declares an expected type, show only compatible
|
||||||
// candidates ("strict" mode). User can override per-session via the toggle.
|
// candidates ("strict" mode). User can override per-session via the toggle.
|
||||||
const [strictFilter, setStrictFilter] = useState<boolean>(Boolean(expectedParamType));
|
const [strictFilter, setStrictFilter] = useState<boolean>(Boolean(expectedParamType));
|
||||||
const ctx = useAutomation2DataFlow();
|
const ctx = useWorkflowDataFlow();
|
||||||
|
|
||||||
// NOTE: All hooks must be called unconditionally on every render to satisfy
|
// NOTE: All hooks must be called unconditionally on every render to satisfy
|
||||||
// the Rules of Hooks. The `if (!open) return null;` early-return therefore
|
// the Rules of Hooks. The `if (!open) return null;` early-return therefore
|
||||||
|
|
@ -303,7 +305,7 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
if (scopeFetchKey.current === key) return; // already fetched for this state
|
if (scopeFetchKey.current === key) return; // already fetched for this state
|
||||||
scopeFetchKey.current = key;
|
scopeFetchKey.current = key;
|
||||||
const nodeShapes = (ctx.nodes ?? []).map((n) => ({ id: n.id, type: n.type }));
|
const nodeShapes = (ctx.nodes ?? []).map((n) => ({ id: n.id, type: n.type }));
|
||||||
fetchGraphDataSources(ctx.request, ctx.instanceId, ctx.currentNodeId, nodeShapes, connections)
|
fetchGraphDataSources(ctx.request, ctx.currentNodeId, nodeShapes, connections)
|
||||||
.then(setScopeData)
|
.then(setScopeData)
|
||||||
.catch(() => setScopeData(null));
|
.catch(() => setScopeData(null));
|
||||||
}, [open, ctx?.instanceId, ctx?.request, ctx?.currentNodeId, connections, nodesRaw]);
|
}, [open, ctx?.instanceId, ctx?.request, ctx?.currentNodeId, connections, nodesRaw]);
|
||||||
|
|
@ -361,10 +363,10 @@ export const DataPicker: React.FC<DataPickerProps> = ({ open,
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="automation2DataPickerTitle"
|
aria-labelledby="workflowDataPickerTitle"
|
||||||
>
|
>
|
||||||
<div className={styles.dataPickerHeader}>
|
<div className={styles.dataPickerHeader}>
|
||||||
<h4 className={styles.dataPickerTitle} id="automation2DataPickerTitle">
|
<h4 className={styles.dataPickerTitle} id="workflowDataPickerTitle">
|
||||||
{t('Datenquelle wählen')}
|
{t('Datenquelle wählen')}
|
||||||
{expectedParamType && (
|
{expectedParamType && (
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor - Field that supports node reference only (no static value).
|
* Workflow Flow Editor - Field that supports node reference only (no static value).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
|
@ -12,8 +14,8 @@ import {
|
||||||
} from './dataRef';
|
} from './dataRef';
|
||||||
import { RefSourceSelect } from './RefSourceSelect';
|
import { RefSourceSelect } from './RefSourceSelect';
|
||||||
import { DataPicker } from './DataPicker';
|
import { DataPicker } from './DataPicker';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -39,7 +41,7 @@ export const DynamicValueField: React.FC<DynamicValueFieldProps> = ({ paramKey,
|
||||||
variant = 'picker',
|
variant = 'picker',
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const [pickerOpen, setPickerOpen] = useState(false);
|
const [pickerOpen, setPickerOpen] = useState(false);
|
||||||
|
|
||||||
const ref: DataRef | null = isRef(value) ? value : null;
|
const ref: DataRef | null = isRef(value) ? value : null;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Text/number field: „Quelle wählen“ → Statisch (Eingabe) oder Kontext-Ref.
|
* Text/number field: „Quelle wählen“ → Statisch (Eingabe) oder Kontext-Ref.
|
||||||
* Textfeld nur bei „Statisch“, nicht bei Kontext-Referenz.
|
* Textfeld nur bei „Statisch“, nicht bei Kontext-Referenz.
|
||||||
|
|
@ -9,9 +11,9 @@ import {
|
||||||
shouldShowStaticControl,
|
shouldShowStaticControl,
|
||||||
type PathPickMode,
|
type PathPickMode,
|
||||||
} from './RefSourceSelect';
|
} from './RefSourceSelect';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { isRef, isValue, createValue } from './dataRef';
|
import { isRef, isValue, createValue } from './dataRef';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -48,7 +50,7 @@ export const HybridStaticRefField: React.FC<HybridStaticRefFieldProps> = ({ labe
|
||||||
pathPickMode = 'default',
|
pathPickMode = 'default',
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
const hasSources =
|
const hasSources =
|
||||||
dataFlow &&
|
dataFlow &&
|
||||||
dataFlow.getAvailableSourceIds().some((id) => {
|
dataFlow.getAvailableSourceIds().some((id) => {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels.
|
* Loop node - Datenquelle für Iteration mit benutzerfreundlichen Labels.
|
||||||
* Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder → {name, value}).
|
* Zeigt nur iterierbare Quellen: Arrays und Objekte (Formularfelder → {name, value}).
|
||||||
|
|
@ -6,8 +8,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRef, isRef, type DataRef } from './dataRef';
|
import { createRef, isRef, type DataRef } from './dataRef';
|
||||||
import { refToOptionValue, optionValueToRef } from './RefSourceSelect';
|
import { refToOptionValue, optionValueToRef } from './RefSourceSelect';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -161,7 +163,7 @@ export const LoopItemsSelect: React.FC<LoopItemsSelectProps> = ({ value,
|
||||||
placeholder,
|
placeholder,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
if (!dataFlow) return null;
|
if (!dataFlow) return null;
|
||||||
|
|
||||||
const sourceIds = dataFlow.getAvailableSourceIds();
|
const sourceIds = dataFlow.getAvailableSourceIds();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Inline dropdown to select a data source (node + path) - no popup.
|
* Inline dropdown to select a data source (node + path) - no popup.
|
||||||
* Form nodes (trigger.form / input.form): only payload.<fieldName> paths (no duplicate tree).
|
* Form nodes (trigger.form / input.form): only payload.<fieldName> paths (no duplicate tree).
|
||||||
|
|
@ -5,7 +7,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRef, isRef, isValue, createValue, type DataRef } from './dataRef';
|
import { createRef, isRef, isValue, createValue, type DataRef } from './dataRef';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
/** How to build path options for StatischKontextSelect / RefSourceSelect. */
|
/** How to build path options for StatischKontextSelect / RefSourceSelect. */
|
||||||
|
|
@ -238,7 +240,7 @@ export const StatischKontextSelect: React.FC<StatischKontextSelectProps> = ({
|
||||||
pathPickMode = 'default',
|
pathPickMode = 'default',
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
if (!dataFlow) return null;
|
if (!dataFlow) return null;
|
||||||
|
|
||||||
const sourceIds = dataFlow.getAvailableSourceIds();
|
const sourceIds = dataFlow.getAvailableSourceIds();
|
||||||
|
|
@ -316,7 +318,7 @@ export const RefSourceSelect: React.FC<RefSourceSelectProps> = ({
|
||||||
pathPickMode = 'default',
|
pathPickMode = 'default',
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const dataFlow = useAutomation2DataFlow();
|
const dataFlow = useWorkflowDataFlow();
|
||||||
if (!dataFlow) return null;
|
if (!dataFlow) return null;
|
||||||
|
|
||||||
const sourceIds = dataFlow.getAvailableSourceIds();
|
const sourceIds = dataFlow.getAvailableSourceIds();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* RequiredAttributePicker — Phase-4 Schicht-4 binding affordance for
|
* RequiredAttributePicker — Phase-4 Schicht-4 binding affordance for
|
||||||
* required parameters of a Schicht-3 Adapter (Editor-Node).
|
* required parameters of a Schicht-3 Adapter (Editor-Node).
|
||||||
|
|
@ -15,11 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { useAutomation2DataFlow } from '../../context/Automation2DataFlowContext';
|
import { useWorkflowDataFlow } from '../../context/WorkflowDataFlowContext';
|
||||||
import { DataPicker } from './DataPicker';
|
import { DataPicker } from './DataPicker';
|
||||||
import { createRef, formatRefLabel, isRef, type DataRef, type SystemVarRef } from './dataRef';
|
import { createRef, formatRefLabel, isRef, type DataRef, type SystemVarRef } from './dataRef';
|
||||||
import { findSourceCandidates, strictlyCompatible, type SourceCandidate } from './paramValidation';
|
import { findSourceCandidates, strictlyCompatible, type SourceCandidate } from './paramValidation';
|
||||||
import styles from '../../editor/Automation2FlowEditor.module.css';
|
import styles from '../../editor/WorkflowFlowEditor.module.css';
|
||||||
|
|
||||||
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
|
||||||
|
|
@ -44,7 +46,7 @@ export const RequiredAttributePicker: React.FC<RequiredAttributePickerProps> = (
|
||||||
description,
|
description,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
const ctx = useAutomation2DataFlow();
|
const ctx = useWorkflowDataFlow();
|
||||||
const [pickerOpen, setPickerOpen] = useState(false);
|
const [pickerOpen, setPickerOpen] = useState(false);
|
||||||
|
|
||||||
const consumerNodeId = ctx?.currentNodeId ?? '';
|
const consumerNodeId = ctx?.currentNodeId ?? '';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Category icons for node types
|
* Category icons for node types
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Shared condition operators for If/Else and Switch nodes.
|
* Shared condition operators for If/Else and Switch nodes.
|
||||||
* Type-dependent: number gets <, >, etc.; string gets contains, equals, etc.
|
* Type-dependent: number gets <, >, etc.; string gets contains, equals, etc.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor - Constants
|
* Workflow Flow Editor - Constants
|
||||||
* Category ordering for node sidebar.
|
* Category ordering for node sidebar.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
// Copyright (c) 2026 PowerOn AG
|
||||||
|
// All rights reserved.
|
||||||
/**
|
/**
|
||||||
* Automation2 Flow Editor - Graph helpers for data flow (ancestors, topo order).
|
* Workflow Flow Editor - Graph helpers for data flow (ancestors, topo order).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { CanvasNode, CanvasConnection } from '../../editor/FlowCanvas';
|
import type { CanvasNode, CanvasConnection } from '../../editor/FlowCanvas';
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue