/** * Automation2 API * Node types and graph execution for n8n-style flows. */ import type { ApiRequestOptions } from '../hooks/useApi'; const LOG = '[Automation2]'; // ============================================================================ // TYPES // ============================================================================ export interface NodeTypeParameter { name: string; type: string; required?: boolean; description?: string; default?: unknown; } export interface NodeType { id: string; category: string; label: string; description: string; parameters: NodeTypeParameter[]; inputs: number; outputs: number; executor: string; meta?: { icon?: string; color?: string; method?: string; action?: string; }; } export interface NodeTypeCategory { id: string; label: Record | string; } export interface NodeTypesResponse { nodeTypes: NodeType[]; categories: NodeTypeCategory[]; } export interface Automation2GraphNode { id: string; type: string; parameters?: Record; } export interface Automation2Connection { source: string; target: string; sourceOutput?: number; targetInput?: number; } export interface Automation2Graph { nodes: Automation2GraphNode[]; connections: Automation2Connection[]; } export interface ExecuteGraphResponse { success: boolean; nodeOutputs?: Record; error?: string; stopped?: boolean; failedNode?: string; paused?: boolean; taskId?: string; runId?: string; nodeId?: string; } export interface Automation2Workflow { id: string; label: string; graph: Automation2Graph; active?: boolean; /** Enriched: run count */ runCount?: number; /** Enriched: has active (running/paused) run */ isRunning?: boolean; /** Enriched: status of active run */ runStatus?: string; /** Enriched: nodeId where workflow is stuck (paused) */ stuckAtNodeId?: string; /** Enriched: human-readable label for stuck node */ stuckAtNodeLabel?: string; /** Enriched: created timestamp (seconds) */ createdAt?: number; /** Enriched: last run started timestamp (seconds) */ lastStartedAt?: number; } // ============================================================================ // API FUNCTIONS // ============================================================================ export type ApiRequestFunction = (options: ApiRequestOptions) => Promise; /** * Fetch node types for the flow builder (backend-driven). * GET /api/automation2/{instanceId}/node-types?language=de */ export async function fetchNodeTypes( request: ApiRequestFunction, instanceId: string, language = 'de' ): Promise { console.log(`${LOG} fetchNodeTypes: instanceId=${instanceId} language=${language}`); const data = await request({ url: `/api/automation2/${instanceId}/node-types`, method: 'get', params: { language }, }); const nodeTypes = data?.nodeTypes ?? []; const categories = data?.categories ?? []; console.log(`${LOG} fetchNodeTypes response: ${nodeTypes.length} nodeTypes, ${categories.length} categories`); return { nodeTypes, categories }; } /** * Execute an automation2 graph. * POST /api/automation2/{instanceId}/execute */ export async function executeGraph( request: ApiRequestFunction, instanceId: string, graph: Automation2Graph, workflowId?: string ): Promise { console.log( `${LOG} executeGraph request: instanceId=${instanceId} workflowId=${workflowId} nodes=${graph.nodes.length} connections=${graph.connections.length}`, { nodes: graph.nodes, connections: graph.connections } ); const start = performance.now(); try { const result = await request({ url: `/api/automation2/${instanceId}/execute`, method: 'post', data: { graph, workflowId }, }); const ms = Math.round(performance.now() - start); console.log( `${LOG} executeGraph response (${ms}ms): success=${result?.success} error=${result?.error ?? 'none'} nodeOutputs_keys=${Object.keys(result?.nodeOutputs ?? {}).join(',')} failedNode=${result?.failedNode ?? '-'}`, result ); return result; } catch (err) { const ms = Math.round(performance.now() - start); console.error( `${LOG} executeGraph FAILED (${ms}ms): instanceId=${instanceId}`, err ); throw err; } } // ------------------------------------------------------------------------- // Workflows CRUD // ------------------------------------------------------------------------- export async function fetchWorkflows( request: ApiRequestFunction, instanceId: string ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/workflows`, method: 'get', }); return data?.workflows ?? []; } export async function fetchWorkflow( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise { return await request({ url: `/api/automation2/${instanceId}/workflows/${workflowId}`, method: 'get', }); } export async function createWorkflow( request: ApiRequestFunction, instanceId: string, body: { label: string; graph: Automation2Graph } ): Promise { return await request({ url: `/api/automation2/${instanceId}/workflows`, method: 'post', data: body, }); } export async function updateWorkflow( request: ApiRequestFunction, instanceId: string, workflowId: string, body: { label?: string; graph?: Automation2Graph } ): Promise { return await request({ url: `/api/automation2/${instanceId}/workflows/${workflowId}`, method: 'put', data: body, }); } export async function deleteWorkflow( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise { await request({ url: `/api/automation2/${instanceId}/workflows/${workflowId}`, method: 'delete', }); } export interface Automation2Run { id: string; workflowId: string; status: string; nodeOutputs?: Record; currentNodeId?: string; } export async function fetchWorkflowRuns( request: ApiRequestFunction, instanceId: string, workflowId: string ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/workflows/${workflowId}/runs`, method: 'get', }); return data?.runs ?? []; } // ------------------------------------------------------------------------- // Tasks // ------------------------------------------------------------------------- export interface Automation2Task { id: string; runId: string; workflowId: string; nodeId: string; nodeType: string; config: Record; status: string; result?: Record; /** Workflow label (enriched by API) */ workflowLabel?: string; /** Unix timestamp ms (from sysCreatedAt) */ createdAt?: number; /** Optional due date - configurable in future */ dueAt?: number; } export async function fetchTasks( request: ApiRequestFunction, instanceId: string, params?: { workflowId?: string; status?: string } ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/tasks`, method: 'get', params, }); return data?.tasks ?? []; } export async function completeTask( request: ApiRequestFunction, instanceId: string, taskId: string, result: Record ): Promise { return await request({ url: `/api/automation2/${instanceId}/tasks/${taskId}/complete`, method: 'post', data: { result }, }); } // ------------------------------------------------------------------------- // Connections and Browse (for Email/SharePoint node config) // ------------------------------------------------------------------------- export interface UserConnection { id: string; authority: string; externalUsername?: string; externalEmail?: string; status: string; } export async function fetchConnections( request: ApiRequestFunction, instanceId: string ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/connections`, method: 'get', }); return data?.connections ?? []; } export interface ConnectionService { service: string; label: string; icon: string; } export async function fetchConnectionServices( request: ApiRequestFunction, instanceId: string, connectionId: string ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/connections/${connectionId}/services`, method: 'get', }); return data?.services ?? []; } export interface BrowseEntry { name: string; path: string; isFolder: boolean; size?: number; mimeType?: string; metadata?: Record; } export async function fetchBrowse( request: ApiRequestFunction, instanceId: string, connectionId: string, service: string, path = '/' ): Promise<{ items: BrowseEntry[]; path: string; service: string }> { const data = await request({ url: `/api/automation2/${instanceId}/connections/${connectionId}/browse`, method: 'get', params: { service, path }, }); return { items: data?.items ?? [], path: data?.path ?? path, service: data?.service ?? service }; }