orcheo-studio 0.24.4 → 0.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bumpversion.cfg +1 -1
- package/dist/assets/{arc-99TH-Koz.js → arc-CJePtrV4.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-DugaTSY2.js → architectureDiagram-VXUJARFQ-BavAz0jp.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-BH68wVtD.js → blockDiagram-VD42YOAC-Bx1XAydH.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-V6Vqtp7B.js → c4Diagram-YG6GDRKO-B2LhByin.js} +1 -1
- package/dist/assets/channel-BuDj-lWJ.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-CvbCAMNv.js → chunk-4BX2VUAB-UqiH5dLp.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-h_T3kQh7.js → chunk-55IACEB6-ChxTr_6v.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-Dm6dgbGh.js → chunk-B4BG7PRW-2ETw-eA1.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-DSXFA4Fi.js → chunk-DI55MBZ5-BdxVSKej.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DJDlfjt1.js → chunk-FMBD7UC4-BJF2M5iQ.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-BnjUGeBL.js → chunk-QN33PNHL-DI9t3nKL.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BRudOghZ.js → chunk-QZHKN3VN-DsbUIBgB.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-CpxSpvqK.js → chunk-TZMSLE5B-DzMhWfBV.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-Bc5AzWzU.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-Bc5AzWzU.js +1 -0
- package/dist/assets/clone-CtxIENQC.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Dl80WZ_a.js → cose-bilkent-S5V4N54A-BcKKbLWz.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-vw-vkw78.js → dagre-6UL2VRFP-CERJ9KSL.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-B4MT5Nvf.js → diagram-PSM6KHXK-DGl--Cyq.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-CMioZnRI.js → diagram-QEK2KX5R-DbfdELCd.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-PJAE8YS0.js → diagram-S2PKOQOG-CsR4YAm5.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-DCd-WouG.js → erDiagram-Q2GNP2WA-BlOr_gpI.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-DS__-WT4.js → flowDiagram-NV44I4VS-BAyDXjxF.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-Btifh-31.js → ganttDiagram-JELNMOA3-b2ntam6s.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-BCyjopey.js → gitGraphDiagram-V2S2FVAM-BvdeZrga.js} +1 -1
- package/dist/assets/{graph-6YBx8iQ0.js → graph-DMrtDwYQ.js} +1 -1
- package/dist/assets/{index-lkycEDHM.js → index-CaWVBOSf.js} +204 -189
- package/dist/assets/index-CbLxL6va.css +1 -0
- package/dist/assets/{infoDiagram-HS3SLOUP-CAsJ2eU1.js → infoDiagram-HS3SLOUP-CFO6LsEb.js} +1 -1
- package/dist/assets/{isUndefined-CcAlyXn3.js → isUndefined-DE2VOYrW.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-Bi8XsX15.js → journeyDiagram-XKPGCS4Q-Svi8cltR.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-PTAFVsDn.js → kanban-definition-3W4ZIXB7-C7uds03_.js} +1 -1
- package/dist/assets/{layout-Do-GOFdW.js → layout-D5N_DnHw.js} +1 -1
- package/dist/assets/{linear-CZlgolw0.js → linear-h9-K1RBq.js} +1 -1
- package/dist/assets/{min-N15IYW1Y.js → min-CjtZYucR.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-Dlgxv6Jo.js → mindmap-definition-VGOIOE7T-Bw80_6TW.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-Dr2AfIw5.js → pieDiagram-ADFJNKIX-DGukFJSP.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-B8TPuvW6.js → quadrantDiagram-AYHSOK5B-CU7GPi0P.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-CdD2oP1N.js → requirementDiagram-UZGBJVZJ-D_aCZrji.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-XEMX6l5k.js → sankeyDiagram-TZEHDZUN-Wgtprjih.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-BlIKpBev.js → sequenceDiagram-WL72ISMW-BCOCCVxS.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-D3KGQY6v.js → stateDiagram-FKZM4ZOC-DHVe22f9.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Bq9eQr8D.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-TkL4mz_f.js → timeline-definition-IT6M3QCI-Cm4CAEQk.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-BDp_c1Vc.js → treemap-GDKQZRPO-DvLhDp9r.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-D0dNQImL.js → xychartDiagram-PRI3JC2R-BtcJYIPe.js} +1 -1
- package/dist/index.html +8 -2
- package/index.html +6 -0
- package/package.json +1 -1
- package/src/App.tsx +48 -44
- package/src/design-system/ui/button.tsx +1 -1
- package/src/features/account/components/settings/appearance-settings-tab.tsx +1 -1
- package/src/features/account/pages/profile/components/profile-general-tab.tsx +1 -1
- package/src/features/account/pages/workspace-members.tsx +58 -62
- package/src/features/auth/pages/login.tsx +4 -2
- package/src/features/auth/pages/verify.tsx +1 -1
- package/src/features/chatkit/components/public-chat-error-boundary.tsx +1 -1
- package/src/features/chatkit/components/public-chat-widget.tsx +2 -3
- package/src/features/chatkit/lib/chatkit-theme.ts +3 -2
- package/src/features/chatkit/lib/public-chat-access.ts +13 -0
- package/src/features/chatkit/pages/public-chat.tsx +18 -27
- package/src/features/shared/components/chat-interface.types.ts +1 -5
- package/src/features/shared/components/top-navigation/studio-brand.tsx +1 -1
- package/src/features/shared/components/top-navigation/version-status.tsx +27 -31
- package/src/features/shared/components/workspace-bootstrap-gate.tsx +4 -4
- package/src/features/workflow/components/dialogs/credential-access-badge.tsx +2 -2
- package/src/features/workflow/components/dialogs/credential-status-badge.tsx +2 -2
- package/src/features/workflow/components/dialogs/credentials-table.tsx +1 -1
- package/src/features/workflow/components/dialogs/update-workflow-dialog.tsx +34 -78
- package/src/features/workflow/components/dialogs/upload-workflow-dialog.tsx +40 -136
- package/src/features/workflow/components/dialogs/workflow-folder-picker.tsx +178 -0
- package/src/features/workflow/components/panels/rjsf-cron-widget.tsx +35 -10
- package/src/features/workflow/components/panels/workflow-diff-dialog.tsx +5 -5
- package/src/features/workflow/components/panels/workflow-history-table.tsx +3 -4
- package/src/features/workflow/components/trace/agent-prism/SpanCard/SpanCardConnector.tsx +1 -5
- package/src/features/workflow/components/trace/agent-prism/shared.ts +1 -10
- package/src/features/workflow/components/trace/agent-prism/theme/theme.css +74 -74
- package/src/features/workflow/lib/workflow-execution-builders.ts +4 -4
- package/src/features/workflow/lib/workflow-execution.types.ts +2 -9
- package/src/features/workflow/lib/workflow-folder-selection.ts +239 -0
- package/src/features/workflow/lib/workflow-frontmatter.ts +3 -1
- package/src/features/workflow/lib/workflow-storage-api.ts +5 -9
- package/src/features/workflow/lib/workflow-storage-helpers.ts +2 -4
- package/src/features/workflow/lib/workflow-storage.ts +6 -2
- package/src/features/workflow/lib/workflow-storage.types.ts +1 -6
- package/src/features/workflow/lib/workflow-upload-errors.ts +6 -2
- package/src/features/workflow/pages/workflow/components/settings-tab-content.tsx +24 -9
- package/src/features/workflow/pages/workflow/components/workflow-tab-content.tsx +6 -4
- package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-execution-controller.ts +4 -1
- package/src/features/workflow/pages/workflow/hooks/use-execution-updates.ts +4 -2
- package/src/features/workflow/pages/workflow-gallery/create-team-dialog.tsx +2 -6
- package/src/features/workflow/pages/workflow-gallery/workflow-card.tsx +16 -16
- package/src/index.css +101 -59
- package/src/lib/api.ts +7 -2
- package/src/lib/workspace-routing.ts +1 -3
- package/tailwind.config.js +47 -2
- package/dist/assets/channel-D562bZxL.js +0 -1
- package/dist/assets/classDiagram-2ON5EDUG-Dix_KnuM.js +0 -1
- package/dist/assets/classDiagram-v2-WZHVMYZB-Dix_KnuM.js +0 -1
- package/dist/assets/clone-0c2Tu-SH.js +0 -1
- package/dist/assets/index-Df4jFRhd.css +0 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Di3Nkv2n.js +0 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared logic for selecting a workflow from a folder.
|
|
3
|
+
*
|
|
4
|
+
* A workflow folder is expected to contain exactly one Python (`.py`) script
|
|
5
|
+
* and at most one JSON (`.json`) config file at its top level. Multiple files
|
|
6
|
+
* of either kind are rejected as ambiguous so callers can surface an
|
|
7
|
+
* easy-to-understand error. The helpers here work for both folder `<input>`
|
|
8
|
+
* elements (`webkitdirectory`) and folder drag-and-drop.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const MAX_SCRIPT_UPLOAD_BYTES = 1024 * 1024;
|
|
12
|
+
export const MAX_CONFIG_UPLOAD_BYTES = 256 * 1024;
|
|
13
|
+
|
|
14
|
+
/** Error with a user-facing message describing why a folder was rejected. */
|
|
15
|
+
export class WorkflowFolderSelectionError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "WorkflowFolderSelectionError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WorkflowFolderSelection {
|
|
23
|
+
scriptName: string;
|
|
24
|
+
scriptContent: string;
|
|
25
|
+
configName: string | null;
|
|
26
|
+
configContent: Record<string, unknown> | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const formatUploadLimit = (bytes: number): string => {
|
|
30
|
+
if (bytes >= 1024 * 1024) {
|
|
31
|
+
return `${bytes / (1024 * 1024)} MB`;
|
|
32
|
+
}
|
|
33
|
+
return `${bytes / 1024} KB`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const hasExtension = (name: string, extension: string): boolean =>
|
|
37
|
+
name.toLowerCase().endsWith(extension);
|
|
38
|
+
|
|
39
|
+
const formatNames = (files: File[]): string =>
|
|
40
|
+
files.map((file) => file.name).join(", ");
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Split a list of files into the single script and optional config.
|
|
44
|
+
* Throws {@link WorkflowFolderSelectionError} when the folder is ambiguous or
|
|
45
|
+
* is missing a script.
|
|
46
|
+
*/
|
|
47
|
+
export const classifyWorkflowFolderFiles = (
|
|
48
|
+
files: File[],
|
|
49
|
+
): { script: File; config: File | null } => {
|
|
50
|
+
const scripts = files.filter((file) => hasExtension(file.name, ".py"));
|
|
51
|
+
const configs = files.filter((file) => hasExtension(file.name, ".json"));
|
|
52
|
+
|
|
53
|
+
if (scripts.length === 0) {
|
|
54
|
+
throw new WorkflowFolderSelectionError(
|
|
55
|
+
"No Python (.py) workflow script was found in the folder. Add exactly one .py file and try again.",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (scripts.length > 1) {
|
|
59
|
+
throw new WorkflowFolderSelectionError(
|
|
60
|
+
`Found ${scripts.length} Python (.py) files (${formatNames(scripts)}). ` +
|
|
61
|
+
"Keep exactly one .py file in the folder so it's clear which script to use.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (configs.length > 1) {
|
|
65
|
+
throw new WorkflowFolderSelectionError(
|
|
66
|
+
`Found ${configs.length} JSON (.json) config files (${formatNames(configs)}). ` +
|
|
67
|
+
"Keep at most one .json file in the folder so it's clear which config to use.",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { script: scripts[0], config: configs[0] ?? null };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const isTopLevelFile = (file: File): boolean => {
|
|
75
|
+
const relativePath =
|
|
76
|
+
(file as File & { webkitRelativePath?: string }).webkitRelativePath ?? "";
|
|
77
|
+
// "" (no path info), "folder/file.py" → top level; "folder/sub/file.py" → nested.
|
|
78
|
+
return relativePath.split("/").length <= 2;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Collect the top-level files from a folder `<input webkitdirectory>` FileList,
|
|
83
|
+
* ignoring anything inside nested subdirectories.
|
|
84
|
+
*/
|
|
85
|
+
export const collectFolderInputFiles = (fileList: FileList | null): File[] => {
|
|
86
|
+
if (!fileList) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
return Array.from(fileList).filter(isTopLevelFile);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
interface FileSystemEntryLike {
|
|
93
|
+
isFile: boolean;
|
|
94
|
+
isDirectory: boolean;
|
|
95
|
+
file?: (
|
|
96
|
+
onSuccess: (file: File) => void,
|
|
97
|
+
onError?: (err: unknown) => void,
|
|
98
|
+
) => void;
|
|
99
|
+
createReader?: () => {
|
|
100
|
+
readEntries: (
|
|
101
|
+
onSuccess: (entries: FileSystemEntryLike[]) => void,
|
|
102
|
+
onError?: (err: unknown) => void,
|
|
103
|
+
) => void;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const entryToFile = (entry: FileSystemEntryLike): Promise<File> =>
|
|
108
|
+
new Promise((resolve, reject) => {
|
|
109
|
+
if (!entry.file) {
|
|
110
|
+
reject(new Error("Unable to read dropped file."));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
entry.file(resolve, reject);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const readDirectoryEntries = (
|
|
117
|
+
directory: FileSystemEntryLike,
|
|
118
|
+
): Promise<FileSystemEntryLike[]> =>
|
|
119
|
+
new Promise((resolve, reject) => {
|
|
120
|
+
const reader = directory.createReader?.();
|
|
121
|
+
if (!reader) {
|
|
122
|
+
resolve([]);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const entries: FileSystemEntryLike[] = [];
|
|
126
|
+
const readBatch = () => {
|
|
127
|
+
reader.readEntries((batch) => {
|
|
128
|
+
// readEntries returns results in batches; an empty batch marks the end.
|
|
129
|
+
if (batch.length === 0) {
|
|
130
|
+
resolve(entries);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
entries.push(...batch);
|
|
134
|
+
readBatch();
|
|
135
|
+
}, reject);
|
|
136
|
+
};
|
|
137
|
+
readBatch();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Collect the top-level files from a folder drag-and-drop event. Supports
|
|
142
|
+
* dropping a folder (its immediate children are used) or dropping the loose
|
|
143
|
+
* files directly. Falls back to `dataTransfer.files` when the entry API is
|
|
144
|
+
* unavailable.
|
|
145
|
+
*/
|
|
146
|
+
export const collectDroppedFolderFiles = async (
|
|
147
|
+
dataTransfer: DataTransfer,
|
|
148
|
+
): Promise<File[]> => {
|
|
149
|
+
const items = dataTransfer.items ? Array.from(dataTransfer.items) : [];
|
|
150
|
+
const entries: FileSystemEntryLike[] = [];
|
|
151
|
+
for (const item of items) {
|
|
152
|
+
const entry =
|
|
153
|
+
typeof item.webkitGetAsEntry === "function"
|
|
154
|
+
? item.webkitGetAsEntry()
|
|
155
|
+
: null;
|
|
156
|
+
if (entry) {
|
|
157
|
+
entries.push(entry as unknown as FileSystemEntryLike);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (entries.length === 0) {
|
|
162
|
+
return Array.from(dataTransfer.files ?? []);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const files: File[] = [];
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
if (entry.isFile) {
|
|
168
|
+
files.push(await entryToFile(entry));
|
|
169
|
+
} else if (entry.isDirectory) {
|
|
170
|
+
const children = await readDirectoryEntries(entry);
|
|
171
|
+
for (const child of children) {
|
|
172
|
+
if (child.isFile) {
|
|
173
|
+
files.push(await entryToFile(child));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return files;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const readFileText = (file: File): Promise<string> =>
|
|
182
|
+
new Promise((resolve, reject) => {
|
|
183
|
+
const reader = new FileReader();
|
|
184
|
+
reader.onload = () => {
|
|
185
|
+
const result = reader.result;
|
|
186
|
+
if (typeof result === "string") {
|
|
187
|
+
resolve(result);
|
|
188
|
+
} else {
|
|
189
|
+
reject(new Error(`Unable to read ${file.name}.`));
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
reader.onerror = () =>
|
|
193
|
+
reject(reader.error ?? new Error(`Unable to read ${file.name}.`));
|
|
194
|
+
reader.readAsText(file);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Classify, validate, and read the workflow script and optional config from a
|
|
199
|
+
* folder's files. Throws {@link WorkflowFolderSelectionError} with a
|
|
200
|
+
* user-facing message on any problem.
|
|
201
|
+
*/
|
|
202
|
+
export const loadWorkflowFolderSelection = async (
|
|
203
|
+
files: File[],
|
|
204
|
+
): Promise<WorkflowFolderSelection> => {
|
|
205
|
+
const { script, config } = classifyWorkflowFolderFiles(files);
|
|
206
|
+
|
|
207
|
+
if (script.size > MAX_SCRIPT_UPLOAD_BYTES) {
|
|
208
|
+
throw new WorkflowFolderSelectionError(
|
|
209
|
+
`Workflow script must be ${formatUploadLimit(MAX_SCRIPT_UPLOAD_BYTES)} or smaller.`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
const scriptContent = await readFileText(script);
|
|
213
|
+
|
|
214
|
+
let configName: string | null = null;
|
|
215
|
+
let configContent: Record<string, unknown> | null = null;
|
|
216
|
+
if (config) {
|
|
217
|
+
if (config.size > MAX_CONFIG_UPLOAD_BYTES) {
|
|
218
|
+
throw new WorkflowFolderSelectionError(
|
|
219
|
+
`Config file must be ${formatUploadLimit(MAX_CONFIG_UPLOAD_BYTES)} or smaller.`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const configText = await readFileText(config);
|
|
223
|
+
try {
|
|
224
|
+
configContent = JSON.parse(configText) as Record<string, unknown>;
|
|
225
|
+
} catch {
|
|
226
|
+
throw new WorkflowFolderSelectionError(
|
|
227
|
+
`${config.name} is not valid JSON.`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
configName = config.name;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
scriptName: script.name,
|
|
235
|
+
scriptContent,
|
|
236
|
+
configName,
|
|
237
|
+
configContent,
|
|
238
|
+
};
|
|
239
|
+
};
|
|
@@ -22,7 +22,9 @@ const parseTomlString = (value: string): string | undefined => {
|
|
|
22
22
|
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
23
23
|
try {
|
|
24
24
|
const parsed = JSON.parse(trimmed) as unknown;
|
|
25
|
-
return typeof parsed === "string" && parsed.trim()
|
|
25
|
+
return typeof parsed === "string" && parsed.trim()
|
|
26
|
+
? parsed.trim()
|
|
27
|
+
: undefined;
|
|
26
28
|
} catch {
|
|
27
29
|
return undefined;
|
|
28
30
|
}
|
|
@@ -498,7 +498,8 @@ export const extractCronConfigFromVersionGraph = (
|
|
|
498
498
|
|
|
499
499
|
// Matches a value that is exactly a single `{{config.configurable.<name>}}`
|
|
500
500
|
// placeholder, optionally padded with whitespace inside the braces.
|
|
501
|
-
const CONFIGURABLE_TEMPLATE =
|
|
501
|
+
const CONFIGURABLE_TEMPLATE =
|
|
502
|
+
/^\{\{\s*config\.configurable\.([^}\s.]+)\s*\}\}$/;
|
|
502
503
|
|
|
503
504
|
const resolveConfigurableTemplate = (
|
|
504
505
|
value: string | null | undefined,
|
|
@@ -539,16 +540,11 @@ const resolveConfigurableCronConfig = (
|
|
|
539
540
|
resolveConfigurableTemplate(config.expression, configurable) ??
|
|
540
541
|
config.expression,
|
|
541
542
|
timezone: resolveConfigurableTemplate(config.timezone, configurable) as
|
|
542
|
-
|
|
|
543
|
-
| undefined,
|
|
543
|
+
string | undefined,
|
|
544
544
|
start_at: resolveConfigurableTemplate(config.start_at, configurable) as
|
|
545
|
-
|
|
|
546
|
-
| null
|
|
547
|
-
| undefined,
|
|
545
|
+
string | null | undefined,
|
|
548
546
|
end_at: resolveConfigurableTemplate(config.end_at, configurable) as
|
|
549
|
-
|
|
|
550
|
-
| null
|
|
551
|
-
| undefined,
|
|
547
|
+
string | null | undefined,
|
|
552
548
|
};
|
|
553
549
|
};
|
|
554
550
|
|
|
@@ -281,11 +281,9 @@ const parseWorkflowMetadata = (
|
|
|
281
281
|
|
|
282
282
|
const workflowRecord = workflowMetadata as Record<string, unknown>;
|
|
283
283
|
const snapshotPayload = workflowRecord.snapshot as
|
|
284
|
-
|
|
|
285
|
-
| undefined;
|
|
284
|
+
WorkflowSnapshot | undefined;
|
|
286
285
|
const summaryPayload = workflowRecord.summary as
|
|
287
|
-
|
|
288
|
-
| undefined;
|
|
286
|
+
WorkflowDiffResult["summary"] | undefined;
|
|
289
287
|
const messagePayload = workflowRecord.message as string | undefined;
|
|
290
288
|
const workflowToGraph = (workflowRecord.workflowToGraph ??
|
|
291
289
|
workflowRecord.canvasToGraph) as Record<string, string> | undefined;
|
|
@@ -399,7 +399,9 @@ export const uploadWorkflowFromFiles = async (
|
|
|
399
399
|
});
|
|
400
400
|
} catch (error) {
|
|
401
401
|
const message =
|
|
402
|
-
error instanceof Error
|
|
402
|
+
error instanceof Error
|
|
403
|
+
? error.message
|
|
404
|
+
: "Upload failed. Please try again.";
|
|
403
405
|
const uploadError = saveWorkflowUploadError(created.id, message);
|
|
404
406
|
const stored = await ensureWorkflow(created.id);
|
|
405
407
|
invalidateWorkflowListCache();
|
|
@@ -453,7 +455,9 @@ export const updateWorkflowFromFiles = async (
|
|
|
453
455
|
});
|
|
454
456
|
} catch (error) {
|
|
455
457
|
const message =
|
|
456
|
-
error instanceof Error
|
|
458
|
+
error instanceof Error
|
|
459
|
+
? error.message
|
|
460
|
+
: "Update failed. Please try again.";
|
|
457
461
|
saveWorkflowUploadError(workflowId, message);
|
|
458
462
|
invalidateWorkflowListCache();
|
|
459
463
|
invalidateWorkflowCache(workflowId);
|
|
@@ -161,12 +161,7 @@ export interface WorkflowListenerHealth {
|
|
|
161
161
|
last_event_at?: string | null;
|
|
162
162
|
last_error?: string | null;
|
|
163
163
|
runtime_status:
|
|
164
|
-
| "
|
|
165
|
-
| "healthy"
|
|
166
|
-
| "backoff"
|
|
167
|
-
| "stopped"
|
|
168
|
-
| "error"
|
|
169
|
-
| "unknown";
|
|
164
|
+
"starting" | "healthy" | "backoff" | "stopped" | "error" | "unknown";
|
|
170
165
|
runtime_detail?: string | null;
|
|
171
166
|
last_polled_at?: string | null;
|
|
172
167
|
consecutive_failures: number;
|
|
@@ -7,7 +7,8 @@ export interface WorkflowUploadError {
|
|
|
7
7
|
|
|
8
8
|
const STORAGE_PREFIX = "orcheo.workflowUploadError.";
|
|
9
9
|
|
|
10
|
-
const storageKey = (workflowId: string): string =>
|
|
10
|
+
const storageKey = (workflowId: string): string =>
|
|
11
|
+
`${STORAGE_PREFIX}${workflowId}`;
|
|
11
12
|
|
|
12
13
|
const canUseSessionStorage = (): boolean =>
|
|
13
14
|
typeof window !== "undefined" && Boolean(window.sessionStorage);
|
|
@@ -26,7 +27,10 @@ export const saveWorkflowUploadError = (
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
try {
|
|
29
|
-
window.sessionStorage.setItem(
|
|
30
|
+
window.sessionStorage.setItem(
|
|
31
|
+
storageKey(workflowId),
|
|
32
|
+
JSON.stringify(error),
|
|
33
|
+
);
|
|
30
34
|
} catch {
|
|
31
35
|
// Upload failure rendering should not depend on storage availability.
|
|
32
36
|
}
|
|
@@ -61,15 +61,27 @@ const runtimeStatusBadge = (
|
|
|
61
61
|
) => {
|
|
62
62
|
switch (status) {
|
|
63
63
|
case "healthy":
|
|
64
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
label: "Healthy",
|
|
66
|
+
className: "bg-success text-success-foreground",
|
|
67
|
+
};
|
|
65
68
|
case "starting":
|
|
66
|
-
return { label: "Starting", className: "bg-
|
|
69
|
+
return { label: "Starting", className: "bg-info text-info-foreground" };
|
|
67
70
|
case "backoff":
|
|
68
|
-
return {
|
|
71
|
+
return {
|
|
72
|
+
label: "Backoff",
|
|
73
|
+
className: "bg-warning text-warning-foreground",
|
|
74
|
+
};
|
|
69
75
|
case "error":
|
|
70
|
-
return {
|
|
76
|
+
return {
|
|
77
|
+
label: "Runtime Error",
|
|
78
|
+
className: "bg-destructive text-destructive-foreground",
|
|
79
|
+
};
|
|
71
80
|
case "stopped":
|
|
72
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
label: "Stopped",
|
|
83
|
+
className: "bg-muted-foreground text-background",
|
|
84
|
+
};
|
|
73
85
|
default:
|
|
74
86
|
return { label: "Unknown", className: "bg-muted text-muted-foreground" };
|
|
75
87
|
}
|
|
@@ -274,13 +286,13 @@ export function SettingsTabContent({
|
|
|
274
286
|
</AlertDescription>
|
|
275
287
|
</Alert>
|
|
276
288
|
) : isListenersLoading ? (
|
|
277
|
-
<Card>
|
|
289
|
+
<Card className="border-border/70 bg-muted/40 shadow-none">
|
|
278
290
|
<CardContent className="pt-6 text-sm text-muted-foreground">
|
|
279
291
|
Loading listener health and control state...
|
|
280
292
|
</CardContent>
|
|
281
293
|
</Card>
|
|
282
294
|
) : listeners.length === 0 ? (
|
|
283
|
-
<Card>
|
|
295
|
+
<Card className="border-border/70 bg-muted/40 shadow-none">
|
|
284
296
|
<CardHeader>
|
|
285
297
|
<CardTitle>No listeners detected</CardTitle>
|
|
286
298
|
<CardDescription>
|
|
@@ -320,7 +332,10 @@ export function SettingsTabContent({
|
|
|
320
332
|
listener.status === "paused" || listener.status === "blocked";
|
|
321
333
|
|
|
322
334
|
return (
|
|
323
|
-
<Card
|
|
335
|
+
<Card
|
|
336
|
+
key={listener.subscription_id}
|
|
337
|
+
className="border-border/70 bg-muted/40 shadow-none"
|
|
338
|
+
>
|
|
324
339
|
<CardHeader className="gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
325
340
|
<div className="space-y-2">
|
|
326
341
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -394,7 +409,7 @@ export function SettingsTabContent({
|
|
|
394
409
|
|
|
395
410
|
{(listener.runtime_detail || listener.last_error) && (
|
|
396
411
|
<div className="sm:col-span-2 xl:col-span-4">
|
|
397
|
-
<div className="rounded-lg border border-
|
|
412
|
+
<div className="rounded-lg border border-warning/40 bg-warning-muted/70 px-3 py-2 text-warning-muted-foreground">
|
|
398
413
|
<div className="mb-1 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em]">
|
|
399
414
|
<Zap className="h-3.5 w-3.5" />
|
|
400
415
|
Runtime detail
|
|
@@ -239,7 +239,7 @@ const resolveRunResultBanner = (
|
|
|
239
239
|
return {
|
|
240
240
|
Icon: CheckCircle2,
|
|
241
241
|
className:
|
|
242
|
-
"border-
|
|
242
|
+
"border-success/40 bg-success-muted text-success-muted-foreground",
|
|
243
243
|
message: "Workflow run succeeded. See the full trace on the Trace tab.",
|
|
244
244
|
};
|
|
245
245
|
case "failed":
|
|
@@ -252,7 +252,7 @@ const resolveRunResultBanner = (
|
|
|
252
252
|
return {
|
|
253
253
|
Icon: AlertTriangle,
|
|
254
254
|
className:
|
|
255
|
-
"border-
|
|
255
|
+
"border-warning/40 bg-warning-muted text-warning-muted-foreground",
|
|
256
256
|
message:
|
|
257
257
|
"Workflow run finished with partial results. Check the Trace tab for details.",
|
|
258
258
|
};
|
|
@@ -261,7 +261,7 @@ const resolveRunResultBanner = (
|
|
|
261
261
|
return {
|
|
262
262
|
Icon: AlertTriangle,
|
|
263
263
|
className:
|
|
264
|
-
"border-
|
|
264
|
+
"border-warning/40 bg-warning-muted text-warning-muted-foreground",
|
|
265
265
|
message:
|
|
266
266
|
"Workflow run ended without a final status. Check the Trace tab for the latest status.",
|
|
267
267
|
};
|
|
@@ -752,7 +752,9 @@ export function WorkflowTabContent({
|
|
|
752
752
|
void handleScheduleToggle(checked)
|
|
753
753
|
}
|
|
754
754
|
disabled={
|
|
755
|
-
isSchedulePending ||
|
|
755
|
+
isSchedulePending ||
|
|
756
|
+
!canToggleSchedule ||
|
|
757
|
+
uploadBlocksWorkflow
|
|
756
758
|
}
|
|
757
759
|
/>
|
|
758
760
|
</div>
|
package/src/features/workflow/pages/workflow/hooks/controller/use-workflow-execution-controller.ts
CHANGED
|
@@ -92,7 +92,10 @@ export function useWorkflowExecutionController(
|
|
|
92
92
|
|
|
93
93
|
const graphToWorkflow = latestVersionRecord?.graphToWorkflow ?? {};
|
|
94
94
|
const executionId = generateRandomId("run");
|
|
95
|
-
const executionRecord = createExecutionRecord(
|
|
95
|
+
const executionRecord = createExecutionRecord(
|
|
96
|
+
executionId,
|
|
97
|
+
graphToWorkflow,
|
|
98
|
+
);
|
|
96
99
|
|
|
97
100
|
if (core.websocketRef.current) {
|
|
98
101
|
core.websocketRef.current.close();
|
|
@@ -48,8 +48,10 @@ export function useExecutionUpdates({
|
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
const describePayload = useCallback(
|
|
51
|
-
(
|
|
52
|
-
|
|
51
|
+
(
|
|
52
|
+
payload: Record<string, unknown>,
|
|
53
|
+
graphToWorkflow: Record<string, string>,
|
|
54
|
+
) => describePayloadRaw(payload, graphToWorkflow, (id) => id),
|
|
53
55
|
[],
|
|
54
56
|
);
|
|
55
57
|
|
|
@@ -70,9 +70,7 @@ export const CreateTeamDialog = ({
|
|
|
70
70
|
await onCreate(trimmedName, trimmedSlug);
|
|
71
71
|
onOpenChange(false);
|
|
72
72
|
} catch (err) {
|
|
73
|
-
setError(
|
|
74
|
-
err instanceof Error ? err.message : "Failed to create team.",
|
|
75
|
-
);
|
|
73
|
+
setError(err instanceof Error ? err.message : "Failed to create team.");
|
|
76
74
|
} finally {
|
|
77
75
|
setIsSubmitting(false);
|
|
78
76
|
}
|
|
@@ -118,9 +116,7 @@ export const CreateTeamDialog = ({
|
|
|
118
116
|
underscores only.
|
|
119
117
|
</p>
|
|
120
118
|
</div>
|
|
121
|
-
{error ?
|
|
122
|
-
<p className="text-sm text-destructive">{error}</p>
|
|
123
|
-
) : null}
|
|
119
|
+
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
|
124
120
|
</div>
|
|
125
121
|
|
|
126
122
|
<DialogFooter>
|
|
@@ -213,7 +213,7 @@ export const WorkflowCard = ({
|
|
|
213
213
|
<>
|
|
214
214
|
<Card
|
|
215
215
|
className={cn(
|
|
216
|
-
"flex w-full flex-col overflow-hidden border-border/70 bg-card text-card-foreground shadow-[0_16px_48px_rgba(
|
|
216
|
+
"flex w-full flex-col overflow-hidden border-border/70 bg-card text-card-foreground shadow-[0_16px_48px_rgba(40,30,16,0.1)] transition-transform duration-200 hover:-translate-y-1 dark:shadow-[0_16px_48px_rgba(0,0,0,0.45)] text-[20px] md:text-[14px]",
|
|
217
217
|
WORKFLOW_GALLERY_CARD_ASPECT_CLASSNAME,
|
|
218
218
|
isClickable && "cursor-pointer",
|
|
219
219
|
)}
|
|
@@ -223,7 +223,7 @@ export const WorkflowCard = ({
|
|
|
223
223
|
role={isClickable ? "button" : undefined}
|
|
224
224
|
tabIndex={isClickable ? 0 : undefined}
|
|
225
225
|
>
|
|
226
|
-
<CardHeader className="relative flex h-12 items-center justify-center overflow-hidden border-b border-border/70 bg-gradient-to-r from-
|
|
226
|
+
<CardHeader className="relative flex h-12 items-center justify-center overflow-hidden border-b border-border/70 bg-gradient-to-r from-stone-950 via-stone-900 to-stone-800 px-4 py-0 text-center text-stone-50 dark:border-border/60 dark:from-stone-100 dark:via-stone-50 dark:to-stone-200 dark:text-stone-950">
|
|
227
227
|
<div
|
|
228
228
|
className="absolute inset-0 opacity-25 dark:opacity-40"
|
|
229
229
|
aria-hidden="true"
|
|
@@ -235,7 +235,7 @@ export const WorkflowCard = ({
|
|
|
235
235
|
/>
|
|
236
236
|
|
|
237
237
|
<div className="relative z-10 flex items-center gap-2">
|
|
238
|
-
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70 dark:text-
|
|
238
|
+
<span className="font-mono text-[10px] uppercase tracking-[0.22em] text-white/70 dark:text-stone-700">
|
|
239
239
|
{headerLabel}
|
|
240
240
|
</span>
|
|
241
241
|
</div>
|
|
@@ -253,7 +253,7 @@ export const WorkflowCard = ({
|
|
|
253
253
|
<Button
|
|
254
254
|
variant="ghost"
|
|
255
255
|
size="icon"
|
|
256
|
-
className="absolute right-2 top-2 z-10 h-7 w-7 text-white/75 hover:bg-white/10 hover:text-white dark:text-
|
|
256
|
+
className="absolute right-2 top-2 z-10 h-7 w-7 text-white/75 hover:bg-white/10 hover:text-white dark:text-stone-700 dark:hover:bg-stone-950/10 dark:hover:text-stone-950"
|
|
257
257
|
onClick={stopPropagation}
|
|
258
258
|
onPointerDown={stopPropagation}
|
|
259
259
|
aria-label="Workflow actions"
|
|
@@ -290,7 +290,7 @@ export const WorkflowCard = ({
|
|
|
290
290
|
<>
|
|
291
291
|
<DropdownMenuSeparator />
|
|
292
292
|
<DropdownMenuItem
|
|
293
|
-
className="text-
|
|
293
|
+
className="text-destructive"
|
|
294
294
|
onSelect={(event) => {
|
|
295
295
|
event.preventDefault();
|
|
296
296
|
event.stopPropagation();
|
|
@@ -314,31 +314,31 @@ export const WorkflowCard = ({
|
|
|
314
314
|
className={cn(
|
|
315
315
|
"absolute left-0 top-0 flex items-center gap-1 rounded-md border px-2 py-1 shadow-sm",
|
|
316
316
|
uploadError
|
|
317
|
-
? "border-
|
|
318
|
-
: "border-border/70 bg-muted/60 text-muted-foreground dark:bg-
|
|
317
|
+
? "border-destructive/40 bg-destructive-muted text-destructive-muted-foreground"
|
|
318
|
+
: "border-border/70 bg-muted/60 text-muted-foreground dark:bg-background/30",
|
|
319
319
|
)}
|
|
320
320
|
>
|
|
321
321
|
<span
|
|
322
322
|
className={cn(
|
|
323
323
|
"h-1.5 w-1.5 rounded-full",
|
|
324
324
|
uploadError
|
|
325
|
-
? "bg-
|
|
326
|
-
: "bg-
|
|
325
|
+
? "bg-destructive shadow-[0_0_6px_hsl(var(--destructive)/0.5)]"
|
|
326
|
+
: "bg-foreground shadow-[0_0_6px_hsl(var(--foreground)/0.35)]",
|
|
327
327
|
)}
|
|
328
328
|
/>
|
|
329
329
|
<span
|
|
330
330
|
className={cn(
|
|
331
331
|
"font-mono text-[9px] font-bold uppercase tracking-[0.1em]",
|
|
332
332
|
uploadError
|
|
333
|
-
? "text-
|
|
334
|
-
: "text-
|
|
333
|
+
? "text-destructive-muted-foreground"
|
|
334
|
+
: "text-muted-foreground",
|
|
335
335
|
)}
|
|
336
336
|
>
|
|
337
337
|
{uploadError ? "Error" : "AI"}
|
|
338
338
|
</span>
|
|
339
339
|
</div>
|
|
340
340
|
|
|
341
|
-
<div className="flex h-[7em] w-[7em] items-center justify-center overflow-hidden rounded-full border-4 border-
|
|
341
|
+
<div className="flex h-[7em] w-[7em] items-center justify-center overflow-hidden rounded-full border-4 border-primary bg-muted shadow-[0_4px_16px_rgba(248,120,37,0.24)] dark:shadow-[0_4px_16px_rgba(0,0,0,0.35)]">
|
|
342
342
|
<img
|
|
343
343
|
src={avatarUrl}
|
|
344
344
|
alt={workflow.name}
|
|
@@ -354,7 +354,7 @@ export const WorkflowCard = ({
|
|
|
354
354
|
<Button
|
|
355
355
|
variant="outline"
|
|
356
356
|
size="icon"
|
|
357
|
-
className="absolute right-10 top-0 h-8 w-8 border-
|
|
357
|
+
className="absolute right-10 top-0 h-8 w-8 border-success/40 bg-success-muted text-success-muted-foreground hover:bg-success/20"
|
|
358
358
|
aria-label={`Update ${workflow.name} to candidate version ${candidateUpdate.latestVersion}`}
|
|
359
359
|
data-card-action="true"
|
|
360
360
|
onClick={(event) => {
|
|
@@ -376,7 +376,7 @@ export const WorkflowCard = ({
|
|
|
376
376
|
<p>{candidateUpdate.latestSummary}</p>
|
|
377
377
|
) : null}
|
|
378
378
|
{candidateUpdate.firstMigration ? (
|
|
379
|
-
<p className="text-
|
|
379
|
+
<p className="text-warning-muted-foreground">
|
|
380
380
|
{candidateUpdate.firstMigration}
|
|
381
381
|
</p>
|
|
382
382
|
) : null}
|
|
@@ -490,7 +490,7 @@ export const WorkflowCard = ({
|
|
|
490
490
|
</DialogHeader>
|
|
491
491
|
|
|
492
492
|
{candidateUpdate.isMajor ? (
|
|
493
|
-
<div className="rounded-md border border-
|
|
493
|
+
<div className="rounded-md border border-warning/50 bg-warning-muted px-3 py-2 text-sm text-warning-muted-foreground">
|
|
494
494
|
This is a major candidate update. Review migration notes before
|
|
495
495
|
applying it to this colleague.
|
|
496
496
|
</div>
|
|
@@ -508,7 +508,7 @@ export const WorkflowCard = ({
|
|
|
508
508
|
</div>
|
|
509
509
|
<p className="mt-1 text-muted-foreground">{note.summary}</p>
|
|
510
510
|
{note.migration ? (
|
|
511
|
-
<p className="mt-2 text-
|
|
511
|
+
<p className="mt-2 text-warning-muted-foreground">
|
|
512
512
|
{note.migration}
|
|
513
513
|
</p>
|
|
514
514
|
) : null}
|