[WIP] Workflows
This commit is contained in:
@@ -6,57 +6,64 @@
|
||||
* Response DTO for action information
|
||||
*/
|
||||
export type ActionResponse = {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,41 +6,48 @@
|
||||
* Simplified action response (for list endpoints)
|
||||
*/
|
||||
export type ActionSummary = {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,66 +6,73 @@
|
||||
* Standard API response wrapper
|
||||
*/
|
||||
export type ApiResponse_ActionResponse = {
|
||||
/**
|
||||
* Response DTO for action information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Response DTO for action information
|
||||
* Creation timestamp
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
created: string;
|
||||
/**
|
||||
* Optional message
|
||||
* Action description
|
||||
*/
|
||||
message?: string | null;
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ export type ApiResponse_EnforcementResponse = {
|
||||
* Enforcement payload
|
||||
*/
|
||||
payload: Record<string, any>;
|
||||
/**
|
||||
* Timestamp when the enforcement was resolved (status changed from created to processed/disabled)
|
||||
*/
|
||||
resolved_at?: string | null;
|
||||
rule?: (null | i64);
|
||||
/**
|
||||
* Rule reference
|
||||
@@ -51,10 +55,6 @@ export type ApiResponse_EnforcementResponse = {
|
||||
* Trigger reference
|
||||
*/
|
||||
trigger_ref: string;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
|
||||
@@ -42,10 +42,6 @@ export type ApiResponse_EventResponse = {
|
||||
* Trigger reference
|
||||
*/
|
||||
trigger_ref: string;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
|
||||
@@ -2,63 +2,79 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ExecutionStatus } from './ExecutionStatus';
|
||||
import type { ExecutionStatus } from "./ExecutionStatus";
|
||||
/**
|
||||
* Standard API response wrapper
|
||||
*/
|
||||
export type ApiResponse_ExecutionResponse = {
|
||||
/**
|
||||
* Response DTO for execution information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Response DTO for execution information
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
action?: number | null;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
action?: number | null;
|
||||
/**
|
||||
* Optional message
|
||||
* Action reference
|
||||
*/
|
||||
message?: string | null;
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow task metadata (only populated for workflow task executions)
|
||||
*/
|
||||
workflow_task?: {
|
||||
workflow_execution: number;
|
||||
task_name: string;
|
||||
task_index?: number | null;
|
||||
task_batch?: number | null;
|
||||
retry_count: number;
|
||||
max_retries: number;
|
||||
next_retry_at?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
timed_out: boolean;
|
||||
duration_ms?: number | null;
|
||||
started_at?: string | null;
|
||||
completed_at?: string | null;
|
||||
} | null;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ export type ApiResponse_PackResponse = {
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -47,7 +51,7 @@ export type ApiResponse_PackResponse = {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps: Array<string>;
|
||||
/**
|
||||
|
||||
@@ -11,9 +11,9 @@ export type ApiResponse_RuleResponse = {
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID
|
||||
* Action ID (null if the referenced action has been deleted)
|
||||
*/
|
||||
action: number;
|
||||
action?: number | null;
|
||||
/**
|
||||
* Parameters to pass to the action when rule is triggered
|
||||
*/
|
||||
@@ -63,9 +63,9 @@ export type ApiResponse_RuleResponse = {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Trigger ID
|
||||
* Trigger ID (null if the referenced trigger has been deleted)
|
||||
*/
|
||||
trigger: number;
|
||||
trigger?: number | null;
|
||||
/**
|
||||
* Parameters for trigger configuration and event filtering
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ export type ApiResponse_SensorResponse = {
|
||||
*/
|
||||
pack_ref?: string | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ export type ApiResponse_TriggerResponse = {
|
||||
*/
|
||||
pack_ref?: string | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ export type ApiResponse_WorkflowResponse = {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ export type CreateActionRequest = {
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema (JSON Schema) defining expected outputs
|
||||
* Output schema (flat format) defining expected outputs with inline required/secret
|
||||
*/
|
||||
out_schema?: any | null;
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export type CreateActionRequest = {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (JSON Schema) defining expected inputs
|
||||
* Parameter schema (StackStorm-style) defining expected inputs with inline required/secret
|
||||
*/
|
||||
param_schema?: any | null;
|
||||
/**
|
||||
@@ -38,5 +38,9 @@ export type CreateActionRequest = {
|
||||
* Optional runtime ID for this action
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Optional semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export type CreateInquiryRequest = {
|
||||
*/
|
||||
prompt: string;
|
||||
/**
|
||||
* Optional JSON schema for the expected response format
|
||||
* Optional schema for the expected response format (flat format with inline required/secret)
|
||||
*/
|
||||
response_schema: Record<string, any>;
|
||||
/**
|
||||
|
||||
@@ -7,13 +7,17 @@
|
||||
*/
|
||||
export type CreatePackRequest = {
|
||||
/**
|
||||
* Configuration schema (JSON Schema)
|
||||
* Configuration schema (flat format with inline required/secret per parameter)
|
||||
*/
|
||||
conf_schema?: Record<string, any>;
|
||||
/**
|
||||
* Pack configuration values
|
||||
*/
|
||||
config?: Record<string, any>;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies?: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -35,7 +39,7 @@ export type CreatePackRequest = {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies (refs of required packs)
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps?: Array<string>;
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ export type CreateSensorRequest = {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (JSON Schema) for sensor configuration
|
||||
* Parameter schema (flat format) for sensor configuration
|
||||
*/
|
||||
param_schema?: any | null;
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ export type CreateTriggerRequest = {
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema (JSON Schema) defining event data structure
|
||||
* Output schema (flat format) defining event data structure with inline required/secret
|
||||
*/
|
||||
out_schema?: any | null;
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export type CreateTriggerRequest = {
|
||||
*/
|
||||
pack_ref?: string | null;
|
||||
/**
|
||||
* Parameter schema (JSON Schema) defining event payload structure
|
||||
* Parameter schema (StackStorm-style) defining trigger configuration with inline required/secret
|
||||
*/
|
||||
param_schema?: any | null;
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ export type CreateWorkflowRequest = {
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema (JSON Schema) defining expected outputs
|
||||
* Output schema (flat format) defining expected outputs with inline required/secret
|
||||
*/
|
||||
out_schema: Record<string, any>;
|
||||
/**
|
||||
@@ -31,7 +31,7 @@ export type CreateWorkflowRequest = {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (JSON Schema) defining expected inputs
|
||||
* Parameter schema (StackStorm-style) defining expected inputs with inline required/secret
|
||||
*/
|
||||
param_schema: Record<string, any>;
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,10 @@ export type EnforcementResponse = {
|
||||
* Enforcement payload
|
||||
*/
|
||||
payload: Record<string, any>;
|
||||
/**
|
||||
* Timestamp when the enforcement was resolved (status changed from created to processed/disabled)
|
||||
*/
|
||||
resolved_at?: string | null;
|
||||
rule?: (null | i64);
|
||||
/**
|
||||
* Rule reference
|
||||
@@ -47,9 +51,5 @@ export type EnforcementResponse = {
|
||||
* Trigger reference
|
||||
*/
|
||||
trigger_ref: string;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,9 +38,5 @@ export type EventResponse = {
|
||||
* Trigger reference
|
||||
*/
|
||||
trigger_ref: string;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,54 +2,70 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ExecutionStatus } from './ExecutionStatus';
|
||||
import type { ExecutionStatus } from "./ExecutionStatus";
|
||||
/**
|
||||
* Response DTO for execution information
|
||||
*/
|
||||
export type ExecutionResponse = {
|
||||
/**
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
action?: number | null;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
action?: number | null;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow task metadata (only populated for workflow task executions)
|
||||
*/
|
||||
workflow_task?: {
|
||||
workflow_execution: number;
|
||||
task_name: string;
|
||||
task_index?: number | null;
|
||||
task_batch?: number | null;
|
||||
retry_count: number;
|
||||
max_retries: number;
|
||||
next_retry_at?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
timed_out: boolean;
|
||||
duration_ms?: number | null;
|
||||
started_at?: string | null;
|
||||
completed_at?: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,46 +2,62 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ExecutionStatus } from './ExecutionStatus';
|
||||
import type { ExecutionStatus } from "./ExecutionStatus";
|
||||
/**
|
||||
* Simplified execution response (for list endpoints)
|
||||
*/
|
||||
export type ExecutionSummary = {
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Rule reference (if triggered by a rule)
|
||||
*/
|
||||
rule_ref?: string | null;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Trigger reference (if triggered by a trigger)
|
||||
*/
|
||||
trigger_ref?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Rule reference (if triggered by a rule)
|
||||
*/
|
||||
rule_ref?: string | null;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Trigger reference (if triggered by a trigger)
|
||||
*/
|
||||
trigger_ref?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow task metadata (only populated for workflow task executions)
|
||||
*/
|
||||
workflow_task?: {
|
||||
workflow_execution: number;
|
||||
task_name: string;
|
||||
task_index?: number | null;
|
||||
task_batch?: number | null;
|
||||
retry_count: number;
|
||||
max_retries: number;
|
||||
next_retry_at?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
timed_out: boolean;
|
||||
duration_ms?: number | null;
|
||||
started_at?: string | null;
|
||||
completed_at?: string | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,20 +6,21 @@
|
||||
* Request DTO for installing a pack from remote source
|
||||
*/
|
||||
export type InstallPackRequest = {
|
||||
/**
|
||||
* Git branch, tag, or commit reference
|
||||
*/
|
||||
ref_spec?: string | null;
|
||||
/**
|
||||
* Skip dependency validation (not recommended)
|
||||
*/
|
||||
skip_deps?: boolean;
|
||||
/**
|
||||
* Skip running pack tests during installation
|
||||
*/
|
||||
skip_tests?: boolean;
|
||||
/**
|
||||
* Repository URL or source location
|
||||
*/
|
||||
source: string;
|
||||
/**
|
||||
* Git branch, tag, or commit reference
|
||||
*/
|
||||
ref_spec?: string | null;
|
||||
/**
|
||||
* Skip dependency validation (not recommended)
|
||||
*/
|
||||
skip_deps?: boolean;
|
||||
/**
|
||||
* Skip running pack tests during installation
|
||||
*/
|
||||
skip_tests?: boolean;
|
||||
/**
|
||||
* Repository URL or source location
|
||||
*/
|
||||
source: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ export type PackResponse = {
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -43,7 +47,7 @@ export type PackResponse = {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps: Array<string>;
|
||||
/**
|
||||
|
||||
@@ -2,55 +2,62 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PaginationMeta } from './PaginationMeta';
|
||||
import type { PaginationMeta } from "./PaginationMeta";
|
||||
/**
|
||||
* Paginated response wrapper
|
||||
*/
|
||||
export type PaginatedResponse_ActionSummary = {
|
||||
/**
|
||||
* The data items
|
||||
*/
|
||||
data: Array<{
|
||||
/**
|
||||
* The data items
|
||||
* Creation timestamp
|
||||
*/
|
||||
data: Array<{
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
}>;
|
||||
created: string;
|
||||
/**
|
||||
* Pagination metadata
|
||||
* Action description
|
||||
*/
|
||||
pagination: PaginationMeta;
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
}>;
|
||||
/**
|
||||
* Pagination metadata
|
||||
*/
|
||||
pagination: PaginationMeta;
|
||||
};
|
||||
|
||||
|
||||
@@ -2,56 +2,72 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ExecutionStatus } from './ExecutionStatus';
|
||||
import type { PaginationMeta } from './PaginationMeta';
|
||||
import type { ExecutionStatus } from "./ExecutionStatus";
|
||||
import type { PaginationMeta } from "./PaginationMeta";
|
||||
/**
|
||||
* Paginated response wrapper
|
||||
*/
|
||||
export type PaginatedResponse_ExecutionSummary = {
|
||||
/**
|
||||
* The data items
|
||||
*/
|
||||
data: Array<{
|
||||
/**
|
||||
* The data items
|
||||
* Action reference
|
||||
*/
|
||||
data: Array<{
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Rule reference (if triggered by a rule)
|
||||
*/
|
||||
rule_ref?: string | null;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Trigger reference (if triggered by a trigger)
|
||||
*/
|
||||
trigger_ref?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
}>;
|
||||
action_ref: string;
|
||||
/**
|
||||
* Pagination metadata
|
||||
* Creation timestamp
|
||||
*/
|
||||
pagination: PaginationMeta;
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Rule reference (if triggered by a rule)
|
||||
*/
|
||||
rule_ref?: string | null;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Trigger reference (if triggered by a trigger)
|
||||
*/
|
||||
trigger_ref?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow task metadata (only populated for workflow task executions)
|
||||
*/
|
||||
workflow_task?: {
|
||||
workflow_execution: number;
|
||||
task_name: string;
|
||||
task_index?: number | null;
|
||||
task_batch?: number | null;
|
||||
retry_count: number;
|
||||
max_retries: number;
|
||||
next_retry_at?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
timed_out: boolean;
|
||||
duration_ms?: number | null;
|
||||
started_at?: string | null;
|
||||
completed_at?: string | null;
|
||||
} | null;
|
||||
}>;
|
||||
/**
|
||||
* Pagination metadata
|
||||
*/
|
||||
pagination: PaginationMeta;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
*/
|
||||
export type RuleResponse = {
|
||||
/**
|
||||
* Action ID
|
||||
* Action ID (null if the referenced action has been deleted)
|
||||
*/
|
||||
action: number;
|
||||
action?: number | null;
|
||||
/**
|
||||
* Parameters to pass to the action when rule is triggered
|
||||
*/
|
||||
@@ -59,9 +59,9 @@ export type RuleResponse = {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Trigger ID
|
||||
* Trigger ID (null if the referenced trigger has been deleted)
|
||||
*/
|
||||
trigger: number;
|
||||
trigger?: number | null;
|
||||
/**
|
||||
* Parameters for trigger configuration and event filtering
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,7 @@ export type SensorResponse = {
|
||||
*/
|
||||
pack_ref?: string | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ export type TriggerResponse = {
|
||||
*/
|
||||
pack_ref?: string | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -23,12 +23,16 @@ export type UpdateActionRequest = {
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Optional semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ export type UpdatePackRequest = {
|
||||
* Pack configuration values
|
||||
*/
|
||||
config: any | null;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies?: any[] | null;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -31,7 +35,7 @@ export type UpdatePackRequest = {
|
||||
*/
|
||||
meta: any | null;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps?: any[] | null;
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,7 @@ export type UpdateSensorRequest = {
|
||||
*/
|
||||
label?: string | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ export type UpdateTriggerRequest = {
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ export type UpdateWorkflowRequest = {
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@ export type WorkflowResponse = {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
@@ -2,432 +2,456 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { CreateActionRequest } from '../models/CreateActionRequest';
|
||||
import type { PaginatedResponse_ActionSummary } from '../models/PaginatedResponse_ActionSummary';
|
||||
import type { SuccessResponse } from '../models/SuccessResponse';
|
||||
import type { UpdateActionRequest } from '../models/UpdateActionRequest';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
import type { CreateActionRequest } from "../models/CreateActionRequest";
|
||||
import type { PaginatedResponse_ActionSummary } from "../models/PaginatedResponse_ActionSummary";
|
||||
import type { SuccessResponse } from "../models/SuccessResponse";
|
||||
import type { UpdateActionRequest } from "../models/UpdateActionRequest";
|
||||
import type { CancelablePromise } from "../core/CancelablePromise";
|
||||
import { OpenAPI } from "../core/OpenAPI";
|
||||
import { request as __request } from "../core/request";
|
||||
export class ActionsService {
|
||||
/**
|
||||
* List all actions with pagination
|
||||
* @returns PaginatedResponse_ActionSummary List of actions
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listActions({
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* List all actions with pagination
|
||||
* @returns PaginatedResponse_ActionSummary List of actions
|
||||
* @throws ApiError
|
||||
* Page number (1-based)
|
||||
*/
|
||||
public static listActions({
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number,
|
||||
}): CancelablePromise<PaginatedResponse_ActionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/actions',
|
||||
query: {
|
||||
'page': page,
|
||||
'page_size': pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
page?: number;
|
||||
/**
|
||||
* Create a new action
|
||||
* @returns any Action created successfully
|
||||
* @throws ApiError
|
||||
* Number of items per page
|
||||
*/
|
||||
public static createAction({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: CreateActionRequest,
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for action information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/api/v1/actions',
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
400: `Validation error`,
|
||||
404: `Pack not found`,
|
||||
409: `Action with same ref already exists`,
|
||||
},
|
||||
});
|
||||
}
|
||||
pageSize?: number;
|
||||
}): CancelablePromise<PaginatedResponse_ActionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/actions",
|
||||
query: {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create a new action
|
||||
* @returns any Action created successfully
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static createAction({
|
||||
requestBody,
|
||||
}: {
|
||||
requestBody: CreateActionRequest;
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Get a single action by reference
|
||||
* @returns any Action details
|
||||
* @throws ApiError
|
||||
* Response DTO for action information
|
||||
*/
|
||||
public static getAction({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string,
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for action information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/actions/{ref}',
|
||||
path: {
|
||||
'ref': ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
/**
|
||||
* Update an existing action
|
||||
* @returns any Action updated successfully
|
||||
* @throws ApiError
|
||||
* Optional message
|
||||
*/
|
||||
public static updateAction({
|
||||
ref,
|
||||
requestBody,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string,
|
||||
requestBody: UpdateActionRequest,
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for action information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'PUT',
|
||||
url: '/api/v1/actions/{ref}',
|
||||
path: {
|
||||
'ref': ref,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
400: `Validation error`,
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: "POST",
|
||||
url: "/api/v1/actions",
|
||||
body: requestBody,
|
||||
mediaType: "application/json",
|
||||
errors: {
|
||||
400: `Validation error`,
|
||||
404: `Pack not found`,
|
||||
409: `Action with same ref already exists`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get a single action by reference
|
||||
* @returns any Action details
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getAction({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Delete an action
|
||||
* @returns SuccessResponse Action deleted successfully
|
||||
* @throws ApiError
|
||||
* Action reference identifier
|
||||
*/
|
||||
public static deleteAction({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string,
|
||||
}): CancelablePromise<SuccessResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'DELETE',
|
||||
url: '/api/v1/actions/{ref}',
|
||||
path: {
|
||||
'ref': ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
ref: string;
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Get queue statistics for an action
|
||||
* @returns any Queue statistics
|
||||
* @throws ApiError
|
||||
* Response DTO for action information
|
||||
*/
|
||||
public static getQueueStats({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string,
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for queue statistics
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
action_id: number;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Number of currently running executions
|
||||
*/
|
||||
active_count: number;
|
||||
/**
|
||||
* Timestamp of last statistics update
|
||||
*/
|
||||
last_updated: string;
|
||||
/**
|
||||
* Maximum concurrent executions allowed
|
||||
*/
|
||||
max_concurrent: number;
|
||||
/**
|
||||
* Timestamp of oldest queued execution (if any)
|
||||
*/
|
||||
oldest_enqueued_at?: string | null;
|
||||
/**
|
||||
* Number of executions waiting in queue
|
||||
*/
|
||||
queue_length: number;
|
||||
/**
|
||||
* Total executions completed since queue creation
|
||||
*/
|
||||
total_completed: number;
|
||||
/**
|
||||
* Total executions enqueued since queue creation
|
||||
*/
|
||||
total_enqueued: number;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/actions/{ref}/queue-stats',
|
||||
path: {
|
||||
'ref': ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found or no queue statistics available`,
|
||||
},
|
||||
});
|
||||
}
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
/**
|
||||
* List actions by pack reference
|
||||
* @returns PaginatedResponse_ActionSummary List of actions for pack
|
||||
* @throws ApiError
|
||||
* Optional message
|
||||
*/
|
||||
public static listActionsByPack({
|
||||
packRef,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Pack reference identifier
|
||||
*/
|
||||
packRef: string,
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number,
|
||||
}): CancelablePromise<PaginatedResponse_ActionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/packs/{pack_ref}/actions',
|
||||
path: {
|
||||
'pack_ref': packRef,
|
||||
},
|
||||
query: {
|
||||
'page': page,
|
||||
'page_size': pageSize,
|
||||
},
|
||||
errors: {
|
||||
404: `Pack not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/actions/{ref}",
|
||||
path: {
|
||||
ref: ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Update an existing action
|
||||
* @returns any Action updated successfully
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static updateAction({
|
||||
ref,
|
||||
requestBody,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
requestBody: UpdateActionRequest;
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for action information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Action description
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* Entry point
|
||||
*/
|
||||
entrypoint: string;
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Whether this is an ad-hoc action (not from pack installation)
|
||||
*/
|
||||
is_adhoc: boolean;
|
||||
/**
|
||||
* Human-readable label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Output schema
|
||||
*/
|
||||
out_schema: any | null;
|
||||
/**
|
||||
* Pack ID
|
||||
*/
|
||||
pack: number;
|
||||
/**
|
||||
* Pack reference
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
* Unique reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime ID
|
||||
*/
|
||||
runtime?: number | null;
|
||||
/**
|
||||
* Semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
*/
|
||||
runtime_version_constraint?: string | null;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow definition ID (non-null if this action is a workflow)
|
||||
*/
|
||||
workflow_def?: number | null;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: "PUT",
|
||||
url: "/api/v1/actions/{ref}",
|
||||
path: {
|
||||
ref: ref,
|
||||
},
|
||||
body: requestBody,
|
||||
mediaType: "application/json",
|
||||
errors: {
|
||||
400: `Validation error`,
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Delete an action
|
||||
* @returns SuccessResponse Action deleted successfully
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static deleteAction({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
}): CancelablePromise<SuccessResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "DELETE",
|
||||
url: "/api/v1/actions/{ref}",
|
||||
path: {
|
||||
ref: ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get queue statistics for an action
|
||||
* @returns any Queue statistics
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getQueueStats({
|
||||
ref,
|
||||
}: {
|
||||
/**
|
||||
* Action reference identifier
|
||||
*/
|
||||
ref: string;
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for queue statistics
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID
|
||||
*/
|
||||
action_id: number;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Number of currently running executions
|
||||
*/
|
||||
active_count: number;
|
||||
/**
|
||||
* Timestamp of last statistics update
|
||||
*/
|
||||
last_updated: string;
|
||||
/**
|
||||
* Maximum concurrent executions allowed
|
||||
*/
|
||||
max_concurrent: number;
|
||||
/**
|
||||
* Timestamp of oldest queued execution (if any)
|
||||
*/
|
||||
oldest_enqueued_at?: string | null;
|
||||
/**
|
||||
* Number of executions waiting in queue
|
||||
*/
|
||||
queue_length: number;
|
||||
/**
|
||||
* Total executions completed since queue creation
|
||||
*/
|
||||
total_completed: number;
|
||||
/**
|
||||
* Total executions enqueued since queue creation
|
||||
*/
|
||||
total_enqueued: number;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/actions/{ref}/queue-stats",
|
||||
path: {
|
||||
ref: ref,
|
||||
},
|
||||
errors: {
|
||||
404: `Action not found or no queue statistics available`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* List actions by pack reference
|
||||
* @returns PaginatedResponse_ActionSummary List of actions for pack
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listActionsByPack({
|
||||
packRef,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Pack reference identifier
|
||||
*/
|
||||
packRef: string;
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number;
|
||||
}): CancelablePromise<PaginatedResponse_ActionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/packs/{pack_ref}/actions",
|
||||
path: {
|
||||
pack_ref: packRef,
|
||||
},
|
||||
query: {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
},
|
||||
errors: {
|
||||
404: `Pack not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,92 +2,92 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ApiResponse_EventResponse } from "../models/ApiResponse_EventResponse";
|
||||
import type { i64 } from "../models/i64";
|
||||
import type { PaginatedResponse_EventSummary } from "../models/PaginatedResponse_EventSummary";
|
||||
import type { CancelablePromise } from "../core/CancelablePromise";
|
||||
import { OpenAPI } from "../core/OpenAPI";
|
||||
import { request as __request } from "../core/request";
|
||||
import type { ApiResponse_EventResponse } from '../models/ApiResponse_EventResponse';
|
||||
import type { i64 } from '../models/i64';
|
||||
import type { PaginatedResponse_EventSummary } from '../models/PaginatedResponse_EventSummary';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
export class EventsService {
|
||||
/**
|
||||
* List all events with pagination and optional filters
|
||||
* @returns PaginatedResponse_EventSummary List of events
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listEvents({
|
||||
trigger,
|
||||
triggerRef,
|
||||
ruleRef,
|
||||
source,
|
||||
page,
|
||||
perPage,
|
||||
}: {
|
||||
/**
|
||||
* Filter by trigger ID
|
||||
* List all events with pagination and optional filters
|
||||
* @returns PaginatedResponse_EventSummary List of events
|
||||
* @throws ApiError
|
||||
*/
|
||||
trigger?: null | i64;
|
||||
public static listEvents({
|
||||
trigger,
|
||||
triggerRef,
|
||||
ruleRef,
|
||||
source,
|
||||
page,
|
||||
perPage,
|
||||
}: {
|
||||
/**
|
||||
* Filter by trigger ID
|
||||
*/
|
||||
trigger?: (null | i64),
|
||||
/**
|
||||
* Filter by trigger reference
|
||||
*/
|
||||
triggerRef?: string | null,
|
||||
/**
|
||||
* Filter by rule reference
|
||||
*/
|
||||
ruleRef?: string | null,
|
||||
/**
|
||||
* Filter by source ID
|
||||
*/
|
||||
source?: (null | i64),
|
||||
/**
|
||||
* Page number (1-indexed)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Items per page
|
||||
*/
|
||||
perPage?: number,
|
||||
}): CancelablePromise<PaginatedResponse_EventSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/events',
|
||||
query: {
|
||||
'trigger': trigger,
|
||||
'trigger_ref': triggerRef,
|
||||
'rule_ref': ruleRef,
|
||||
'source': source,
|
||||
'page': page,
|
||||
'per_page': perPage,
|
||||
},
|
||||
errors: {
|
||||
401: `Unauthorized`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Filter by trigger reference
|
||||
* Get a single event by ID
|
||||
* @returns ApiResponse_EventResponse Event details
|
||||
* @throws ApiError
|
||||
*/
|
||||
triggerRef?: string | null;
|
||||
/**
|
||||
* Filter by rule reference
|
||||
*/
|
||||
ruleRef?: string | null;
|
||||
/**
|
||||
* Filter by source ID
|
||||
*/
|
||||
source?: null | i64;
|
||||
/**
|
||||
* Page number (1-indexed)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Items per page
|
||||
*/
|
||||
perPage?: number;
|
||||
}): CancelablePromise<PaginatedResponse_EventSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/events",
|
||||
query: {
|
||||
trigger: trigger,
|
||||
trigger_ref: triggerRef,
|
||||
rule_ref: ruleRef,
|
||||
source: source,
|
||||
page: page,
|
||||
per_page: perPage,
|
||||
},
|
||||
errors: {
|
||||
401: `Unauthorized`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get a single event by ID
|
||||
* @returns ApiResponse_EventResponse Event details
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getEvent({
|
||||
id,
|
||||
}: {
|
||||
/**
|
||||
* Event ID
|
||||
*/
|
||||
id: number;
|
||||
}): CancelablePromise<ApiResponse_EventResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/events/{id}",
|
||||
path: {
|
||||
id: id,
|
||||
},
|
||||
errors: {
|
||||
401: `Unauthorized`,
|
||||
404: `Event not found`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
public static getEvent({
|
||||
id,
|
||||
}: {
|
||||
/**
|
||||
* Event ID
|
||||
*/
|
||||
id: number,
|
||||
}): CancelablePromise<ApiResponse_EventResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/events/{id}',
|
||||
path: {
|
||||
'id': id,
|
||||
},
|
||||
errors: {
|
||||
401: `Unauthorized`,
|
||||
404: `Event not found`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,260 +2,283 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { ExecutionStatus } from '../models/ExecutionStatus';
|
||||
import type { PaginatedResponse_ExecutionSummary } from '../models/PaginatedResponse_ExecutionSummary';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
import type { ExecutionStatus } from "../models/ExecutionStatus";
|
||||
import type { PaginatedResponse_ExecutionSummary } from "../models/PaginatedResponse_ExecutionSummary";
|
||||
import type { CancelablePromise } from "../core/CancelablePromise";
|
||||
import { OpenAPI } from "../core/OpenAPI";
|
||||
import { request as __request } from "../core/request";
|
||||
export class ExecutionsService {
|
||||
/**
|
||||
* List all executions with pagination and optional filters
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listExecutions({
|
||||
status,
|
||||
actionRef,
|
||||
packName,
|
||||
ruleRef,
|
||||
triggerRef,
|
||||
executor,
|
||||
resultContains,
|
||||
enforcement,
|
||||
parent,
|
||||
topLevelOnly,
|
||||
page,
|
||||
perPage,
|
||||
}: {
|
||||
/**
|
||||
* List all executions with pagination and optional filters
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions
|
||||
* @throws ApiError
|
||||
* Filter by execution status
|
||||
*/
|
||||
public static listExecutions({
|
||||
status,
|
||||
actionRef,
|
||||
packName,
|
||||
ruleRef,
|
||||
triggerRef,
|
||||
executor,
|
||||
resultContains,
|
||||
enforcement,
|
||||
parent,
|
||||
page,
|
||||
perPage,
|
||||
}: {
|
||||
/**
|
||||
* Filter by execution status
|
||||
*/
|
||||
status?: (null | ExecutionStatus),
|
||||
/**
|
||||
* Filter by action reference
|
||||
*/
|
||||
actionRef?: string | null,
|
||||
/**
|
||||
* Filter by pack name
|
||||
*/
|
||||
packName?: string | null,
|
||||
/**
|
||||
* Filter by rule reference
|
||||
*/
|
||||
ruleRef?: string | null,
|
||||
/**
|
||||
* Filter by trigger reference
|
||||
*/
|
||||
triggerRef?: string | null,
|
||||
/**
|
||||
* Filter by executor ID
|
||||
*/
|
||||
executor?: number | null,
|
||||
/**
|
||||
* Search in result JSON (case-insensitive substring match)
|
||||
*/
|
||||
resultContains?: string | null,
|
||||
/**
|
||||
* Filter by enforcement ID
|
||||
*/
|
||||
enforcement?: number | null,
|
||||
/**
|
||||
* Filter by parent execution ID
|
||||
*/
|
||||
parent?: number | null,
|
||||
/**
|
||||
* Page number (for pagination)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Items per page (for pagination)
|
||||
*/
|
||||
perPage?: number,
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/executions',
|
||||
query: {
|
||||
'status': status,
|
||||
'action_ref': actionRef,
|
||||
'pack_name': packName,
|
||||
'rule_ref': ruleRef,
|
||||
'trigger_ref': triggerRef,
|
||||
'executor': executor,
|
||||
'result_contains': resultContains,
|
||||
'enforcement': enforcement,
|
||||
'parent': parent,
|
||||
'page': page,
|
||||
'per_page': perPage,
|
||||
},
|
||||
});
|
||||
}
|
||||
status?: null | ExecutionStatus;
|
||||
/**
|
||||
* List executions by enforcement ID
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions for enforcement
|
||||
* @throws ApiError
|
||||
* Filter by action reference
|
||||
*/
|
||||
public static listExecutionsByEnforcement({
|
||||
enforcementId,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcementId: number,
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number,
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/executions/enforcement/{enforcement_id}',
|
||||
path: {
|
||||
'enforcement_id': enforcementId,
|
||||
},
|
||||
query: {
|
||||
'page': page,
|
||||
'page_size': pageSize,
|
||||
},
|
||||
errors: {
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
actionRef?: string | null;
|
||||
/**
|
||||
* Get execution statistics
|
||||
* @returns any Execution statistics
|
||||
* @throws ApiError
|
||||
* Filter by pack name
|
||||
*/
|
||||
public static getExecutionStats(): CancelablePromise<Record<string, any>> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/executions/stats',
|
||||
errors: {
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
packName?: string | null;
|
||||
/**
|
||||
* List executions by status
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions with specified status
|
||||
* @throws ApiError
|
||||
* Filter by rule reference
|
||||
*/
|
||||
public static listExecutionsByStatus({
|
||||
status,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Execution status (requested, scheduling, scheduled, running, completed, failed, canceling, cancelled, timeout, abandoned)
|
||||
*/
|
||||
status: string,
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number,
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number,
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/executions/status/{status}',
|
||||
path: {
|
||||
'status': status,
|
||||
},
|
||||
query: {
|
||||
'page': page,
|
||||
'page_size': pageSize,
|
||||
},
|
||||
errors: {
|
||||
400: `Invalid status`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
ruleRef?: string | null;
|
||||
/**
|
||||
* Get a single execution by ID
|
||||
* @returns any Execution details
|
||||
* @throws ApiError
|
||||
* Filter by trigger reference
|
||||
*/
|
||||
public static getExecution({
|
||||
id,
|
||||
}: {
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number,
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for execution information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
action?: number | null;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/api/v1/executions/{id}',
|
||||
path: {
|
||||
'id': id,
|
||||
},
|
||||
errors: {
|
||||
404: `Execution not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
triggerRef?: string | null;
|
||||
/**
|
||||
* Filter by executor ID
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Search in result JSON (case-insensitive substring match)
|
||||
*/
|
||||
resultContains?: string | null;
|
||||
/**
|
||||
* Filter by enforcement ID
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Filter by parent execution ID
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* If true, only return top-level executions (those without a parent)
|
||||
*/
|
||||
topLevelOnly?: boolean | null;
|
||||
/**
|
||||
* Page number (for pagination)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Items per page (for pagination)
|
||||
*/
|
||||
perPage?: number;
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/executions",
|
||||
query: {
|
||||
status: status,
|
||||
action_ref: actionRef,
|
||||
pack_name: packName,
|
||||
rule_ref: ruleRef,
|
||||
trigger_ref: triggerRef,
|
||||
executor: executor,
|
||||
result_contains: resultContains,
|
||||
enforcement: enforcement,
|
||||
parent: parent,
|
||||
top_level_only: topLevelOnly,
|
||||
page: page,
|
||||
per_page: perPage,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* List executions by enforcement ID
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions for enforcement
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listExecutionsByEnforcement({
|
||||
enforcementId,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Enforcement ID
|
||||
*/
|
||||
enforcementId: number;
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number;
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/executions/enforcement/{enforcement_id}",
|
||||
path: {
|
||||
enforcement_id: enforcementId,
|
||||
},
|
||||
query: {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
},
|
||||
errors: {
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get execution statistics
|
||||
* @returns any Execution statistics
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getExecutionStats(): CancelablePromise<Record<string, any>> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/executions/stats",
|
||||
errors: {
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* List executions by status
|
||||
* @returns PaginatedResponse_ExecutionSummary List of executions with specified status
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static listExecutionsByStatus({
|
||||
status,
|
||||
page,
|
||||
pageSize,
|
||||
}: {
|
||||
/**
|
||||
* Execution status (requested, scheduling, scheduled, running, completed, failed, canceling, cancelled, timeout, abandoned)
|
||||
*/
|
||||
status: string;
|
||||
/**
|
||||
* Page number (1-based)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
pageSize?: number;
|
||||
}): CancelablePromise<PaginatedResponse_ExecutionSummary> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/executions/status/{status}",
|
||||
path: {
|
||||
status: status,
|
||||
},
|
||||
query: {
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
},
|
||||
errors: {
|
||||
400: `Invalid status`,
|
||||
500: `Internal server error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get a single execution by ID
|
||||
* @returns any Execution details
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static getExecution({
|
||||
id,
|
||||
}: {
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
}): CancelablePromise<{
|
||||
/**
|
||||
* Response DTO for execution information
|
||||
*/
|
||||
data: {
|
||||
/**
|
||||
* Action ID (optional, may be null for ad-hoc executions)
|
||||
*/
|
||||
action?: number | null;
|
||||
/**
|
||||
* Action reference
|
||||
*/
|
||||
action_ref: string;
|
||||
/**
|
||||
* Execution configuration/parameters
|
||||
*/
|
||||
config: Record<string, any>;
|
||||
/**
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Enforcement ID (rule enforcement that triggered this)
|
||||
*/
|
||||
enforcement?: number | null;
|
||||
/**
|
||||
* Executor ID (worker/executor that ran this)
|
||||
*/
|
||||
executor?: number | null;
|
||||
/**
|
||||
* Execution ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* Parent execution ID (for nested/child executions)
|
||||
*/
|
||||
parent?: number | null;
|
||||
/**
|
||||
* Execution result/output
|
||||
*/
|
||||
result: Record<string, any>;
|
||||
/**
|
||||
* Execution status
|
||||
*/
|
||||
status: ExecutionStatus;
|
||||
/**
|
||||
* Last update timestamp
|
||||
*/
|
||||
updated: string;
|
||||
/**
|
||||
* Workflow task metadata (only populated for workflow task executions)
|
||||
*/
|
||||
workflow_task?: {
|
||||
workflow_execution: number;
|
||||
task_name: string;
|
||||
task_index?: number | null;
|
||||
task_batch?: number | null;
|
||||
retry_count: number;
|
||||
max_retries: number;
|
||||
next_retry_at?: string | null;
|
||||
timeout_seconds?: number | null;
|
||||
timed_out: boolean;
|
||||
duration_ms?: number | null;
|
||||
started_at?: string | null;
|
||||
completed_at?: string | null;
|
||||
} | null;
|
||||
};
|
||||
/**
|
||||
* Optional message
|
||||
*/
|
||||
message?: string | null;
|
||||
}> {
|
||||
return __request(OpenAPI, {
|
||||
method: "GET",
|
||||
url: "/api/v1/executions/{id}",
|
||||
path: {
|
||||
id: id,
|
||||
},
|
||||
errors: {
|
||||
404: `Execution not found`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ export class PacksService {
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -96,7 +100,7 @@ export class PacksService {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps: Array<string>;
|
||||
/**
|
||||
@@ -145,7 +149,6 @@ export class PacksService {
|
||||
mediaType: 'application/json',
|
||||
errors: {
|
||||
400: `Invalid request or tests failed`,
|
||||
409: `Pack already exists`,
|
||||
501: `Not implemented yet`,
|
||||
},
|
||||
});
|
||||
@@ -200,6 +203,10 @@ export class PacksService {
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -225,7 +232,7 @@ export class PacksService {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps: Array<string>;
|
||||
/**
|
||||
@@ -288,6 +295,10 @@ export class PacksService {
|
||||
* Creation timestamp
|
||||
*/
|
||||
created: string;
|
||||
/**
|
||||
* Pack dependencies (refs of required packs)
|
||||
*/
|
||||
dependencies: Array<string>;
|
||||
/**
|
||||
* Pack description
|
||||
*/
|
||||
@@ -313,7 +324,7 @@ export class PacksService {
|
||||
*/
|
||||
ref: string;
|
||||
/**
|
||||
* Runtime dependencies
|
||||
* Runtime dependencies (e.g., shell, python, nodejs)
|
||||
*/
|
||||
runtime_deps: Array<string>;
|
||||
/**
|
||||
|
||||
@@ -150,7 +150,7 @@ export class WorkflowsService {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
@@ -241,7 +241,7 @@ export class WorkflowsService {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
@@ -333,7 +333,7 @@ export class WorkflowsService {
|
||||
*/
|
||||
pack_ref: string;
|
||||
/**
|
||||
* Parameter schema
|
||||
* Parameter schema (StackStorm-style with inline required/secret)
|
||||
*/
|
||||
param_schema: any | null;
|
||||
/**
|
||||
|
||||
312
web/src/components/common/WorkflowTasksPanel.tsx
Normal file
312
web/src/components/common/WorkflowTasksPanel.tsx
Normal file
@@ -0,0 +1,312 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Workflow,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
Clock,
|
||||
Loader2,
|
||||
AlertTriangle,
|
||||
Ban,
|
||||
CircleDot,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { useChildExecutions } from "@/hooks/useExecutions";
|
||||
|
||||
interface WorkflowTasksPanelProps {
|
||||
/** The parent (workflow) execution ID */
|
||||
parentExecutionId: number;
|
||||
/** Whether the panel starts collapsed (default: false — open by default for workflows) */
|
||||
defaultCollapsed?: boolean;
|
||||
}
|
||||
|
||||
/** Format a duration in ms to a human-readable string. */
|
||||
function formatDuration(ms: number): string {
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
const secs = ms / 1000;
|
||||
if (secs < 60) return `${secs.toFixed(1)}s`;
|
||||
const mins = Math.floor(secs / 60);
|
||||
const remainSecs = Math.round(secs % 60);
|
||||
if (mins < 60) return `${mins}m ${remainSecs}s`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
const remainMins = mins % 60;
|
||||
return `${hrs}h ${remainMins}m`;
|
||||
}
|
||||
|
||||
function getStatusIcon(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
||||
case "failed":
|
||||
return <XCircle className="h-4 w-4 text-red-500" />;
|
||||
case "running":
|
||||
return <Loader2 className="h-4 w-4 text-blue-500 animate-spin" />;
|
||||
case "requested":
|
||||
case "scheduling":
|
||||
case "scheduled":
|
||||
return <Clock className="h-4 w-4 text-yellow-500" />;
|
||||
case "timeout":
|
||||
return <AlertTriangle className="h-4 w-4 text-orange-500" />;
|
||||
case "canceling":
|
||||
case "cancelled":
|
||||
return <Ban className="h-4 w-4 text-gray-400" />;
|
||||
case "abandoned":
|
||||
return <AlertTriangle className="h-4 w-4 text-red-400" />;
|
||||
default:
|
||||
return <CircleDot className="h-4 w-4 text-gray-400" />;
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusBadgeClasses(status: string): string {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return "bg-green-100 text-green-800";
|
||||
case "failed":
|
||||
return "bg-red-100 text-red-800";
|
||||
case "running":
|
||||
return "bg-blue-100 text-blue-800";
|
||||
case "requested":
|
||||
case "scheduling":
|
||||
case "scheduled":
|
||||
return "bg-yellow-100 text-yellow-800";
|
||||
case "timeout":
|
||||
return "bg-orange-100 text-orange-800";
|
||||
case "canceling":
|
||||
case "cancelled":
|
||||
return "bg-gray-100 text-gray-800";
|
||||
case "abandoned":
|
||||
return "bg-red-100 text-red-600";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel that displays workflow task (child) executions for a parent
|
||||
* workflow execution. Shows each task's name, action, status, and timing.
|
||||
*/
|
||||
export default function WorkflowTasksPanel({
|
||||
parentExecutionId,
|
||||
defaultCollapsed = false,
|
||||
}: WorkflowTasksPanelProps) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
|
||||
const { data, isLoading, error } = useChildExecutions(parentExecutionId);
|
||||
|
||||
const tasks = useMemo(() => {
|
||||
if (!data?.data) return [];
|
||||
return data.data;
|
||||
}, [data]);
|
||||
|
||||
const summary = useMemo(() => {
|
||||
const total = tasks.length;
|
||||
const completed = tasks.filter((t) => t.status === "completed").length;
|
||||
const failed = tasks.filter((t) => t.status === "failed").length;
|
||||
const running = tasks.filter(
|
||||
(t) =>
|
||||
t.status === "running" ||
|
||||
t.status === "requested" ||
|
||||
t.status === "scheduling" ||
|
||||
t.status === "scheduled",
|
||||
).length;
|
||||
const other = total - completed - failed - running;
|
||||
return { total, completed, failed, running, other };
|
||||
}, [tasks]);
|
||||
|
||||
if (!isLoading && tasks.length === 0 && !error) {
|
||||
// No child tasks — nothing to show
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
{/* Header */}
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="w-full flex items-center justify-between p-6 text-left hover:bg-gray-50 rounded-lg transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-5 w-5 text-gray-400" />
|
||||
) : (
|
||||
<ChevronDown className="h-5 w-5 text-gray-400" />
|
||||
)}
|
||||
<Workflow className="h-5 w-5 text-indigo-500" />
|
||||
<h2 className="text-xl font-semibold">Workflow Tasks</h2>
|
||||
{!isLoading && (
|
||||
<span className="text-sm text-gray-500">
|
||||
({summary.total} task{summary.total !== 1 ? "s" : ""})
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Summary badges */}
|
||||
{!isCollapsed || !isLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{summary.completed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
{summary.completed}
|
||||
</span>
|
||||
)}
|
||||
{summary.running > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
{summary.running}
|
||||
</span>
|
||||
)}
|
||||
{summary.failed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
<XCircle className="h-3 w-3" />
|
||||
{summary.failed}
|
||||
</span>
|
||||
)}
|
||||
{summary.other > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-700">
|
||||
{summary.other}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
|
||||
{/* Content */}
|
||||
{!isCollapsed && (
|
||||
<div className="px-6 pb-6">
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-5 w-5 animate-spin text-gray-400" />
|
||||
<span className="ml-2 text-sm text-gray-500">
|
||||
Loading workflow tasks…
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded text-sm">
|
||||
Error loading workflow tasks:{" "}
|
||||
{error instanceof Error ? error.message : "Unknown error"}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !error && tasks.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
{/* Column headers */}
|
||||
<div className="grid grid-cols-12 gap-3 px-3 py-2 text-xs font-medium text-gray-500 uppercase tracking-wider border-b border-gray-100">
|
||||
<div className="col-span-1">#</div>
|
||||
<div className="col-span-3">Task</div>
|
||||
<div className="col-span-3">Action</div>
|
||||
<div className="col-span-2">Status</div>
|
||||
<div className="col-span-2">Duration</div>
|
||||
<div className="col-span-1">Retry</div>
|
||||
</div>
|
||||
|
||||
{/* Task rows */}
|
||||
{tasks.map((task, idx) => {
|
||||
const wt = task.workflow_task;
|
||||
const taskName = wt?.task_name ?? `Task ${idx + 1}`;
|
||||
const retryCount = wt?.retry_count ?? 0;
|
||||
const maxRetries = wt?.max_retries ?? 0;
|
||||
const timedOut = wt?.timed_out ?? false;
|
||||
|
||||
// Compute duration from created → updated (best available)
|
||||
const created = new Date(task.created);
|
||||
const updated = new Date(task.updated);
|
||||
const durationMs =
|
||||
wt?.duration_ms ??
|
||||
(task.status === "completed" ||
|
||||
task.status === "failed" ||
|
||||
task.status === "timeout"
|
||||
? updated.getTime() - created.getTime()
|
||||
: null);
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={task.id}
|
||||
to={`/executions/${task.id}`}
|
||||
className="grid grid-cols-12 gap-3 px-3 py-3 rounded-lg hover:bg-gray-50 transition-colors items-center group"
|
||||
>
|
||||
{/* Index */}
|
||||
<div className="col-span-1 text-sm text-gray-400 font-mono">
|
||||
{idx + 1}
|
||||
</div>
|
||||
|
||||
{/* Task name */}
|
||||
<div className="col-span-3 flex items-center gap-2 min-w-0">
|
||||
{getStatusIcon(task.status)}
|
||||
<span
|
||||
className="text-sm font-medium text-gray-900 truncate group-hover:text-blue-600"
|
||||
title={taskName}
|
||||
>
|
||||
{taskName}
|
||||
</span>
|
||||
{wt?.task_index != null && (
|
||||
<span className="text-xs text-gray-400 flex-shrink-0">
|
||||
[{wt.task_index}]
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action ref */}
|
||||
<div className="col-span-3 min-w-0">
|
||||
<span
|
||||
className="text-sm text-gray-600 truncate block"
|
||||
title={task.action_ref}
|
||||
>
|
||||
{task.action_ref}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Status badge */}
|
||||
<div className="col-span-2 flex items-center gap-1.5">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${getStatusBadgeClasses(task.status)}`}
|
||||
>
|
||||
{task.status}
|
||||
</span>
|
||||
{timedOut && (
|
||||
<span title="Timed out">
|
||||
<AlertTriangle className="h-3.5 w-3.5 text-orange-500" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Duration */}
|
||||
<div className="col-span-2 text-sm text-gray-500">
|
||||
{task.status === "running" ? (
|
||||
<span className="text-blue-600">
|
||||
{formatDistanceToNow(created, { addSuffix: false })}…
|
||||
</span>
|
||||
) : durationMs != null && durationMs > 0 ? (
|
||||
formatDuration(durationMs)
|
||||
) : (
|
||||
<span className="text-gray-300">—</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Retry info */}
|
||||
<div className="col-span-1 text-sm text-gray-500">
|
||||
{maxRetries > 0 ? (
|
||||
<span
|
||||
className="inline-flex items-center gap-0.5"
|
||||
title={`Attempt ${retryCount + 1} of ${maxRetries + 1}`}
|
||||
>
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
{retryCount}/{maxRetries}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-300">—</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
297
web/src/components/executions/ExecutionPreviewPanel.tsx
Normal file
297
web/src/components/executions/ExecutionPreviewPanel.tsx
Normal file
@@ -0,0 +1,297 @@
|
||||
import { memo, useEffect } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { X, ExternalLink, Loader2 } from "lucide-react";
|
||||
import { useExecution } from "@/hooks/useExecutions";
|
||||
import { useExecutionStream } from "@/hooks/useExecutionStream";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import type { ExecutionStatus } from "@/api";
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
const secs = ms / 1000;
|
||||
if (secs < 60) return `${secs.toFixed(1)}s`;
|
||||
const mins = Math.floor(secs / 60);
|
||||
const remainSecs = Math.round(secs % 60);
|
||||
if (mins < 60) return `${mins}m ${remainSecs}s`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
const remainMins = mins % 60;
|
||||
return `${hrs}h ${remainMins}m`;
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "succeeded":
|
||||
case "completed":
|
||||
return "bg-green-100 text-green-800";
|
||||
case "failed":
|
||||
case "timeout":
|
||||
return "bg-red-100 text-red-800";
|
||||
case "running":
|
||||
return "bg-blue-100 text-blue-800";
|
||||
case "scheduled":
|
||||
case "scheduling":
|
||||
case "requested":
|
||||
return "bg-yellow-100 text-yellow-800";
|
||||
case "canceling":
|
||||
case "cancelled":
|
||||
return "bg-gray-100 text-gray-600";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800";
|
||||
}
|
||||
};
|
||||
|
||||
interface ExecutionPreviewPanelProps {
|
||||
executionId: number;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const ExecutionPreviewPanel = memo(function ExecutionPreviewPanel({
|
||||
executionId,
|
||||
onClose,
|
||||
}: ExecutionPreviewPanelProps) {
|
||||
const { data, isLoading, error } = useExecution(executionId);
|
||||
const execution = data?.data;
|
||||
|
||||
// Subscribe to real-time updates for this execution
|
||||
useExecutionStream({ executionId, enabled: true });
|
||||
|
||||
// Close on Escape key
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [onClose]);
|
||||
|
||||
const isRunning =
|
||||
execution?.status === "running" ||
|
||||
execution?.status === "scheduling" ||
|
||||
execution?.status === "scheduled" ||
|
||||
execution?.status === "requested";
|
||||
|
||||
const created = execution ? new Date(execution.created) : null;
|
||||
const updated = execution ? new Date(execution.updated) : null;
|
||||
const durationMs =
|
||||
created && updated && !isRunning
|
||||
? updated.getTime() - created.getTime()
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="border-l border-gray-200 bg-white flex flex-col h-full overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-200 bg-gray-50 flex-shrink-0">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<h3 className="text-sm font-semibold text-gray-900 truncate">
|
||||
Execution #{executionId}
|
||||
</h3>
|
||||
{execution && (
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full font-medium flex-shrink-0 ${getStatusColor(execution.status)}`}
|
||||
>
|
||||
{execution.status}
|
||||
</span>
|
||||
)}
|
||||
{isRunning && (
|
||||
<Loader2 className="h-3.5 w-3.5 text-blue-500 animate-spin flex-shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<Link
|
||||
to={`/executions/${executionId}`}
|
||||
className="p-1.5 text-gray-400 hover:text-blue-600 rounded hover:bg-gray-100 transition-colors"
|
||||
title="Open full detail page"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Link>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1.5 text-gray-400 hover:text-gray-600 rounded hover:bg-gray-100 transition-colors"
|
||||
title="Close preview (Esc)"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center h-32">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-gray-400" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && !execution && (
|
||||
<div className="p-4">
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-3 py-2 rounded text-sm">
|
||||
Error: {(error as Error).message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{execution && (
|
||||
<div className="divide-y divide-gray-100">
|
||||
{/* Action */}
|
||||
<div className="px-4 py-3">
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Action
|
||||
</dt>
|
||||
<dd className="mt-1">
|
||||
<Link
|
||||
to={`/actions/${execution.action_ref}`}
|
||||
className="text-sm text-blue-600 hover:text-blue-800 font-medium"
|
||||
>
|
||||
{execution.action_ref}
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
{/* Timing */}
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Created
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-gray-900">
|
||||
{created!.toLocaleString()}
|
||||
<span className="text-gray-400 ml-1.5 text-xs">
|
||||
{formatDistanceToNow(created!, { addSuffix: true })}
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
{durationMs != null && durationMs > 0 && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Duration
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-gray-900">
|
||||
{formatDuration(durationMs)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{isRunning && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Elapsed
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-blue-600 flex items-center gap-1.5">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
{formatDistanceToNow(created!)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* References */}
|
||||
<div className="px-4 py-3 space-y-2">
|
||||
{execution.parent && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Parent Execution
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm">
|
||||
<Link
|
||||
to={`/executions/${execution.parent}`}
|
||||
className="text-blue-600 hover:text-blue-800 font-mono"
|
||||
>
|
||||
#{execution.parent}
|
||||
</Link>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{execution.enforcement && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Enforcement
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-gray-900 font-mono">
|
||||
#{execution.enforcement}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{execution.executor && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Executor
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-gray-900 font-mono">
|
||||
#{execution.executor}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
{execution.workflow_task && (
|
||||
<div>
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide">
|
||||
Workflow Task
|
||||
</dt>
|
||||
<dd className="mt-0.5 text-sm text-gray-900">
|
||||
<span className="font-medium">
|
||||
{execution.workflow_task.task_name}
|
||||
</span>
|
||||
{execution.workflow_task.task_index != null && (
|
||||
<span className="text-gray-400 ml-1">
|
||||
[{execution.workflow_task.task_index}]
|
||||
</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Config / Parameters */}
|
||||
{execution.config &&
|
||||
Object.keys(execution.config).length > 0 && (
|
||||
<div className="px-4 py-3">
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-1.5">
|
||||
Parameters
|
||||
</dt>
|
||||
<dd>
|
||||
<pre className="bg-gray-50 border border-gray-200 rounded p-3 text-xs overflow-x-auto max-h-48 overflow-y-auto">
|
||||
{JSON.stringify(execution.config, null, 2)}
|
||||
</pre>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
{execution.result &&
|
||||
Object.keys(execution.result).length > 0 && (
|
||||
<div className="px-4 py-3">
|
||||
<dt className="text-xs font-medium text-gray-500 uppercase tracking-wide mb-1.5">
|
||||
Result
|
||||
</dt>
|
||||
<dd>
|
||||
<pre
|
||||
className={`border rounded p-3 text-xs overflow-x-auto max-h-64 overflow-y-auto ${
|
||||
execution.status === ("failed" as ExecutionStatus) ||
|
||||
execution.status === ("timeout" as ExecutionStatus)
|
||||
? "bg-red-50 border-red-200"
|
||||
: "bg-gray-50 border-gray-200"
|
||||
}`}
|
||||
>
|
||||
{JSON.stringify(execution.result, null, 2)}
|
||||
</pre>
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
{execution && (
|
||||
<div className="px-4 py-3 border-t border-gray-200 bg-gray-50 flex-shrink-0">
|
||||
<Link
|
||||
to={`/executions/${executionId}`}
|
||||
className="block w-full text-center px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 hover:bg-blue-100 rounded-md transition-colors"
|
||||
>
|
||||
Open Full Details
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default ExecutionPreviewPanel;
|
||||
78
web/src/components/executions/Pagination.tsx
Normal file
78
web/src/components/executions/Pagination.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { memo } from "react";
|
||||
|
||||
interface PaginationProps {
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
function computeRange(page: number, pageSize: number, total: number) {
|
||||
const start = (page - 1) * pageSize + 1;
|
||||
const end = Math.min(page * pageSize, total);
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
const Pagination = memo(function Pagination({
|
||||
page,
|
||||
setPage,
|
||||
pageSize,
|
||||
total,
|
||||
}: PaginationProps) {
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
if (totalPages <= 1) return null;
|
||||
|
||||
const { start, end } = computeRange(page, pageSize, total);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-50 px-6 py-4 flex items-center justify-between border-t border-gray-200">
|
||||
<div className="flex-1 flex justify-between sm:hidden">
|
||||
<button
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={page === 1}
|
||||
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-gray-700">
|
||||
Showing <span className="font-medium">{start}</span> to{" "}
|
||||
<span className="font-medium">{end}</span> of{" "}
|
||||
<span className="font-medium">{total}</span> executions
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
|
||||
<button
|
||||
onClick={() => setPage(page - 1)}
|
||||
disabled={page === 1}
|
||||
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPage(page + 1)}
|
||||
disabled={page === totalPages}
|
||||
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Pagination.displayName = "Pagination";
|
||||
|
||||
export default Pagination;
|
||||
618
web/src/components/executions/WorkflowExecutionTree.tsx
Normal file
618
web/src/components/executions/WorkflowExecutionTree.tsx
Normal file
@@ -0,0 +1,618 @@
|
||||
import { useState, useMemo, memo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
Workflow,
|
||||
Loader2,
|
||||
CheckCircle2,
|
||||
XCircle,
|
||||
Clock,
|
||||
AlertTriangle,
|
||||
Ban,
|
||||
CircleDot,
|
||||
RotateCcw,
|
||||
} from "lucide-react";
|
||||
import { useChildExecutions } from "@/hooks/useExecutions";
|
||||
import type { ExecutionSummary } from "@/api";
|
||||
import Pagination from "./Pagination";
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
function getStatusColor(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return "bg-green-100 text-green-800";
|
||||
case "failed":
|
||||
case "timeout":
|
||||
return "bg-red-100 text-red-800";
|
||||
case "running":
|
||||
return "bg-blue-100 text-blue-800";
|
||||
case "requested":
|
||||
case "scheduling":
|
||||
case "scheduled":
|
||||
return "bg-yellow-100 text-yellow-800";
|
||||
case "canceling":
|
||||
case "cancelled":
|
||||
return "bg-gray-100 text-gray-600";
|
||||
default:
|
||||
return "bg-gray-100 text-gray-800";
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusIcon(status: string) {
|
||||
switch (status) {
|
||||
case "completed":
|
||||
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
||||
case "failed":
|
||||
return <XCircle className="h-4 w-4 text-red-500" />;
|
||||
case "running":
|
||||
return <Loader2 className="h-4 w-4 text-blue-500 animate-spin" />;
|
||||
case "requested":
|
||||
case "scheduling":
|
||||
case "scheduled":
|
||||
return <Clock className="h-4 w-4 text-yellow-500" />;
|
||||
case "timeout":
|
||||
return <AlertTriangle className="h-4 w-4 text-orange-500" />;
|
||||
case "canceling":
|
||||
case "cancelled":
|
||||
return <Ban className="h-4 w-4 text-gray-400" />;
|
||||
case "abandoned":
|
||||
return <AlertTriangle className="h-4 w-4 text-red-400" />;
|
||||
default:
|
||||
return <CircleDot className="h-4 w-4 text-gray-400" />;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
const secs = ms / 1000;
|
||||
if (secs < 60) return `${secs.toFixed(1)}s`;
|
||||
const mins = Math.floor(secs / 60);
|
||||
const remainSecs = Math.round(secs % 60);
|
||||
if (mins < 60) return `${mins}m ${remainSecs}s`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
const remainMins = mins % 60;
|
||||
return `${hrs}h ${remainMins}m`;
|
||||
}
|
||||
|
||||
// ─── Child execution row (recursive) ────────────────────────────────────────
|
||||
|
||||
interface ChildExecutionRowProps {
|
||||
execution: ExecutionSummary;
|
||||
depth: number;
|
||||
selectedExecutionId: number | null;
|
||||
onSelectExecution: (id: number) => void;
|
||||
workflowActionRefs: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single child-execution row inside the accordion. If it has its own
|
||||
* children (nested workflow), it can be expanded recursively.
|
||||
*/
|
||||
const ChildExecutionRow = memo(function ChildExecutionRow({
|
||||
execution,
|
||||
depth,
|
||||
selectedExecutionId,
|
||||
onSelectExecution,
|
||||
workflowActionRefs,
|
||||
}: ChildExecutionRowProps) {
|
||||
const isWorkflow = workflowActionRefs.has(execution.action_ref);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
// Only fetch children when expanded and this is a workflow action
|
||||
const { data, isLoading } = useChildExecutions(
|
||||
expanded && isWorkflow ? execution.id : undefined,
|
||||
);
|
||||
|
||||
const children = useMemo(() => data?.data ?? [], [data]);
|
||||
const hasChildren = expanded && children.length > 0;
|
||||
|
||||
const wt = execution.workflow_task;
|
||||
const taskName = wt?.task_name;
|
||||
const retryCount = wt?.retry_count ?? 0;
|
||||
const maxRetries = wt?.max_retries ?? 0;
|
||||
|
||||
const created = new Date(execution.created);
|
||||
const updated = new Date(execution.updated);
|
||||
const durationMs =
|
||||
wt?.duration_ms ??
|
||||
(execution.status === "completed" ||
|
||||
execution.status === "failed" ||
|
||||
execution.status === "timeout"
|
||||
? updated.getTime() - created.getTime()
|
||||
: null);
|
||||
|
||||
const indent = 16 + depth * 24;
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
className={`hover:bg-gray-50/80 group border-t border-gray-100 cursor-pointer ${
|
||||
selectedExecutionId === execution.id
|
||||
? "bg-blue-50 hover:bg-blue-50"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => onSelectExecution(execution.id)}
|
||||
>
|
||||
{/* Task name / expand toggle */}
|
||||
<td className="py-3 pr-2" style={{ paddingLeft: indent }}>
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
{isWorkflow && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setExpanded((prev) => !prev);
|
||||
}}
|
||||
className={`flex-shrink-0 p-0.5 rounded hover:bg-gray-200 transition-colors ${
|
||||
expanded || isLoading
|
||||
? "visible"
|
||||
: "invisible group-hover:visible"
|
||||
}`}
|
||||
title={expanded ? "Collapse" : "Expand"}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="h-3.5 w-3.5 text-gray-400 animate-spin" />
|
||||
) : expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-gray-400" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-gray-400" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{getStatusIcon(execution.status)}
|
||||
|
||||
{taskName && (
|
||||
<span
|
||||
className="text-sm font-medium text-gray-700 truncate"
|
||||
title={taskName}
|
||||
>
|
||||
{taskName}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{wt?.task_index != null && (
|
||||
<span className="text-xs text-gray-400 flex-shrink-0">
|
||||
[{wt.task_index}]
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{/* Exec ID */}
|
||||
<td className="px-4 py-3 font-mono text-xs">
|
||||
<Link
|
||||
to={`/executions/${execution.id}`}
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
#{execution.id}
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
{/* Action */}
|
||||
<td className="px-4 py-3">
|
||||
<Link
|
||||
to={`/executions/${execution.id}`}
|
||||
className="text-sm text-blue-600 hover:text-blue-800 hover:underline truncate block"
|
||||
title={execution.action_ref}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{execution.action_ref}
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
{/* Status */}
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`px-2 py-0.5 text-xs rounded-full font-medium ${getStatusColor(execution.status)}`}
|
||||
>
|
||||
{execution.status}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Duration */}
|
||||
<td className="px-4 py-3 text-sm text-gray-500">
|
||||
{execution.status === "running" ? (
|
||||
<span className="text-blue-600 flex items-center gap-1">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
running
|
||||
</span>
|
||||
) : durationMs != null && durationMs > 0 ? (
|
||||
formatDuration(durationMs)
|
||||
) : (
|
||||
<span className="text-gray-300">—</span>
|
||||
)}
|
||||
</td>
|
||||
|
||||
{/* Retry */}
|
||||
<td className="px-4 py-3 text-sm text-gray-500">
|
||||
{maxRetries > 0 ? (
|
||||
<span
|
||||
className="inline-flex items-center gap-0.5"
|
||||
title={`Attempt ${retryCount + 1} of ${maxRetries + 1}`}
|
||||
>
|
||||
<RotateCcw className="h-3 w-3" />
|
||||
{retryCount}/{maxRetries}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-gray-300">—</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Nested children */}
|
||||
{expanded &&
|
||||
!isLoading &&
|
||||
hasChildren &&
|
||||
children.map((child: ExecutionSummary) => (
|
||||
<ChildExecutionRow
|
||||
key={child.id}
|
||||
execution={child}
|
||||
depth={depth + 1}
|
||||
selectedExecutionId={selectedExecutionId}
|
||||
onSelectExecution={onSelectExecution}
|
||||
workflowActionRefs={workflowActionRefs}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Top-level workflow row (accordion) ─────────────────────────────────────
|
||||
|
||||
interface WorkflowExecutionRowProps {
|
||||
execution: ExecutionSummary;
|
||||
workflowActionRefs: Set<string>;
|
||||
selectedExecutionId: number | null;
|
||||
onSelectExecution: (id: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A top-level execution row with an expandable accordion for child tasks.
|
||||
*/
|
||||
const WorkflowExecutionRow = memo(function WorkflowExecutionRow({
|
||||
execution,
|
||||
workflowActionRefs,
|
||||
selectedExecutionId,
|
||||
onSelectExecution,
|
||||
}: WorkflowExecutionRowProps) {
|
||||
const isWorkflow = workflowActionRefs.has(execution.action_ref);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
const { data, isLoading } = useChildExecutions(
|
||||
expanded && isWorkflow ? execution.id : undefined,
|
||||
);
|
||||
|
||||
const children = useMemo(() => data?.data ?? [], [data]);
|
||||
|
||||
const summary = useMemo(() => {
|
||||
const total = children.length;
|
||||
const completed = children.filter(
|
||||
(t: ExecutionSummary) => t.status === "completed",
|
||||
).length;
|
||||
const failed = children.filter(
|
||||
(t: ExecutionSummary) => t.status === "failed" || t.status === "timeout",
|
||||
).length;
|
||||
const running = children.filter(
|
||||
(t: ExecutionSummary) =>
|
||||
t.status === "running" ||
|
||||
t.status === "requested" ||
|
||||
t.status === "scheduling" ||
|
||||
t.status === "scheduled",
|
||||
).length;
|
||||
return { total, completed, failed, running };
|
||||
}, [children]);
|
||||
|
||||
const hasWorkflowChildren = expanded && children.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Main execution row */}
|
||||
<tr
|
||||
className={`hover:bg-gray-50 border-b border-gray-200 cursor-pointer ${
|
||||
selectedExecutionId === execution.id
|
||||
? "bg-blue-50 hover:bg-blue-50"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => onSelectExecution(execution.id)}
|
||||
>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{isWorkflow && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setExpanded((prev) => !prev);
|
||||
}}
|
||||
className="flex-shrink-0 p-0.5 rounded hover:bg-gray-200 transition-colors"
|
||||
title={
|
||||
expanded ? "Collapse workflow tasks" : "Expand workflow tasks"
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 className="h-4 w-4 text-gray-400 animate-spin" />
|
||||
) : expanded ? (
|
||||
<ChevronDown className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-gray-500" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<Link
|
||||
to={`/executions/${execution.id}`}
|
||||
className="text-blue-600 hover:text-blue-800 font-mono text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
#{execution.id}
|
||||
</Link>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span className="text-sm text-gray-900">{execution.action_ref}</span>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{execution.rule_ref ? (
|
||||
<span className="text-sm text-gray-700">{execution.rule_ref}</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400 italic">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{execution.trigger_ref ? (
|
||||
<span className="text-sm text-gray-700">
|
||||
{execution.trigger_ref}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-400 italic">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<span
|
||||
className={`px-2 py-1 text-xs rounded ${getStatusColor(execution.status)}`}
|
||||
>
|
||||
{execution.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-500">
|
||||
{new Date(execution.created).toLocaleString()}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Expanded child-task section */}
|
||||
{expanded && (
|
||||
<tr>
|
||||
<td colSpan={6} className="p-0">
|
||||
<div className="bg-gray-50 border-b border-gray-200">
|
||||
{/* Summary bar */}
|
||||
{hasWorkflowChildren && (
|
||||
<div className="flex items-center gap-3 px-8 py-2 border-b border-gray-200 bg-gray-100/60">
|
||||
<Workflow className="h-4 w-4 text-indigo-500" />
|
||||
<span className="text-xs font-medium text-gray-600">
|
||||
{summary.total} task{summary.total !== 1 ? "s" : ""}
|
||||
</span>
|
||||
{summary.completed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
{summary.completed}
|
||||
</span>
|
||||
)}
|
||||
{summary.running > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
{summary.running}
|
||||
</span>
|
||||
)}
|
||||
{summary.failed > 0 && (
|
||||
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700">
|
||||
<XCircle className="h-3 w-3" />
|
||||
{summary.failed}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loading state */}
|
||||
{isLoading && (
|
||||
<div className="flex items-center gap-2 px-8 py-4">
|
||||
<Loader2 className="h-4 w-4 animate-spin text-gray-400" />
|
||||
<span className="text-sm text-gray-500">
|
||||
Loading workflow tasks...
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No children yet (workflow still starting) */}
|
||||
{!isLoading && children.length === 0 && (
|
||||
<div className="px-8 py-3 text-sm text-gray-400 italic">
|
||||
No child tasks yet.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Children table */}
|
||||
{hasWorkflowChildren && (
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
<th
|
||||
className="py-2 pr-2 text-left"
|
||||
style={{ paddingLeft: 40 }}
|
||||
>
|
||||
Task
|
||||
</th>
|
||||
<th className="px-4 py-2 text-left">ID</th>
|
||||
<th className="px-4 py-2 text-left">Action</th>
|
||||
<th className="px-4 py-2 text-left">Status</th>
|
||||
<th className="px-4 py-2 text-left">Duration</th>
|
||||
<th className="px-4 py-2 text-left">Retry</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{children.map((child: ExecutionSummary) => (
|
||||
<ChildExecutionRow
|
||||
key={child.id}
|
||||
execution={child}
|
||||
depth={0}
|
||||
selectedExecutionId={selectedExecutionId}
|
||||
onSelectExecution={onSelectExecution}
|
||||
workflowActionRefs={workflowActionRefs}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
// ─── Main tree table ────────────────────────────────────────────────────────
|
||||
|
||||
interface WorkflowExecutionTreeProps {
|
||||
executions: ExecutionSummary[];
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
error: Error | null;
|
||||
hasActiveFilters: boolean;
|
||||
clearFilters: () => void;
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
workflowActionRefs: Set<string>;
|
||||
selectedExecutionId: number | null;
|
||||
onSelectExecution: (id: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the executions list in "By Workflow" mode. Top-level executions
|
||||
* are shown with the same columns as the "All" view, but each row is
|
||||
* expandable to reveal the workflow's child task executions in an accordion.
|
||||
* Nested workflows can be drilled into recursively.
|
||||
*/
|
||||
const WorkflowExecutionTree = memo(function WorkflowExecutionTree({
|
||||
executions,
|
||||
isLoading,
|
||||
isFetching,
|
||||
error,
|
||||
hasActiveFilters,
|
||||
clearFilters,
|
||||
page,
|
||||
setPage,
|
||||
pageSize,
|
||||
total,
|
||||
workflowActionRefs,
|
||||
selectedExecutionId,
|
||||
onSelectExecution,
|
||||
}: WorkflowExecutionTreeProps) {
|
||||
// Initial load
|
||||
if (isLoading && executions.length === 0) {
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error with no cached data
|
||||
if (error && executions.length === 0) {
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
||||
<p>Error: {error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Empty
|
||||
if (executions.length === 0) {
|
||||
return (
|
||||
<div className="bg-white p-12 text-center rounded-lg shadow">
|
||||
<p>No executions found</p>
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
className="mt-3 text-sm text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Clear filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* Loading overlay */}
|
||||
{isFetching && (
|
||||
<div className="absolute inset-0 bg-white/60 z-10 flex items-center justify-center rounded-lg">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Non-fatal error banner */}
|
||||
{error && (
|
||||
<div className="mb-4 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
||||
<p>Error refreshing: {error.message}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="bg-white shadow rounded-lg overflow-hidden">
|
||||
<table className="min-w-full">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
ID
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Action
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Rule
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Trigger
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Status
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
Created
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white">
|
||||
{executions.map((exec: ExecutionSummary) => (
|
||||
<WorkflowExecutionRow
|
||||
key={exec.id}
|
||||
execution={exec}
|
||||
workflowActionRefs={workflowActionRefs}
|
||||
selectedExecutionId={selectedExecutionId}
|
||||
onSelectExecution={onSelectExecution}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
WorkflowExecutionTree.displayName = "WorkflowExecutionTree";
|
||||
|
||||
export default WorkflowExecutionTree;
|
||||
@@ -90,12 +90,6 @@ export function useEnforcementStream(
|
||||
// Extract enforcement data from notification payload (flat structure)
|
||||
const enforcementData = notification.payload as any;
|
||||
|
||||
// Invalidate history queries so the EntityHistoryPanel picks up new records
|
||||
// (e.g. status changes recorded by the enforcement_history trigger)
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["history", "enforcement", notification.entity_id],
|
||||
});
|
||||
|
||||
// Update specific enforcement query if it exists
|
||||
queryClient.setQueryData(
|
||||
["enforcements", notification.entity_id],
|
||||
|
||||
@@ -48,6 +48,22 @@ function stripNotificationMeta(payload: any): any {
|
||||
function executionMatchesParams(execution: any, params: any): boolean {
|
||||
if (!params) return true;
|
||||
|
||||
// Check topLevelOnly filter — child executions (with a parent) must not
|
||||
// appear in top-level list queries.
|
||||
if (params.topLevelOnly && execution.parent != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check parent filter — child execution queries (keyed by { parent: id })
|
||||
// should only receive notifications for executions belonging to that parent.
|
||||
// Without this, every execution notification would match child queries since
|
||||
// they have no other filter fields.
|
||||
if (params.parent !== undefined) {
|
||||
if (execution.parent !== params.parent) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check status filter (from API query parameters)
|
||||
if (params.status && execution.status !== params.status) {
|
||||
return false;
|
||||
|
||||
@@ -11,6 +11,7 @@ interface ExecutionsQueryParams {
|
||||
ruleRef?: string;
|
||||
triggerRef?: string;
|
||||
executor?: number;
|
||||
topLevelOnly?: boolean;
|
||||
}
|
||||
|
||||
export function useExecutions(params?: ExecutionsQueryParams) {
|
||||
@@ -21,7 +22,8 @@ export function useExecutions(params?: ExecutionsQueryParams) {
|
||||
params?.packName ||
|
||||
params?.ruleRef ||
|
||||
params?.triggerRef ||
|
||||
params?.executor;
|
||||
params?.executor ||
|
||||
params?.topLevelOnly;
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["executions", params],
|
||||
@@ -35,6 +37,7 @@ export function useExecutions(params?: ExecutionsQueryParams) {
|
||||
ruleRef: params?.ruleRef,
|
||||
triggerRef: params?.triggerRef,
|
||||
executor: params?.executor,
|
||||
topLevelOnly: params?.topLevelOnly,
|
||||
});
|
||||
return response;
|
||||
},
|
||||
@@ -59,3 +62,37 @@ export function useExecution(id: number) {
|
||||
staleTime: 30000, // 30 seconds - SSE handles real-time updates
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch child executions (workflow tasks) for a given parent execution ID.
|
||||
*
|
||||
* Enabled only when `parentId` is provided. Polls every 5 seconds while any
|
||||
* child execution is still in a running/pending state so the UI stays current.
|
||||
*/
|
||||
export function useChildExecutions(parentId: number | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["executions", { parent: parentId }],
|
||||
queryFn: async () => {
|
||||
const response = await ExecutionsService.listExecutions({
|
||||
parent: parentId,
|
||||
perPage: 100,
|
||||
});
|
||||
return response;
|
||||
},
|
||||
enabled: !!parentId,
|
||||
staleTime: 5000,
|
||||
// Re-fetch periodically so in-progress tasks update
|
||||
refetchInterval: (query) => {
|
||||
const data = query.state.data;
|
||||
if (!data) return false;
|
||||
const hasActive = data.data.some(
|
||||
(e) =>
|
||||
e.status === "requested" ||
|
||||
e.status === "scheduling" ||
|
||||
e.status === "scheduled" ||
|
||||
e.status === "running",
|
||||
);
|
||||
return hasActive ? 5000 : false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,12 +61,20 @@ export function useFilterSuggestions() {
|
||||
return [...new Set(refs)].sort();
|
||||
}, [actionsData]);
|
||||
|
||||
const workflowActionRefs = useMemo(() => {
|
||||
const refs =
|
||||
actionsData?.data
|
||||
?.filter((a) => a.workflow_def != null)
|
||||
.map((a) => a.ref) || [];
|
||||
return new Set(refs);
|
||||
}, [actionsData]);
|
||||
|
||||
const triggerRefs = useMemo(() => {
|
||||
const refs = triggersData?.data?.map((t) => t.ref) || [];
|
||||
return [...new Set(refs)].sort();
|
||||
}, [triggersData]);
|
||||
|
||||
return { packNames, ruleRefs, actionRefs, triggerRefs };
|
||||
return { packNames, ruleRefs, actionRefs, triggerRefs, workflowActionRefs };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,11 +5,7 @@ import { apiClient } from "@/lib/api-client";
|
||||
* Supported entity types for history queries.
|
||||
* Maps to the TimescaleDB history hypertables.
|
||||
*/
|
||||
export type HistoryEntityType =
|
||||
| "execution"
|
||||
| "worker"
|
||||
| "enforcement"
|
||||
| "event";
|
||||
export type HistoryEntityType = "execution" | "worker";
|
||||
|
||||
/**
|
||||
* A single history record from the API.
|
||||
@@ -68,8 +64,6 @@ export interface HistoryQueryParams {
|
||||
* Uses the entity-specific endpoints:
|
||||
* - GET /api/v1/executions/:id/history
|
||||
* - GET /api/v1/workers/:id/history
|
||||
* - GET /api/v1/enforcements/:id/history
|
||||
* - GET /api/v1/events/:id/history
|
||||
*/
|
||||
async function fetchEntityHistory(
|
||||
entityType: HistoryEntityType,
|
||||
@@ -79,8 +73,6 @@ async function fetchEntityHistory(
|
||||
const pluralMap: Record<HistoryEntityType, string> = {
|
||||
execution: "executions",
|
||||
worker: "workers",
|
||||
enforcement: "enforcements",
|
||||
event: "events",
|
||||
};
|
||||
|
||||
const queryParams: Record<string, string | number> = {};
|
||||
@@ -143,23 +135,3 @@ export function useWorkerHistory(
|
||||
) {
|
||||
return useEntityHistory("worker", workerId, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience hook for enforcement history.
|
||||
*/
|
||||
export function useEnforcementHistory(
|
||||
enforcementId: number,
|
||||
params: HistoryQueryParams = {},
|
||||
) {
|
||||
return useEntityHistory("enforcement", enforcementId, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience hook for event history.
|
||||
*/
|
||||
export function useEventHistory(
|
||||
eventId: number,
|
||||
params: HistoryQueryParams = {},
|
||||
) {
|
||||
return useEntityHistory("event", eventId, params);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,16 @@ import { Link, useParams, useNavigate } from "react-router-dom";
|
||||
import { useActions, useAction, useDeleteAction } from "@/hooks/useActions";
|
||||
import { useExecutions } from "@/hooks/useExecutions";
|
||||
import { useState, useMemo } from "react";
|
||||
import { ChevronDown, ChevronRight, Search, X, Play, Plus } from "lucide-react";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Search,
|
||||
X,
|
||||
Play,
|
||||
Plus,
|
||||
GitBranch,
|
||||
Pencil,
|
||||
} from "lucide-react";
|
||||
import ExecuteActionModal from "@/components/common/ExecuteActionModal";
|
||||
import ErrorDisplay from "@/components/common/ErrorDisplay";
|
||||
import { extractProperties } from "@/components/common/ParamSchemaForm";
|
||||
@@ -177,7 +186,12 @@ export default function ActionsPage() {
|
||||
: "border-2 border-transparent hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-sm text-gray-900 truncate">
|
||||
<div className="font-medium text-sm text-gray-900 truncate flex items-center gap-1.5">
|
||||
{action.workflow_def && (
|
||||
<span title="Workflow">
|
||||
<GitBranch className="w-3.5 h-3.5 text-purple-500 flex-shrink-0" />
|
||||
</span>
|
||||
)}
|
||||
{action.label}
|
||||
</div>
|
||||
<div className="font-mono text-xs text-gray-500 mt-1 truncate">
|
||||
@@ -236,6 +250,7 @@ export default function ActionsPage() {
|
||||
}
|
||||
|
||||
function ActionDetail({ actionRef }: { actionRef: string }) {
|
||||
const navigate = useNavigate();
|
||||
const { data: action, isLoading, error } = useAction(actionRef);
|
||||
const { data: executionsData } = useExecutions({
|
||||
actionRef: actionRef,
|
||||
@@ -290,6 +305,17 @@ function ActionDetail({ actionRef }: { actionRef: string }) {
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{action.data?.workflow_def && (
|
||||
<button
|
||||
onClick={() =>
|
||||
navigate(`/actions/workflows/${action.data!.ref}/edit`)
|
||||
}
|
||||
className="px-4 py-2 bg-purple-600 text-white rounded hover:bg-purple-700 flex items-center gap-2"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
Edit Workflow
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowExecuteModal(true)}
|
||||
className="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 flex items-center gap-2"
|
||||
|
||||
@@ -457,7 +457,7 @@ export default function WorkflowBuilderPage() {
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await saveWorkflowFile.mutateAsync({
|
||||
const fileData = {
|
||||
name: state.name,
|
||||
label: state.label,
|
||||
description: state.description || undefined,
|
||||
@@ -472,7 +472,30 @@ export default function WorkflowBuilderPage() {
|
||||
Object.keys(state.output).length > 0 ? state.output : undefined,
|
||||
tags: state.tags.length > 0 ? state.tags : undefined,
|
||||
enabled: state.enabled,
|
||||
});
|
||||
};
|
||||
try {
|
||||
await saveWorkflowFile.mutateAsync(fileData);
|
||||
} catch (createErr: unknown) {
|
||||
const apiErr = createErr as { status?: number };
|
||||
if (apiErr?.status === 409) {
|
||||
// Workflow already exists — fall back to update
|
||||
const workflowRef = `${state.packRef}.${state.name}`;
|
||||
await updateWorkflowFile.mutateAsync({
|
||||
workflowRef,
|
||||
data: fileData,
|
||||
});
|
||||
} else {
|
||||
throw createErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After a successful first save, navigate to the edit URL so the
|
||||
// page transitions into edit mode (locks ref, uses update on next save).
|
||||
if (!isEditing) {
|
||||
const newRef = `${state.packRef}.${state.name}`;
|
||||
navigate(`/actions/workflows/${newRef}/edit`, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
setSaveSuccess(true);
|
||||
@@ -490,6 +513,7 @@ export default function WorkflowBuilderPage() {
|
||||
saveWorkflowFile,
|
||||
updateWorkflowFile,
|
||||
actionSchemaMap,
|
||||
navigate,
|
||||
]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
@@ -540,9 +564,11 @@ export default function WorkflowBuilderPage() {
|
||||
{/* Left section: Back + metadata */}
|
||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||
<button
|
||||
onClick={() => navigate("/actions")}
|
||||
onClick={() =>
|
||||
navigate(isEditing ? `/actions/${editRef}` : "/actions")
|
||||
}
|
||||
className="p-1.5 rounded hover:bg-gray-100 text-gray-500 hover:text-gray-700 transition-colors flex-shrink-0"
|
||||
title="Back to Actions"
|
||||
title={isEditing ? "Back to Workflow" : "Back to Actions"}
|
||||
>
|
||||
<ArrowLeft className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -558,6 +584,7 @@ export default function WorkflowBuilderPage() {
|
||||
}))}
|
||||
placeholder="Pack..."
|
||||
className="max-w-[140px]"
|
||||
disabled={isEditing}
|
||||
/>
|
||||
|
||||
<span className="text-gray-400 text-lg font-light">/</span>
|
||||
@@ -571,8 +598,9 @@ export default function WorkflowBuilderPage() {
|
||||
name: e.target.value.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
||||
})
|
||||
}
|
||||
className="px-2 py-1.5 border border-gray-300 rounded text-sm font-mono focus:ring-2 focus:ring-blue-500 focus:border-blue-500 w-48"
|
||||
className={`px-2 py-1.5 border border-gray-300 rounded text-sm font-mono w-48 ${isEditing ? "bg-gray-100 cursor-not-allowed text-gray-500" : "focus:ring-2 focus:ring-blue-500 focus:border-blue-500"}`}
|
||||
placeholder="workflow_name"
|
||||
disabled={isEditing}
|
||||
/>
|
||||
|
||||
<span className="text-gray-400 text-lg font-light">—</span>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { useEnforcement } from "@/hooks/useEvents";
|
||||
import { EnforcementStatus, EnforcementCondition } from "@/api";
|
||||
import EntityHistoryPanel from "@/components/common/EntityHistoryPanel";
|
||||
|
||||
export default function EnforcementDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -189,6 +188,18 @@ export default function EnforcementDetailPage() {
|
||||
{formatDate(enforcement.created)}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">
|
||||
Resolved At
|
||||
</dt>
|
||||
<dd className="mt-1 text-gray-900">
|
||||
{enforcement.resolved_at ? (
|
||||
formatDate(enforcement.resolved_at)
|
||||
) : (
|
||||
<span className="text-gray-500">Pending</span>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -331,6 +342,14 @@ export default function EnforcementDetailPage() {
|
||||
{formatDate(enforcement.created)}
|
||||
</dd>
|
||||
</div>
|
||||
{enforcement.resolved_at && (
|
||||
<div>
|
||||
<dt className="text-gray-500">Resolved</dt>
|
||||
<dd className="text-gray-900">
|
||||
{formatDate(enforcement.resolved_at)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
@@ -377,15 +396,6 @@ export default function EnforcementDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Change History */}
|
||||
<div className="mt-6">
|
||||
<EntityHistoryPanel
|
||||
entityType="enforcement"
|
||||
entityId={enforcement.id}
|
||||
title="Enforcement History"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { useEvent } from "@/hooks/useEvents";
|
||||
import EntityHistoryPanel from "@/components/common/EntityHistoryPanel";
|
||||
|
||||
export default function EventDetailPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
@@ -259,15 +258,6 @@ export default function EventDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Change History */}
|
||||
<div className="mt-6">
|
||||
<EntityHistoryPanel
|
||||
entityType="event"
|
||||
entityId={event.id}
|
||||
title="Event History"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import { useState, useMemo } from "react";
|
||||
import { RotateCcw, Loader2 } from "lucide-react";
|
||||
import ExecuteActionModal from "@/components/common/ExecuteActionModal";
|
||||
import EntityHistoryPanel from "@/components/common/EntityHistoryPanel";
|
||||
import WorkflowTasksPanel from "@/components/common/WorkflowTasksPanel";
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
@@ -116,6 +117,9 @@ export default function ExecutionDetailPage() {
|
||||
// Fetch the action so we can get param_schema for the re-run modal
|
||||
const { data: actionData } = useAction(execution?.action_ref || "");
|
||||
|
||||
// Determine if this execution is a workflow (action has workflow_def)
|
||||
const isWorkflow = !!actionData?.data?.workflow_def;
|
||||
|
||||
const [showRerunModal, setShowRerunModal] = useState(false);
|
||||
|
||||
// Fetch status history for the timeline
|
||||
@@ -207,6 +211,11 @@ export default function ExecutionDetailPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-3xl font-bold">Execution #{execution.id}</h1>
|
||||
{isWorkflow && (
|
||||
<span className="px-3 py-1 text-sm rounded-full bg-indigo-100 text-indigo-800">
|
||||
Workflow
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={`px-3 py-1 text-sm rounded-full ${getStatusColor(execution.status)}`}
|
||||
>
|
||||
@@ -247,6 +256,25 @@ export default function ExecutionDetailPage() {
|
||||
{execution.action_ref}
|
||||
</Link>
|
||||
</p>
|
||||
{execution.workflow_task && (
|
||||
<p className="text-sm text-indigo-600 mt-1 flex items-center gap-1.5">
|
||||
<span className="text-gray-500">Task</span>{" "}
|
||||
<span className="font-medium">
|
||||
{execution.workflow_task.task_name}
|
||||
</span>
|
||||
{execution.parent && (
|
||||
<>
|
||||
<span className="text-gray-500">in workflow</span>
|
||||
<Link
|
||||
to={`/executions/${execution.parent}`}
|
||||
className="text-indigo-600 hover:text-indigo-800 font-medium"
|
||||
>
|
||||
Execution #{execution.parent}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Re-Run Modal */}
|
||||
@@ -504,6 +532,13 @@ export default function ExecutionDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Workflow Tasks (shown only for workflow executions) */}
|
||||
{isWorkflow && (
|
||||
<div className="mt-6">
|
||||
<WorkflowTasksPanel parentExecutionId={execution.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Change History */}
|
||||
<div className="mt-6">
|
||||
<EntityHistoryPanel
|
||||
|
||||
@@ -3,13 +3,19 @@ import { useExecutions } from "@/hooks/useExecutions";
|
||||
import { useExecutionStream } from "@/hooks/useExecutionStream";
|
||||
import { ExecutionStatus } from "@/api";
|
||||
import { useState, useMemo, memo, useCallback, useEffect } from "react";
|
||||
import { Search, X } from "lucide-react";
|
||||
import { Search, X, List, GitBranch } from "lucide-react";
|
||||
import MultiSelect from "@/components/common/MultiSelect";
|
||||
import AutocompleteInput from "@/components/common/AutocompleteInput";
|
||||
import {
|
||||
useFilterSuggestions,
|
||||
useMergedSuggestions,
|
||||
} from "@/hooks/useFilterSuggestions";
|
||||
import WorkflowExecutionTree from "@/components/executions/WorkflowExecutionTree";
|
||||
import ExecutionPreviewPanel from "@/components/executions/ExecutionPreviewPanel";
|
||||
|
||||
type ViewMode = "all" | "workflow";
|
||||
|
||||
const VIEW_MODE_STORAGE_KEY = "attune:executions:viewMode";
|
||||
|
||||
// Memoized filter input component for non-ref fields (e.g. Executor ID)
|
||||
const FilterInput = memo(
|
||||
@@ -87,6 +93,8 @@ const ExecutionsResultsTable = memo(
|
||||
setPage,
|
||||
pageSize,
|
||||
total,
|
||||
selectedExecutionId,
|
||||
onSelectExecution,
|
||||
}: {
|
||||
executions: any[];
|
||||
isLoading: boolean;
|
||||
@@ -98,6 +106,8 @@ const ExecutionsResultsTable = memo(
|
||||
setPage: (page: number) => void;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
selectedExecutionId: number | null;
|
||||
onSelectExecution: (id: number) => void;
|
||||
}) => {
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
@@ -182,11 +192,20 @@ const ExecutionsResultsTable = memo(
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{executions.map((exec: any) => (
|
||||
<tr key={exec.id} className="hover:bg-gray-50">
|
||||
<tr
|
||||
key={exec.id}
|
||||
className={`hover:bg-gray-50 cursor-pointer ${
|
||||
selectedExecutionId === exec.id
|
||||
? "bg-blue-50 hover:bg-blue-50"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => onSelectExecution(exec.id)}
|
||||
>
|
||||
<td className="px-6 py-4 font-mono text-sm">
|
||||
<Link
|
||||
to={`/executions/${exec.id}`}
|
||||
className="text-blue-600 hover:text-blue-800"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
#{exec.id}
|
||||
</Link>
|
||||
@@ -294,6 +313,15 @@ ExecutionsResultsTable.displayName = "ExecutionsResultsTable";
|
||||
export default function ExecutionsPage() {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// --- View mode toggle ---
|
||||
const [viewMode, setViewMode] = useState<ViewMode>(() => {
|
||||
const stored = localStorage.getItem(VIEW_MODE_STORAGE_KEY);
|
||||
if (stored === "all" || stored === "workflow") return stored;
|
||||
const param = searchParams.get("view");
|
||||
if (param === "all" || param === "workflow") return param;
|
||||
return "all";
|
||||
});
|
||||
|
||||
// --- Filter input state (updates immediately on keystroke) ---
|
||||
const [page, setPage] = useState(1);
|
||||
const pageSize = 50;
|
||||
@@ -342,8 +370,11 @@ export default function ExecutionsPage() {
|
||||
if (debouncedStatuses.length === 1) {
|
||||
params.status = debouncedStatuses[0] as ExecutionStatus;
|
||||
}
|
||||
if (viewMode === "workflow") {
|
||||
params.topLevelOnly = true;
|
||||
}
|
||||
return params;
|
||||
}, [page, pageSize, debouncedFilters, debouncedStatuses]);
|
||||
}, [page, pageSize, debouncedFilters, debouncedStatuses, viewMode]);
|
||||
|
||||
const { data, isLoading, isFetching, error } = useExecutions(queryParams);
|
||||
const { isConnected } = useExecutionStream({ enabled: true });
|
||||
@@ -423,103 +454,181 @@ export default function ExecutionsPage() {
|
||||
Object.values(searchFilters).some((v) => v !== "") ||
|
||||
selectedStatuses.length > 0;
|
||||
|
||||
const [selectedExecutionId, setSelectedExecutionId] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const handleSelectExecution = useCallback((id: number) => {
|
||||
setSelectedExecutionId((prev) => (prev === id ? null : id));
|
||||
}, []);
|
||||
|
||||
const handleClosePreview = useCallback(() => {
|
||||
setSelectedExecutionId(null);
|
||||
}, []);
|
||||
|
||||
const handleViewModeChange = useCallback((mode: ViewMode) => {
|
||||
setViewMode(mode);
|
||||
localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode);
|
||||
setPage(1);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
{/* Header - always visible */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Executions</h1>
|
||||
{isFetching && hasActiveFilters && (
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
Searching executions...
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isConnected && (
|
||||
<div className="flex items-center gap-2 text-sm text-green-600">
|
||||
<div className="h-2 w-2 rounded-full bg-green-600 animate-pulse" />
|
||||
<span>Live Updates</span>
|
||||
<div className="flex h-[calc(100vh-4rem)]">
|
||||
{/* Main content area */}
|
||||
<div
|
||||
className={`flex-1 min-w-0 overflow-y-auto p-6 ${selectedExecutionId ? "mr-0" : ""}`}
|
||||
>
|
||||
{/* Header - always visible */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-3xl font-bold">Executions</h1>
|
||||
{isConnected && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-green-600 bg-green-50 border border-green-200 rounded-full px-2.5 py-1">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-green-500 animate-pulse" />
|
||||
<span>Live</span>
|
||||
</div>
|
||||
)}
|
||||
{isFetching && hasActiveFilters && (
|
||||
<p className="text-sm text-gray-500">Searching executions...</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* View mode toggle */}
|
||||
<div className="inline-flex rounded-lg border border-gray-300 bg-white shadow-sm">
|
||||
<button
|
||||
onClick={() => handleViewModeChange("all")}
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-l-lg transition-colors ${
|
||||
viewMode === "all"
|
||||
? "bg-blue-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<List className="h-4 w-4" />
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleViewModeChange("workflow")}
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-r-lg transition-colors ${
|
||||
viewMode === "workflow"
|
||||
? "bg-blue-600 text-white"
|
||||
: "text-gray-600 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<GitBranch className="h-4 w-4" />
|
||||
By Workflow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter section - always mounted, never unmounts during loading */}
|
||||
<div className="bg-white shadow rounded-lg p-4 mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-5 w-5 text-gray-400" />
|
||||
<h2 className="text-lg font-semibold">Filter Executions</h2>
|
||||
</div>
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4">
|
||||
<AutocompleteInput
|
||||
label="Pack"
|
||||
value={searchFilters.pack}
|
||||
onChange={(value) => handleFilterChange("pack", value)}
|
||||
suggestions={packSuggestions}
|
||||
placeholder="e.g., core"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Rule"
|
||||
value={searchFilters.rule}
|
||||
onChange={(value) => handleFilterChange("rule", value)}
|
||||
suggestions={ruleSuggestions}
|
||||
placeholder="e.g., core.on_timer"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Action"
|
||||
value={searchFilters.action}
|
||||
onChange={(value) => handleFilterChange("action", value)}
|
||||
suggestions={actionSuggestions}
|
||||
placeholder="e.g., core.echo"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Trigger"
|
||||
value={searchFilters.trigger}
|
||||
onChange={(value) => handleFilterChange("trigger", value)}
|
||||
suggestions={triggerSuggestions}
|
||||
placeholder="e.g., core.timer"
|
||||
/>
|
||||
<FilterInput
|
||||
label="Executor ID"
|
||||
value={searchFilters.executor}
|
||||
onChange={(value) => handleFilterChange("executor", value)}
|
||||
placeholder="e.g., 1"
|
||||
/>
|
||||
<div>
|
||||
<MultiSelect
|
||||
label="Status"
|
||||
options={STATUS_OPTIONS}
|
||||
value={selectedStatuses}
|
||||
onChange={setSelectedStatuses}
|
||||
placeholder="All Statuses"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results section - isolated from filter state, only depends on query results */}
|
||||
{viewMode === "all" ? (
|
||||
<ExecutionsResultsTable
|
||||
executions={filteredExecutions}
|
||||
isLoading={isLoading}
|
||||
isFetching={isFetching}
|
||||
error={error as Error | null}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
clearFilters={clearFilters}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
selectedExecutionId={selectedExecutionId}
|
||||
onSelectExecution={handleSelectExecution}
|
||||
/>
|
||||
) : (
|
||||
<WorkflowExecutionTree
|
||||
executions={filteredExecutions}
|
||||
isLoading={isLoading}
|
||||
isFetching={isFetching}
|
||||
error={error as Error | null}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
clearFilters={clearFilters}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
workflowActionRefs={baseSuggestions.workflowActionRefs}
|
||||
selectedExecutionId={selectedExecutionId}
|
||||
onSelectExecution={handleSelectExecution}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Filter section - always mounted, never unmounts during loading */}
|
||||
<div className="bg-white shadow rounded-lg p-4 mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-5 w-5 text-gray-400" />
|
||||
<h2 className="text-lg font-semibold">Filter Executions</h2>
|
||||
</div>
|
||||
{hasActiveFilters && (
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
className="flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
{/* Right-side preview panel */}
|
||||
{selectedExecutionId && (
|
||||
<div className="w-[400px] flex-shrink-0 h-full">
|
||||
<ExecutionPreviewPanel
|
||||
executionId={selectedExecutionId}
|
||||
onClose={handleClosePreview}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4">
|
||||
<AutocompleteInput
|
||||
label="Pack"
|
||||
value={searchFilters.pack}
|
||||
onChange={(value) => handleFilterChange("pack", value)}
|
||||
suggestions={packSuggestions}
|
||||
placeholder="e.g., core"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Rule"
|
||||
value={searchFilters.rule}
|
||||
onChange={(value) => handleFilterChange("rule", value)}
|
||||
suggestions={ruleSuggestions}
|
||||
placeholder="e.g., core.on_timer"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Action"
|
||||
value={searchFilters.action}
|
||||
onChange={(value) => handleFilterChange("action", value)}
|
||||
suggestions={actionSuggestions}
|
||||
placeholder="e.g., core.echo"
|
||||
/>
|
||||
<AutocompleteInput
|
||||
label="Trigger"
|
||||
value={searchFilters.trigger}
|
||||
onChange={(value) => handleFilterChange("trigger", value)}
|
||||
suggestions={triggerSuggestions}
|
||||
placeholder="e.g., core.timer"
|
||||
/>
|
||||
<FilterInput
|
||||
label="Executor ID"
|
||||
value={searchFilters.executor}
|
||||
onChange={(value) => handleFilterChange("executor", value)}
|
||||
placeholder="e.g., 1"
|
||||
/>
|
||||
<div>
|
||||
<MultiSelect
|
||||
label="Status"
|
||||
options={STATUS_OPTIONS}
|
||||
value={selectedStatuses}
|
||||
onChange={setSelectedStatuses}
|
||||
placeholder="All Statuses"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Results section - isolated from filter state, only depends on query results */}
|
||||
<ExecutionsResultsTable
|
||||
executions={filteredExecutions}
|
||||
isLoading={isLoading}
|
||||
isFetching={isFetching}
|
||||
error={error as Error | null}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
clearFilters={clearFilters}
|
||||
page={page}
|
||||
setPage={setPage}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user