orcheo-studio 0.24.4 → 0.24.5
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-C-7b0zH4.js} +1 -1
- package/dist/assets/{architectureDiagram-VXUJARFQ-DugaTSY2.js → architectureDiagram-VXUJARFQ-Ciy8lV6J.js} +1 -1
- package/dist/assets/{blockDiagram-VD42YOAC-BH68wVtD.js → blockDiagram-VD42YOAC-dC88adQ_.js} +1 -1
- package/dist/assets/{c4Diagram-YG6GDRKO-V6Vqtp7B.js → c4Diagram-YG6GDRKO-BiHVvXEu.js} +1 -1
- package/dist/assets/channel-pJdT5i-M.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-CvbCAMNv.js → chunk-4BX2VUAB-C6V7Huup.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-h_T3kQh7.js → chunk-55IACEB6-DRmZnlbz.js} +1 -1
- package/dist/assets/{chunk-B4BG7PRW-Dm6dgbGh.js → chunk-B4BG7PRW-DwV5xRJr.js} +1 -1
- package/dist/assets/{chunk-DI55MBZ5-DSXFA4Fi.js → chunk-DI55MBZ5-BHsUZ0IJ.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DJDlfjt1.js → chunk-FMBD7UC4-6Q-Lz0AM.js} +1 -1
- package/dist/assets/{chunk-QN33PNHL-BnjUGeBL.js → chunk-QN33PNHL-BFy_ml8O.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BRudOghZ.js → chunk-QZHKN3VN-C1fN91FN.js} +1 -1
- package/dist/assets/{chunk-TZMSLE5B-CpxSpvqK.js → chunk-TZMSLE5B-FLRSQ_Fi.js} +1 -1
- package/dist/assets/classDiagram-2ON5EDUG-CNT-EGNU.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-CNT-EGNU.js +1 -0
- package/dist/assets/clone-BCi0j082.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-Dl80WZ_a.js → cose-bilkent-S5V4N54A-BSWP1X1D.js} +1 -1
- package/dist/assets/{dagre-6UL2VRFP-vw-vkw78.js → dagre-6UL2VRFP-CYoZFlcz.js} +1 -1
- package/dist/assets/{diagram-PSM6KHXK-B4MT5Nvf.js → diagram-PSM6KHXK-B8RGX0YL.js} +1 -1
- package/dist/assets/{diagram-QEK2KX5R-CMioZnRI.js → diagram-QEK2KX5R-ChWXJ8p_.js} +1 -1
- package/dist/assets/{diagram-S2PKOQOG-PJAE8YS0.js → diagram-S2PKOQOG-Dwic0Fjn.js} +1 -1
- package/dist/assets/{erDiagram-Q2GNP2WA-DCd-WouG.js → erDiagram-Q2GNP2WA-0NSDB0DX.js} +1 -1
- package/dist/assets/{flowDiagram-NV44I4VS-DS__-WT4.js → flowDiagram-NV44I4VS-DEoGWs9L.js} +1 -1
- package/dist/assets/{ganttDiagram-JELNMOA3-Btifh-31.js → ganttDiagram-JELNMOA3-DqzV4S6l.js} +1 -1
- package/dist/assets/{gitGraphDiagram-V2S2FVAM-BCyjopey.js → gitGraphDiagram-V2S2FVAM-B-EIQM_H.js} +1 -1
- package/dist/assets/{graph-6YBx8iQ0.js → graph-Ha1DTJNG.js} +1 -1
- package/dist/assets/{index-lkycEDHM.js → index-DTxvVcXp.js} +61 -61
- package/dist/assets/{infoDiagram-HS3SLOUP-CAsJ2eU1.js → infoDiagram-HS3SLOUP-C4_vpEkn.js} +1 -1
- package/dist/assets/{isUndefined-CcAlyXn3.js → isUndefined-DkuoqItu.js} +1 -1
- package/dist/assets/{journeyDiagram-XKPGCS4Q-Bi8XsX15.js → journeyDiagram-XKPGCS4Q-BxY4e6OZ.js} +1 -1
- package/dist/assets/{kanban-definition-3W4ZIXB7-PTAFVsDn.js → kanban-definition-3W4ZIXB7-BTX3_Q7O.js} +1 -1
- package/dist/assets/{layout-Do-GOFdW.js → layout-Dt1W0fl0.js} +1 -1
- package/dist/assets/{linear-CZlgolw0.js → linear-CjywSMUX.js} +1 -1
- package/dist/assets/{min-N15IYW1Y.js → min-Dblq6bty.js} +1 -1
- package/dist/assets/{mindmap-definition-VGOIOE7T-Dlgxv6Jo.js → mindmap-definition-VGOIOE7T-C5P7bAFm.js} +1 -1
- package/dist/assets/{pieDiagram-ADFJNKIX-Dr2AfIw5.js → pieDiagram-ADFJNKIX-BSMylwok.js} +1 -1
- package/dist/assets/{quadrantDiagram-AYHSOK5B-B8TPuvW6.js → quadrantDiagram-AYHSOK5B-C74B7bqa.js} +1 -1
- package/dist/assets/{requirementDiagram-UZGBJVZJ-CdD2oP1N.js → requirementDiagram-UZGBJVZJ-CvPzL8Le.js} +1 -1
- package/dist/assets/{sankeyDiagram-TZEHDZUN-XEMX6l5k.js → sankeyDiagram-TZEHDZUN-BFHm6g6t.js} +1 -1
- package/dist/assets/{sequenceDiagram-WL72ISMW-BlIKpBev.js → sequenceDiagram-WL72ISMW-e74mJ0xk.js} +1 -1
- package/dist/assets/{stateDiagram-FKZM4ZOC-D3KGQY6v.js → stateDiagram-FKZM4ZOC-VLQxNVA7.js} +1 -1
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Bbpq2t6B.js +1 -0
- package/dist/assets/{timeline-definition-IT6M3QCI-TkL4mz_f.js → timeline-definition-IT6M3QCI-oveNP_QD.js} +1 -1
- package/dist/assets/{treemap-GDKQZRPO-BDp_c1Vc.js → treemap-GDKQZRPO-Cowimd29.js} +1 -1
- package/dist/assets/{xychartDiagram-PRI3JC2R-D0dNQImL.js → xychartDiagram-PRI3JC2R-CP9Fm_aO.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/App.tsx +48 -44
- package/src/features/account/pages/workspace-members.tsx +58 -62
- package/src/features/auth/pages/login.tsx +3 -1
- package/src/features/chatkit/lib/public-chat-access.ts +13 -0
- package/src/features/chatkit/pages/public-chat.tsx +1 -12
- package/src/features/shared/components/chat-interface.types.ts +1 -5
- package/src/features/shared/components/top-navigation/version-status.tsx +27 -31
- package/src/features/workflow/components/panels/rjsf-cron-widget.tsx +35 -10
- 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/lib/workflow-execution-builders.ts +4 -4
- package/src/features/workflow/lib/workflow-execution.types.ts +2 -9
- 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/workflow-tab-content.tsx +3 -1
- 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/lib/api.ts +7 -2
- package/src/lib/workspace-routing.ts +1 -3
- 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/stateDiagram-v2-4FDKWEC3-Di3Nkv2n.js +0 -1
|
@@ -282,69 +282,65 @@ export default function WorkspaceMembers() {
|
|
|
282
282
|
</div>
|
|
283
283
|
)}
|
|
284
284
|
|
|
285
|
-
{canManage &&
|
|
286
|
-
|
|
287
|
-
<div className="
|
|
288
|
-
<
|
|
289
|
-
<h3 className="text-sm font-medium">Pending Invitations</h3>
|
|
290
|
-
</div>
|
|
291
|
-
<Table>
|
|
292
|
-
<TableHeader>
|
|
293
|
-
<TableRow>
|
|
294
|
-
<TableHead>Email</TableHead>
|
|
295
|
-
<TableHead>Role</TableHead>
|
|
296
|
-
<TableHead>Expires</TableHead>
|
|
297
|
-
<TableHead className="text-right">Actions</TableHead>
|
|
298
|
-
</TableRow>
|
|
299
|
-
</TableHeader>
|
|
300
|
-
<TableBody>
|
|
301
|
-
{invitations
|
|
302
|
-
.filter((i) => i.status === "pending")
|
|
303
|
-
.map((invitation) => {
|
|
304
|
-
const isRevoking =
|
|
305
|
-
revokingInvitationId === invitation.id;
|
|
306
|
-
const isExpired =
|
|
307
|
-
new Date(invitation.expires_at) < new Date();
|
|
308
|
-
return (
|
|
309
|
-
<TableRow
|
|
310
|
-
key={invitation.id}
|
|
311
|
-
className={isExpired ? "opacity-60" : undefined}
|
|
312
|
-
>
|
|
313
|
-
<TableCell className="text-sm">
|
|
314
|
-
{invitation.email}
|
|
315
|
-
</TableCell>
|
|
316
|
-
<TableCell className="capitalize">
|
|
317
|
-
{invitation.role}
|
|
318
|
-
</TableCell>
|
|
319
|
-
<TableCell className="text-sm text-muted-foreground">
|
|
320
|
-
{isExpired ? (
|
|
321
|
-
<span className="text-destructive">Expired</span>
|
|
322
|
-
) : (
|
|
323
|
-
new Date(
|
|
324
|
-
invitation.expires_at,
|
|
325
|
-
).toLocaleDateString()
|
|
326
|
-
)}
|
|
327
|
-
</TableCell>
|
|
328
|
-
<TableCell className="text-right">
|
|
329
|
-
<Button
|
|
330
|
-
variant="ghost"
|
|
331
|
-
size="sm"
|
|
332
|
-
className="text-destructive hover:text-destructive"
|
|
333
|
-
onClick={() =>
|
|
334
|
-
void handleRevokeInvitation(invitation.id)
|
|
335
|
-
}
|
|
336
|
-
disabled={isRevoking}
|
|
337
|
-
>
|
|
338
|
-
{isRevoking ? "Revoking..." : "Revoke"}
|
|
339
|
-
</Button>
|
|
340
|
-
</TableCell>
|
|
341
|
-
</TableRow>
|
|
342
|
-
);
|
|
343
|
-
})}
|
|
344
|
-
</TableBody>
|
|
345
|
-
</Table>
|
|
285
|
+
{canManage && invitations.some((i) => i.status === "pending") && (
|
|
286
|
+
<div className="rounded-lg border">
|
|
287
|
+
<div className="border-b px-4 py-3">
|
|
288
|
+
<h3 className="text-sm font-medium">Pending Invitations</h3>
|
|
346
289
|
</div>
|
|
347
|
-
|
|
290
|
+
<Table>
|
|
291
|
+
<TableHeader>
|
|
292
|
+
<TableRow>
|
|
293
|
+
<TableHead>Email</TableHead>
|
|
294
|
+
<TableHead>Role</TableHead>
|
|
295
|
+
<TableHead>Expires</TableHead>
|
|
296
|
+
<TableHead className="text-right">Actions</TableHead>
|
|
297
|
+
</TableRow>
|
|
298
|
+
</TableHeader>
|
|
299
|
+
<TableBody>
|
|
300
|
+
{invitations
|
|
301
|
+
.filter((i) => i.status === "pending")
|
|
302
|
+
.map((invitation) => {
|
|
303
|
+
const isRevoking = revokingInvitationId === invitation.id;
|
|
304
|
+
const isExpired =
|
|
305
|
+
new Date(invitation.expires_at) < new Date();
|
|
306
|
+
return (
|
|
307
|
+
<TableRow
|
|
308
|
+
key={invitation.id}
|
|
309
|
+
className={isExpired ? "opacity-60" : undefined}
|
|
310
|
+
>
|
|
311
|
+
<TableCell className="text-sm">
|
|
312
|
+
{invitation.email}
|
|
313
|
+
</TableCell>
|
|
314
|
+
<TableCell className="capitalize">
|
|
315
|
+
{invitation.role}
|
|
316
|
+
</TableCell>
|
|
317
|
+
<TableCell className="text-sm text-muted-foreground">
|
|
318
|
+
{isExpired ? (
|
|
319
|
+
<span className="text-destructive">Expired</span>
|
|
320
|
+
) : (
|
|
321
|
+
new Date(invitation.expires_at).toLocaleDateString()
|
|
322
|
+
)}
|
|
323
|
+
</TableCell>
|
|
324
|
+
<TableCell className="text-right">
|
|
325
|
+
<Button
|
|
326
|
+
variant="ghost"
|
|
327
|
+
size="sm"
|
|
328
|
+
className="text-destructive hover:text-destructive"
|
|
329
|
+
onClick={() =>
|
|
330
|
+
void handleRevokeInvitation(invitation.id)
|
|
331
|
+
}
|
|
332
|
+
disabled={isRevoking}
|
|
333
|
+
>
|
|
334
|
+
{isRevoking ? "Revoking..." : "Revoke"}
|
|
335
|
+
</Button>
|
|
336
|
+
</TableCell>
|
|
337
|
+
</TableRow>
|
|
338
|
+
);
|
|
339
|
+
})}
|
|
340
|
+
</TableBody>
|
|
341
|
+
</Table>
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
348
344
|
|
|
349
345
|
{isForbidden ? null : isLoading ? (
|
|
350
346
|
<p className="text-sm text-muted-foreground">Loading members...</p>
|
|
@@ -73,7 +73,9 @@ export default function Login() {
|
|
|
73
73
|
setStage("sent");
|
|
74
74
|
} catch (err) {
|
|
75
75
|
setError(
|
|
76
|
-
err instanceof Error
|
|
76
|
+
err instanceof Error
|
|
77
|
+
? err.message
|
|
78
|
+
: "Unable to send the sign-in email.",
|
|
77
79
|
);
|
|
78
80
|
} finally {
|
|
79
81
|
setBusy(false);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PublicChatHttpError } from "@features/chatkit/lib/chatkit-client";
|
|
2
|
+
|
|
3
|
+
export const getPublicChatAccessErrorMessage = (
|
|
4
|
+
error: Pick<PublicChatHttpError, "status" | "code">,
|
|
5
|
+
): string => {
|
|
6
|
+
if (error.code === "chatkit.auth.workspace_mismatch") {
|
|
7
|
+
return "Your account is signed in, but it is not a member of this workflow workspace. Switch workspaces or ask the owner to add you.";
|
|
8
|
+
}
|
|
9
|
+
if (error.status === 403) {
|
|
10
|
+
return "You do not have permission to use this workflow. Ask the owner to confirm your workspace access.";
|
|
11
|
+
}
|
|
12
|
+
return "You do not have access to this workflow yet. Ask the owner to confirm it is still published.";
|
|
13
|
+
};
|
|
@@ -19,6 +19,7 @@ import type { PublicWorkflowMetadata } from "@features/workflow/lib/workflow-sto
|
|
|
19
19
|
import { PublicChatWidget } from "@features/chatkit/components/public-chat-widget";
|
|
20
20
|
import { PublicChatErrorBoundary } from "@features/chatkit/components/public-chat-error-boundary";
|
|
21
21
|
import type { PublicChatHttpError } from "@features/chatkit/lib/chatkit-client";
|
|
22
|
+
import { getPublicChatAccessErrorMessage } from "@features/chatkit/lib/public-chat-access";
|
|
22
23
|
|
|
23
24
|
type ColorScheme = "light" | "dark";
|
|
24
25
|
|
|
@@ -27,18 +28,6 @@ type WorkflowState =
|
|
|
27
28
|
| { status: "error"; message: string }
|
|
28
29
|
| { status: "ready"; workflow: PublicWorkflowMetadata };
|
|
29
30
|
|
|
30
|
-
export const getPublicChatAccessErrorMessage = (
|
|
31
|
-
error: Pick<PublicChatHttpError, "status" | "code">,
|
|
32
|
-
): string => {
|
|
33
|
-
if (error.code === "chatkit.auth.workspace_mismatch") {
|
|
34
|
-
return "Your account is signed in, but it is not a member of this workflow workspace. Switch workspaces or ask the owner to add you.";
|
|
35
|
-
}
|
|
36
|
-
if (error.status === 403) {
|
|
37
|
-
return "You do not have permission to use this workflow. Ask the owner to confirm your workspace access.";
|
|
38
|
-
}
|
|
39
|
-
return "You do not have access to this workflow yet. Ask the owner to confirm it is still published.";
|
|
40
|
-
};
|
|
41
|
-
|
|
42
31
|
const sanitizeWorkflowNameForEmail = (value: string): string =>
|
|
43
32
|
value
|
|
44
33
|
.replace(/[\r\n]+/g, " ")
|
|
@@ -27,11 +27,7 @@ export interface ChatInterfaceProps {
|
|
|
27
27
|
isMinimizable?: boolean;
|
|
28
28
|
isClosable?: boolean;
|
|
29
29
|
position?:
|
|
30
|
-
| "bottom-right"
|
|
31
|
-
| "bottom-left"
|
|
32
|
-
| "top-right"
|
|
33
|
-
| "top-left"
|
|
34
|
-
| "center";
|
|
30
|
+
"bottom-right" | "bottom-left" | "top-right" | "top-left" | "center";
|
|
35
31
|
triggerButton?: React.ReactNode;
|
|
36
32
|
user: ChatParticipant;
|
|
37
33
|
ai: ChatParticipant;
|
|
@@ -44,17 +44,6 @@ const parseCache = (raw: string | null): SystemInfoCachePayload | null => {
|
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
-
const shouldRefresh = (cache: SystemInfoCachePayload | null): boolean => {
|
|
48
|
-
if (!cache) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
const lastCheckedAt = Date.parse(cache.checkedAt);
|
|
52
|
-
if (Number.isNaN(lastCheckedAt)) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
return Date.now() - lastCheckedAt >= UPDATE_CHECK_TTL_MS;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
47
|
const parseSemver = (value: string | null | undefined): ParsedSemver | null => {
|
|
59
48
|
if (!value) {
|
|
60
49
|
return null;
|
|
@@ -152,7 +141,10 @@ const isDismissed = (): boolean => {
|
|
|
152
141
|
};
|
|
153
142
|
|
|
154
143
|
export default function VersionStatus() {
|
|
155
|
-
const [
|
|
144
|
+
const [cachedInfo, setCachedInfo] = useState<SystemInfoResponse | null>(null);
|
|
145
|
+
const [liveBackendVersion, setLiveBackendVersion] = useState<string | null>(
|
|
146
|
+
null,
|
|
147
|
+
);
|
|
156
148
|
const [dismissedReminder, setDismissedReminder] = useState<boolean>(() =>
|
|
157
149
|
isDismissed(),
|
|
158
150
|
);
|
|
@@ -163,11 +155,12 @@ export default function VersionStatus() {
|
|
|
163
155
|
window.localStorage.getItem(UPDATE_CHECK_CACHE_KEY),
|
|
164
156
|
);
|
|
165
157
|
if (cache) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
158
|
+
setCachedInfo(cache.payload);
|
|
159
|
+
setLiveBackendVersion(cache.payload.backend.current_version);
|
|
160
|
+
const cacheAge = Date.now() - Date.parse(cache.checkedAt);
|
|
161
|
+
if (cacheAge < UPDATE_CHECK_TTL_MS) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
171
164
|
}
|
|
172
165
|
|
|
173
166
|
let active = true;
|
|
@@ -176,7 +169,8 @@ export default function VersionStatus() {
|
|
|
176
169
|
if (!active) {
|
|
177
170
|
return;
|
|
178
171
|
}
|
|
179
|
-
|
|
172
|
+
setLiveBackendVersion(payload.backend.current_version);
|
|
173
|
+
setCachedInfo(payload);
|
|
180
174
|
const cachePayload: SystemInfoCachePayload = {
|
|
181
175
|
checkedAt: new Date().toISOString(),
|
|
182
176
|
payload,
|
|
@@ -196,39 +190,41 @@ export default function VersionStatus() {
|
|
|
196
190
|
}, []);
|
|
197
191
|
|
|
198
192
|
const studioUpdateAvailable = useMemo(() => {
|
|
199
|
-
if (!
|
|
193
|
+
if (!cachedInfo) {
|
|
200
194
|
return false;
|
|
201
195
|
}
|
|
202
|
-
if (
|
|
196
|
+
if (cachedInfo.studio.update_available) {
|
|
203
197
|
return true;
|
|
204
198
|
}
|
|
205
|
-
return compareSemver(studioVersion,
|
|
206
|
-
}, [studioVersion,
|
|
199
|
+
return compareSemver(studioVersion, cachedInfo.studio.latest_version) < 0;
|
|
200
|
+
}, [studioVersion, cachedInfo]);
|
|
207
201
|
|
|
208
202
|
const showReminder =
|
|
209
|
-
((
|
|
203
|
+
((cachedInfo?.backend.update_available ?? false) ||
|
|
210
204
|
studioUpdateAvailable) &&
|
|
211
205
|
!dismissedReminder;
|
|
212
206
|
|
|
213
207
|
const versionSummary = useMemo(() => {
|
|
214
|
-
const backend =
|
|
208
|
+
const backend = liveBackendVersion ?? "unknown";
|
|
215
209
|
return `Orcheo ${studioVersion} · Backend ${backend}`;
|
|
216
|
-
}, [studioVersion,
|
|
210
|
+
}, [studioVersion, liveBackendVersion]);
|
|
217
211
|
|
|
218
212
|
const updateLines = useMemo(() => {
|
|
219
|
-
if (!
|
|
213
|
+
if (!cachedInfo) return [];
|
|
220
214
|
const lines: string[] = [];
|
|
221
|
-
if (
|
|
215
|
+
if (cachedInfo.backend.update_available) {
|
|
222
216
|
lines.push(
|
|
223
|
-
`Backend: ${
|
|
217
|
+
`Backend: ${cachedInfo.backend.current_version} → ${cachedInfo.backend.latest_version}`,
|
|
224
218
|
);
|
|
225
219
|
}
|
|
226
220
|
if (studioUpdateAvailable) {
|
|
227
|
-
const studioCurrent =
|
|
228
|
-
lines.push(
|
|
221
|
+
const studioCurrent = cachedInfo.studio.current_version ?? studioVersion;
|
|
222
|
+
lines.push(
|
|
223
|
+
`Orcheo: ${studioCurrent} → ${cachedInfo.studio.latest_version}`,
|
|
224
|
+
);
|
|
229
225
|
}
|
|
230
226
|
return lines;
|
|
231
|
-
}, [studioUpdateAvailable, studioVersion,
|
|
227
|
+
}, [studioUpdateAvailable, studioVersion, cachedInfo]);
|
|
232
228
|
|
|
233
229
|
const dismissUpdateReminder = () => {
|
|
234
230
|
if (typeof window === "undefined") {
|
|
@@ -72,11 +72,20 @@ const clampInt = (value: string, max: number, fallback: number) => {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/** Parse a single cron field's first numeric token, falling back to `fallback`. */
|
|
75
|
-
const parseField = (
|
|
76
|
-
field
|
|
75
|
+
const parseField = (
|
|
76
|
+
field: string | undefined,
|
|
77
|
+
max: number,
|
|
78
|
+
fallback: number,
|
|
79
|
+
) =>
|
|
80
|
+
field && field !== "*"
|
|
81
|
+
? clampInt(field.split(/[,/-]/)[0], max, fallback)
|
|
82
|
+
: fallback;
|
|
77
83
|
|
|
78
84
|
/** Extract the step N from a `*\/N` field (clamped to `max`), or undefined if it isn't a step. */
|
|
79
|
-
const parseStep = (
|
|
85
|
+
const parseStep = (
|
|
86
|
+
field: string | undefined,
|
|
87
|
+
max: number,
|
|
88
|
+
): number | undefined => {
|
|
80
89
|
const match = /^\*\/(\d+)$/.exec(field ?? "");
|
|
81
90
|
return match ? clampInt(match[1], max, 1) || 1 : undefined;
|
|
82
91
|
};
|
|
@@ -98,7 +107,9 @@ const parseDaysOfWeek = (field: string | undefined): number[] => {
|
|
|
98
107
|
return Array.from(new Set(days));
|
|
99
108
|
};
|
|
100
109
|
|
|
101
|
-
const parseCron = (
|
|
110
|
+
const parseCron = (
|
|
111
|
+
value: string | undefined,
|
|
112
|
+
): { freq: Frequency; parts: CronParts } => {
|
|
102
113
|
const [minute, hour, dom, , dow] = (value ?? "").trim().split(/\s+/);
|
|
103
114
|
const minuteStep = parseStep(minute, 59);
|
|
104
115
|
const hourStep = parseStep(hour, 23);
|
|
@@ -117,13 +128,15 @@ const parseCron = (value: string | undefined): { freq: Frequency; parts: CronPar
|
|
|
117
128
|
// `*/30 * * * *` (step) or `* * * * *` (every minute) → minute interval.
|
|
118
129
|
else if (minuteStep !== undefined || minute === "*") freq = "minute";
|
|
119
130
|
// `M */2 * * *` (step) or `M * * * *` (every hour) → hour interval.
|
|
120
|
-
else if (hourStep !== undefined || hour === "*" || hour === undefined)
|
|
131
|
+
else if (hourStep !== undefined || hour === "*" || hour === undefined)
|
|
132
|
+
freq = "hour";
|
|
121
133
|
|
|
122
134
|
return { freq, parts };
|
|
123
135
|
};
|
|
124
136
|
|
|
125
137
|
const buildCron = (freq: Frequency, parts: CronParts): string => {
|
|
126
|
-
const { interval, hourInterval, minute, hour, dayOfMonth, daysOfWeek } =
|
|
138
|
+
const { interval, hourInterval, minute, hour, dayOfMonth, daysOfWeek } =
|
|
139
|
+
parts;
|
|
127
140
|
switch (freq) {
|
|
128
141
|
case "minute":
|
|
129
142
|
// `*/1` is just "every minute"; emit the canonical wildcard form.
|
|
@@ -136,7 +149,9 @@ const buildCron = (freq: Frequency, parts: CronParts): string => {
|
|
|
136
149
|
case "day":
|
|
137
150
|
return `${minute} ${hour} * * *`;
|
|
138
151
|
case "week": {
|
|
139
|
-
const dow = daysOfWeek.length
|
|
152
|
+
const dow = daysOfWeek.length
|
|
153
|
+
? [...daysOfWeek].sort((a, b) => a - b).join(",")
|
|
154
|
+
: "*";
|
|
140
155
|
return `${minute} ${hour} * * ${dow}`;
|
|
141
156
|
}
|
|
142
157
|
case "month":
|
|
@@ -165,7 +180,11 @@ const canonicalCron = (value: string | undefined): string | null => {
|
|
|
165
180
|
if (/[a-z]/i.test(fields.join(""))) return null;
|
|
166
181
|
const days = parseDaysOfWeek(dow);
|
|
167
182
|
const dowField =
|
|
168
|
-
dow === "*"
|
|
183
|
+
dow === "*"
|
|
184
|
+
? "*"
|
|
185
|
+
: days.length
|
|
186
|
+
? [...days].sort((a, b) => a - b).join(",")
|
|
187
|
+
: dow;
|
|
169
188
|
return [minute, hour, dom, month, dowField].join(" ");
|
|
170
189
|
};
|
|
171
190
|
|
|
@@ -292,7 +311,9 @@ function CronWidget(props: WidgetProps) {
|
|
|
292
311
|
|
|
293
312
|
{(freq === "minute" || freq === "hour") && (
|
|
294
313
|
<Select
|
|
295
|
-
value={String(
|
|
314
|
+
value={String(
|
|
315
|
+
freq === "minute" ? parts.interval : parts.hourInterval,
|
|
316
|
+
)}
|
|
296
317
|
onValueChange={(v) =>
|
|
297
318
|
freq === "minute"
|
|
298
319
|
? update("minute", { ...parts, interval: Number(v) })
|
|
@@ -343,7 +364,11 @@ function CronWidget(props: WidgetProps) {
|
|
|
343
364
|
"flex h-9 min-w-[6rem] items-center justify-between gap-2 whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
344
365
|
)}
|
|
345
366
|
>
|
|
346
|
-
<span
|
|
367
|
+
<span
|
|
368
|
+
className={cn(
|
|
369
|
+
parts.daysOfWeek.length === 0 && "text-muted-foreground",
|
|
370
|
+
)}
|
|
371
|
+
>
|
|
347
372
|
{selectedDaysLabel}
|
|
348
373
|
</span>
|
|
349
374
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
@@ -31,16 +31,7 @@ export type ColorVariant =
|
|
|
31
31
|
| "gray";
|
|
32
32
|
|
|
33
33
|
export type ComponentSize =
|
|
34
|
-
| "
|
|
35
|
-
| "5"
|
|
36
|
-
| "6"
|
|
37
|
-
| "7"
|
|
38
|
-
| "8"
|
|
39
|
-
| "9"
|
|
40
|
-
| "10"
|
|
41
|
-
| "11"
|
|
42
|
-
| "12"
|
|
43
|
-
| "16";
|
|
34
|
+
"4" | "5" | "6" | "7" | "8" | "9" | "10" | "11" | "12" | "16";
|
|
44
35
|
|
|
45
36
|
// CONSTANTS
|
|
46
37
|
|
|
@@ -50,10 +50,10 @@ const extractVersionRecord = (
|
|
|
50
50
|
lookup: WorkflowLookup,
|
|
51
51
|
) => {
|
|
52
52
|
const inputs = history.inputs ?? {};
|
|
53
|
-
const metadata = (inputs.metadata ??
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
const metadata = (inputs.metadata ??
|
|
54
|
+
inputs.workflow ??
|
|
55
|
+
inputs.canvas ??
|
|
56
|
+
{}) as Record<string, unknown>;
|
|
57
57
|
const versionIdRaw = metadata?.workflow_version_id;
|
|
58
58
|
if (typeof versionIdRaw === "string" && versionIdRaw) {
|
|
59
59
|
return lookup.versions.get(versionIdRaw);
|
|
@@ -31,17 +31,10 @@ export type WorkflowLookup = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
export type WorkflowExecutionStatus =
|
|
34
|
-
| "
|
|
35
|
-
| "success"
|
|
36
|
-
| "failed"
|
|
37
|
-
| "partial";
|
|
34
|
+
"running" | "success" | "failed" | "partial";
|
|
38
35
|
|
|
39
36
|
export type WorkflowExecutionNodeStatus =
|
|
40
|
-
| "
|
|
41
|
-
| "running"
|
|
42
|
-
| "success"
|
|
43
|
-
| "error"
|
|
44
|
-
| "warning";
|
|
37
|
+
"idle" | "running" | "success" | "error" | "warning";
|
|
45
38
|
|
|
46
39
|
export interface WorkflowExecutionNode {
|
|
47
40
|
id: string;
|
|
@@ -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
|
}
|
|
@@ -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>
|