@revenexx/studio-integrations 1.1.1 → 1.3.0
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/CHANGELOG.md +638 -0
- package/README.md +3 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +45 -19
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
- package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -2
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +2 -1
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
- package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
- package/dist/runtime/components/Integrations/RunsTable.vue +129 -18
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +78 -30
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +7 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +25 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -0
- package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
- package/dist/runtime/data/nodeVersionMove.js +65 -0
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +79 -12
- package/dist/runtime/i18n/messages/en.json +79 -12
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +55 -8
- package/dist/runtime/pages/integrations/runs.vue +116 -7
- package/dist/runtime/pages/integrations/secrets.vue +2 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
- package/dist/runtime/pages/integrations/templates/index.vue +18 -15
- package/dist/runtime/pages/integrations/workflows/[id].vue +169 -24
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
- package/dist/runtime/types/integrations.d.ts +49 -1
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/buildSettle.d.ts +68 -0
- package/dist/runtime/utils/buildSettle.js +29 -0
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/settingCondition.d.ts +48 -0
- package/dist/runtime/utils/settingCondition.js +84 -0
- package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
- package/dist/runtime/utils/stateNamespaceName.js +8 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -146,20 +146,20 @@ function spliceNode() {
|
|
|
146
146
|
>
|
|
147
147
|
<button
|
|
148
148
|
type="button"
|
|
149
|
-
class="flex size-
|
|
149
|
+
class="flex size-6 items-center justify-center rounded text-muted-foreground transition hover:bg-muted/60 hover:text-foreground"
|
|
150
150
|
:title="t('integration.canvas.edge.action.insertNode')"
|
|
151
151
|
@click.stop="spliceNode"
|
|
152
152
|
>
|
|
153
|
-
<Plus :size="
|
|
153
|
+
<Plus :size="13" />
|
|
154
154
|
</button>
|
|
155
155
|
<div class="h-3.5 w-px bg-black/10" />
|
|
156
156
|
<button
|
|
157
157
|
type="button"
|
|
158
|
-
class="flex size-
|
|
158
|
+
class="flex size-6 items-center justify-center rounded text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
159
159
|
:title="t('integration.canvas.edge.action.delete')"
|
|
160
160
|
@click.stop="removeEdge"
|
|
161
161
|
>
|
|
162
|
-
<TrashBin :size="
|
|
162
|
+
<TrashBin :size="13" />
|
|
163
163
|
</button>
|
|
164
164
|
</div>
|
|
165
165
|
</EdgeLabelRenderer>
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
DropdownMenuTrigger,
|
|
29
29
|
TooltipProvider
|
|
30
30
|
} from "@revenexx/studio";
|
|
31
|
-
import { Braces, EllipsisVertical, GripVertical, Group as GroupIcon, Hand, Map as MapIcon, Minus, MousePointer2, Plus, Settings2, StickyNote } from "lucide-vue-next";
|
|
31
|
+
import { Braces, Database, EllipsisVertical, GripVertical, Group as GroupIcon, Hand, Map as MapIcon, Minus, MousePointer2, Plus, Settings2, StickyNote } from "lucide-vue-next";
|
|
32
32
|
import "@vue-flow/core/dist/style.css";
|
|
33
33
|
import "@vue-flow/core/dist/theme-default.css";
|
|
34
34
|
import "@vue-flow/minimap/dist/style.css";
|
|
@@ -40,6 +40,7 @@ import WorkflowTriggerView from "./WorkflowTriggerView.vue";
|
|
|
40
40
|
import StickyNoteView from "./StickyNoteView.vue";
|
|
41
41
|
import GroupFrameView from "./GroupFrameView.vue";
|
|
42
42
|
import WorkflowContextMenu, {} from "./WorkflowContextMenu.vue";
|
|
43
|
+
import WorkflowStateNamespacesDialog from "./WorkflowStateNamespacesDialog.vue";
|
|
43
44
|
import WorkflowVariablesDialog from "./WorkflowVariablesDialog.vue";
|
|
44
45
|
import { useCanvasShortcuts } from "../../composables/useCanvasShortcuts";
|
|
45
46
|
import { usePaletteLayout } from "../../composables/usePaletteLayout";
|
|
@@ -50,7 +51,10 @@ import { layoutWorkflowNodes } from "./workflowLayout";
|
|
|
50
51
|
import { TRIGGER_TEMPLATES, findManifest } from "../../data/integrationNodeCatalog";
|
|
51
52
|
import { isTerminalManifest, portsForNode } from "../../data/nodeInputData";
|
|
52
53
|
import { computeLoopBodies, iteratorNodeIds, nodeLoopData } from "../../data/loopBody";
|
|
53
|
-
import { portFromHandleId } from "../../data/handleId";
|
|
54
|
+
import { inputHandleId, outputHandleId, portFromHandleId } from "../../data/handleId";
|
|
55
|
+
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
56
|
+
import { deepClone } from "../../utils/deepClone";
|
|
57
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
54
58
|
import { groupFindingsByNode } from "../../utils/validationFindings";
|
|
55
59
|
import { reorderBranchSubtrees } from "../../data/branchReorder";
|
|
56
60
|
import { findingsByNodeField } from "../../utils/validationFindings";
|
|
@@ -65,14 +69,11 @@ const props = defineProps({
|
|
|
65
69
|
const emit = defineEmits(["change", "run", "toggleActive", "openSettings"]);
|
|
66
70
|
const t = useIntegrationsT();
|
|
67
71
|
const { keys: secretKeys } = useIntegrationsSecrets();
|
|
68
|
-
const blob = ref(
|
|
72
|
+
const blob = ref(deepClone(props.draft ?? props.workflow.blob));
|
|
69
73
|
watch(() => props.workflow.id, () => {
|
|
70
|
-
blob.value =
|
|
74
|
+
blob.value = deepClone(props.workflow.blob);
|
|
71
75
|
syncFromBlob();
|
|
72
76
|
});
|
|
73
|
-
function clone(v) {
|
|
74
|
-
return JSON.parse(JSON.stringify(v));
|
|
75
|
-
}
|
|
76
77
|
const vfNodes = ref([]);
|
|
77
78
|
const vfEdges = ref([]);
|
|
78
79
|
const flow = useVueFlow();
|
|
@@ -80,7 +81,7 @@ const invalidNodeMessages = computed(() => findingsByNodeField(props.workflow.wa
|
|
|
80
81
|
const dynamicOutputs = ref({});
|
|
81
82
|
const invalidNodeFields = computed(() => {
|
|
82
83
|
const out = {};
|
|
83
|
-
for (const g of groupFindingsByNode(props.workflow.warnings ?? [], props.workflow.blob.nodes, () => "")) {
|
|
84
|
+
for (const g of groupFindingsByNode(props.workflow.warnings ?? [], props.workflow.blob.nodes, () => "", (_n, key) => key)) {
|
|
84
85
|
if (g.nodeId) out[g.nodeId] = g.fields;
|
|
85
86
|
}
|
|
86
87
|
return out;
|
|
@@ -160,9 +161,6 @@ function nodePosition(id) {
|
|
|
160
161
|
function pickSourceSide(_sourceId, _targetId) {
|
|
161
162
|
return "bottom";
|
|
162
163
|
}
|
|
163
|
-
function oppositeSide(s) {
|
|
164
|
-
return s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
165
|
-
}
|
|
166
164
|
function stickyArea(s) {
|
|
167
165
|
return (s.size?.width ?? 240) * (s.size?.height ?? 160);
|
|
168
166
|
}
|
|
@@ -252,6 +250,7 @@ function markBorn(...ids) {
|
|
|
252
250
|
function bornClass(id) {
|
|
253
251
|
return bornIds.value.has(id) ? "node-pop-in" : void 0;
|
|
254
252
|
}
|
|
253
|
+
const NODE_FALLBACK_POSITION = { x: 240, y: 80 };
|
|
255
254
|
function syncFromBlob() {
|
|
256
255
|
const loops = nodeLoopData(blob.value.nodes, blob.value.edges);
|
|
257
256
|
const stickies = [...blob.value.stickies ?? []].sort((a, b) => stickyArea(b) - stickyArea(a));
|
|
@@ -304,7 +303,7 @@ function syncFromBlob() {
|
|
|
304
303
|
...blob.value.nodes.filter((n) => !collapsedBy.has(n.id)).map((n) => ({
|
|
305
304
|
id: n.id,
|
|
306
305
|
type: "workflow",
|
|
307
|
-
position: n.position ??
|
|
306
|
+
position: n.position ?? NODE_FALLBACK_POSITION,
|
|
308
307
|
class: bornClass(n.id),
|
|
309
308
|
data: {
|
|
310
309
|
...n,
|
|
@@ -330,8 +329,8 @@ function syncFromBlob() {
|
|
|
330
329
|
...vf,
|
|
331
330
|
source,
|
|
332
331
|
target,
|
|
333
|
-
sourceHandle: srcGroup ? "
|
|
334
|
-
targetHandle: tgtGroup ? "
|
|
332
|
+
sourceHandle: srcGroup ? outputHandleId("default", "bottom") : vf.sourceHandle,
|
|
333
|
+
targetHandle: tgtGroup ? inputHandleId("top") : vf.targetHandle
|
|
335
334
|
});
|
|
336
335
|
}
|
|
337
336
|
const bySource = /* @__PURE__ */ new Map();
|
|
@@ -355,15 +354,15 @@ function makeVfEdge(id, sourceId, targetId, output) {
|
|
|
355
354
|
const manifest = wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
356
355
|
const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false"].includes(output);
|
|
357
356
|
const srcSide = pickSourceSide(sourceId, targetId);
|
|
358
|
-
const tgtSide =
|
|
357
|
+
const tgtSide = OPPOSITE_SIDE[srcSide];
|
|
359
358
|
const order = orderedOutputPorts(sourceId);
|
|
360
359
|
const portIndex = order.indexOf(output);
|
|
361
360
|
return {
|
|
362
361
|
id,
|
|
363
362
|
source: sourceId,
|
|
364
363
|
target: targetId,
|
|
365
|
-
sourceHandle:
|
|
366
|
-
targetHandle:
|
|
364
|
+
sourceHandle: outputHandleId(output, srcSide),
|
|
365
|
+
targetHandle: inputHandleId(tgtSide),
|
|
367
366
|
type: "flow",
|
|
368
367
|
data: {
|
|
369
368
|
kind,
|
|
@@ -561,7 +560,7 @@ function removeBlobNode(id) {
|
|
|
561
560
|
push();
|
|
562
561
|
}
|
|
563
562
|
function push() {
|
|
564
|
-
emit("change",
|
|
563
|
+
emit("change", deepClone(blob.value));
|
|
565
564
|
}
|
|
566
565
|
const STICKY_DEFAULT_SIZE = { width: 240, height: 160 };
|
|
567
566
|
function updateSticky(id, patch) {
|
|
@@ -572,6 +571,18 @@ function updateSticky(id, patch) {
|
|
|
572
571
|
push();
|
|
573
572
|
}
|
|
574
573
|
const variablesOpen = ref(false);
|
|
574
|
+
const stateOpen = ref(false);
|
|
575
|
+
const stateCount = computed(() => (blob.value.state ?? []).length);
|
|
576
|
+
function updateStateNamespaces(next) {
|
|
577
|
+
blob.value.state = next.length ? next : void 0;
|
|
578
|
+
push();
|
|
579
|
+
}
|
|
580
|
+
function declareStateNamespace(namespace, role, visibility) {
|
|
581
|
+
const declared = blob.value.state ?? [];
|
|
582
|
+
if (declared.some((n) => n.namespace === namespace)) return;
|
|
583
|
+
blob.value.state = [...declared, { namespace, role, visibility }];
|
|
584
|
+
push();
|
|
585
|
+
}
|
|
575
586
|
const variableCount = computed(() => Object.keys(blob.value.variables ?? {}).length);
|
|
576
587
|
function updateVariables(next) {
|
|
577
588
|
blob.value.variables = Object.keys(next).length ? next : void 0;
|
|
@@ -622,7 +633,7 @@ function duplicateBlobNodes(ids) {
|
|
|
622
633
|
src.forEach((n, i) => idMap.set(n.id, `n-${stamp}${i}`));
|
|
623
634
|
const offset = 40;
|
|
624
635
|
const copies = src.map((n) => ({
|
|
625
|
-
...
|
|
636
|
+
...deepClone(n),
|
|
626
637
|
id: idMap.get(n.id),
|
|
627
638
|
position: { x: (n.position?.x ?? 0) + offset, y: (n.position?.y ?? 0) + offset }
|
|
628
639
|
}));
|
|
@@ -661,12 +672,10 @@ async function copyNodes(ids) {
|
|
|
661
672
|
if (!nodes.length) return;
|
|
662
673
|
const inner = new Set(nodes.map((n) => n.id));
|
|
663
674
|
const edges = blob.value.edges.filter((e) => inner.has(e.from.nodeId) && inner.has(e.to.nodeId));
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
toast.error(t("integration.editor.clipboard.error.write"));
|
|
669
|
-
}
|
|
675
|
+
await copyToClipboard(JSON.stringify({ kind: CLIP_KIND, version: 1, nodes, edges }, null, 2), {
|
|
676
|
+
copied: t("integration.editor.clipboard.toast.copied", nodes.length),
|
|
677
|
+
failed: t("integration.editor.clipboard.error.write")
|
|
678
|
+
});
|
|
670
679
|
}
|
|
671
680
|
async function pasteNodes(at) {
|
|
672
681
|
let text = "";
|
|
@@ -695,7 +704,7 @@ async function pasteNodes(at) {
|
|
|
695
704
|
const dx = at ? Math.round(at.x - minX) : 40;
|
|
696
705
|
const dy = at ? Math.round(at.y - minY) : 40;
|
|
697
706
|
blob.value.nodes.push(...payload.nodes.map((n) => ({
|
|
698
|
-
...
|
|
707
|
+
...deepClone(n),
|
|
699
708
|
id: idMap.get(n.id),
|
|
700
709
|
position: { x: (n.position?.x ?? 0) + dx, y: (n.position?.y ?? 0) + dy }
|
|
701
710
|
})));
|
|
@@ -906,10 +915,10 @@ function groupAtPoint(pos) {
|
|
|
906
915
|
function addGroupFrame() {
|
|
907
916
|
const rect = flow.vueFlowRef.value?.getBoundingClientRect();
|
|
908
917
|
const center = rect ? flow.screenToFlowCoordinate({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }) : { x: 120, y: 120 };
|
|
909
|
-
addFromPayload({ kind: "group" }, {
|
|
918
|
+
addFromPayload({ kind: "group" }, freeSpotNear({
|
|
910
919
|
x: center.x - EMPTY_GROUP_SIZE.width / 2,
|
|
911
920
|
y: center.y - EMPTY_GROUP_SIZE.height / 2
|
|
912
|
-
});
|
|
921
|
+
}));
|
|
913
922
|
}
|
|
914
923
|
function adoptIntoGroupAt(id, pos) {
|
|
915
924
|
const g = groupAtPoint(pos);
|
|
@@ -970,6 +979,21 @@ function onDrop(ev) {
|
|
|
970
979
|
const payload = JSON.parse(raw);
|
|
971
980
|
addFromPayload(payload, flow.screenToFlowCoordinate({ x: ev.clientX, y: ev.clientY }));
|
|
972
981
|
}
|
|
982
|
+
const CASCADE_STEP = 2 * GRID;
|
|
983
|
+
function freeSpotNear(at) {
|
|
984
|
+
const taken = [
|
|
985
|
+
...vfNodes.value.map((n) => n.position),
|
|
986
|
+
...blob.value.nodes.map((n) => n.position ?? NODE_FALLBACK_POSITION),
|
|
987
|
+
...Object.values(blob.value.triggerPositions ?? {})
|
|
988
|
+
];
|
|
989
|
+
const occupied = (p) => taken.some((q) => Math.abs(q.x - p.x) < GRID && Math.abs(q.y - p.y) < GRID);
|
|
990
|
+
const spot = { x: snap(at.x), y: snap(at.y) };
|
|
991
|
+
for (let i = 0; i <= taken.length && occupied(spot); i++) {
|
|
992
|
+
spot.x += CASCADE_STEP;
|
|
993
|
+
spot.y += CASCADE_STEP;
|
|
994
|
+
}
|
|
995
|
+
return spot;
|
|
996
|
+
}
|
|
973
997
|
function onPaletteInsert(payload) {
|
|
974
998
|
if (pendingConnect.value && payload.kind === "manifest") {
|
|
975
999
|
const pc = pendingConnect.value;
|
|
@@ -1003,7 +1027,7 @@ function onPaletteInsert(payload) {
|
|
|
1003
1027
|
pendingSpliceEdgeId.value = null;
|
|
1004
1028
|
const rect = flow.vueFlowRef.value?.getBoundingClientRect();
|
|
1005
1029
|
const center = rect ? flow.screenToFlowCoordinate({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }) : { x: 120, y: 120 };
|
|
1006
|
-
addFromPayload(payload, center);
|
|
1030
|
+
addFromPayload(payload, freeSpotNear(center));
|
|
1007
1031
|
}
|
|
1008
1032
|
const pendingSpliceEdgeId = ref(null);
|
|
1009
1033
|
provide("workflow-edge-actions", {
|
|
@@ -1279,7 +1303,7 @@ function onConfigOpenAutoFocus(event) {
|
|
|
1279
1303
|
event.preventDefault();
|
|
1280
1304
|
event.target?.focus();
|
|
1281
1305
|
}
|
|
1282
|
-
const canvasHasKeyboard = computed(() => !lightboxOpen.value && !variablesOpen.value && !props.overlayOpen);
|
|
1306
|
+
const canvasHasKeyboard = computed(() => !lightboxOpen.value && !variablesOpen.value && !stateOpen.value && !props.overlayOpen);
|
|
1283
1307
|
const singleSelected = computed(() => flow.getSelectedNodes.value.length === 1 ? flow.getSelectedNodes.value[0] : null);
|
|
1284
1308
|
useCanvasShortcuts(() => ({
|
|
1285
1309
|
"enter": {
|
|
@@ -1324,6 +1348,9 @@ useCanvasShortcuts(() => ({
|
|
|
1324
1348
|
"shift+v": () => {
|
|
1325
1349
|
variablesOpen.value = true;
|
|
1326
1350
|
},
|
|
1351
|
+
"shift+t": () => {
|
|
1352
|
+
stateOpen.value = true;
|
|
1353
|
+
},
|
|
1327
1354
|
"shift+alt+s": () => emit("openSettings"),
|
|
1328
1355
|
"-": () => {
|
|
1329
1356
|
void flow.zoomOut({ duration: 150 });
|
|
@@ -1728,6 +1755,17 @@ const edgeTypes = {
|
|
|
1728
1755
|
<span v-if="variableCount" class="rounded-lg bg-muted px-1.5 text-xs tabular-nums text-muted-foreground">{{ variableCount }}</span>
|
|
1729
1756
|
</button>
|
|
1730
1757
|
</IntegrationsEditorTip>
|
|
1758
|
+
<IntegrationsEditorTip :label="t('integration.editor.tool.state.tooltip')" shortcut="shift+t">
|
|
1759
|
+
<button
|
|
1760
|
+
type="button"
|
|
1761
|
+
class="flex h-8 items-center gap-1.5 rounded-lg px-2.5 text-sm font-medium text-foreground transition hover:bg-black/[0.04]"
|
|
1762
|
+
@click="stateOpen = true"
|
|
1763
|
+
>
|
|
1764
|
+
<Database :size="13" />
|
|
1765
|
+
{{ t("integration.editor.tool.state.label") }}
|
|
1766
|
+
<span v-if="stateCount" class="rounded-lg bg-muted px-1.5 text-xs tabular-nums text-muted-foreground">{{ stateCount }}</span>
|
|
1767
|
+
</button>
|
|
1768
|
+
</IntegrationsEditorTip>
|
|
1731
1769
|
<IntegrationsEditorTip :label="t('integration.editor.tool.settings.tooltip')" shortcut="shift+alt+s">
|
|
1732
1770
|
<button
|
|
1733
1771
|
type="button"
|
|
@@ -1827,6 +1865,14 @@ const edgeTypes = {
|
|
|
1827
1865
|
</div>
|
|
1828
1866
|
</div>
|
|
1829
1867
|
|
|
1868
|
+
<!-- State namespaces lightbox (control-pane entry) — what this workflow may
|
|
1869
|
+
remember between runs. -->
|
|
1870
|
+
<WorkflowStateNamespacesDialog
|
|
1871
|
+
v-model:open="stateOpen"
|
|
1872
|
+
:state="blob.state ?? []"
|
|
1873
|
+
@update:state="updateStateNamespaces"
|
|
1874
|
+
/>
|
|
1875
|
+
|
|
1830
1876
|
<!-- Workflow variables lightbox (control-pane entry). -->
|
|
1831
1877
|
<WorkflowVariablesDialog
|
|
1832
1878
|
v-model:open="variablesOpen"
|
|
@@ -1865,6 +1911,7 @@ const edgeTypes = {
|
|
|
1865
1911
|
:selection="selection"
|
|
1866
1912
|
:secret-keys="secretKeys"
|
|
1867
1913
|
:variables="blob.variables"
|
|
1914
|
+
:state-namespaces="blob.state ?? []"
|
|
1868
1915
|
:upstream-outputs="selectedUpstreamOutputs"
|
|
1869
1916
|
:autofocus-name="renameIntent"
|
|
1870
1917
|
:nodes="blob.nodes"
|
|
@@ -1880,6 +1927,7 @@ const edgeTypes = {
|
|
|
1880
1927
|
selectedId = id;
|
|
1881
1928
|
renameIntent = false;
|
|
1882
1929
|
}"
|
|
1930
|
+
@declare-state="declareStateNamespace"
|
|
1883
1931
|
/>
|
|
1884
1932
|
</DialogContent>
|
|
1885
1933
|
</Dialog>
|
|
@@ -11,6 +11,7 @@ import { Badge, Button } from "@revenexx/studio";
|
|
|
11
11
|
import { toast } from "vue-sonner";
|
|
12
12
|
import "@vue-flow/core/dist/style.css";
|
|
13
13
|
import "@vue-flow/core/dist/theme-default.css";
|
|
14
|
+
import LoadingAnnouncement from "./LoadingAnnouncement.vue";
|
|
14
15
|
import WorkflowNodeView from "./WorkflowNodeView.vue";
|
|
15
16
|
import WorkflowTriggerView from "./WorkflowTriggerView.vue";
|
|
16
17
|
import StickyNoteView from "./StickyNoteView.vue";
|
|
@@ -18,8 +19,11 @@ import GroupFrameView from "./GroupFrameView.vue";
|
|
|
18
19
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
19
20
|
import NodeDynamicOutputsResolver from "./NodeDynamicOutputsResolver.vue";
|
|
20
21
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
22
|
+
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
23
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
21
24
|
import { iteratorNodeIds, nodeLoopData } from "../../data/loopBody";
|
|
22
25
|
import { useIntegrationsRevisions } from "../../composables/useIntegrationsRevisions";
|
|
26
|
+
import { errorReason } from "../../utils/apiError";
|
|
23
27
|
const props = defineProps({
|
|
24
28
|
workflow: { type: Object, required: true },
|
|
25
29
|
draftPending: { type: Boolean, required: false }
|
|
@@ -118,7 +122,6 @@ function buildPreview(blob) {
|
|
|
118
122
|
return { x: 0, y: 0 };
|
|
119
123
|
};
|
|
120
124
|
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
121
|
-
const opposite = (s) => s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
122
125
|
const edgeKind = (sourceNodeId, output) => {
|
|
123
126
|
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
124
127
|
if (!wn) return "default";
|
|
@@ -182,8 +185,8 @@ function buildPreview(blob) {
|
|
|
182
185
|
id: e.id,
|
|
183
186
|
source: e.from.nodeId,
|
|
184
187
|
target: e.to.nodeId,
|
|
185
|
-
sourceHandle:
|
|
186
|
-
targetHandle:
|
|
188
|
+
sourceHandle: outputHandleId(e.from.output, srcSide),
|
|
189
|
+
targetHandle: inputHandleId(OPPOSITE_SIDE[srcSide]),
|
|
187
190
|
type: "flow",
|
|
188
191
|
selectable: false,
|
|
189
192
|
data: { kind, label: isBranchPort ? e.from.output : void 0 },
|
|
@@ -222,7 +225,7 @@ async function select(revision) {
|
|
|
222
225
|
buildPreview(detail.blob);
|
|
223
226
|
}
|
|
224
227
|
} catch (e) {
|
|
225
|
-
toast.error(e
|
|
228
|
+
toast.error(errorReason(e, t("integration.history.toast.loadRevisionFailed")));
|
|
226
229
|
} finally {
|
|
227
230
|
detailLoading.value = false;
|
|
228
231
|
}
|
|
@@ -239,7 +242,7 @@ async function restoreRevision(rev) {
|
|
|
239
242
|
await loadList(true);
|
|
240
243
|
void select(null);
|
|
241
244
|
} catch (e) {
|
|
242
|
-
toast.error(e
|
|
245
|
+
toast.error(errorReason(e, t("integration.history.toast.restoreFailed")));
|
|
243
246
|
} finally {
|
|
244
247
|
restoring.value = null;
|
|
245
248
|
}
|
|
@@ -328,6 +331,14 @@ onMounted(() => {
|
|
|
328
331
|
</p>
|
|
329
332
|
</div>
|
|
330
333
|
|
|
334
|
+
<!-- In the aside's chrome rather than in the scrolling list, so it is on the
|
|
335
|
+
page while the revisions are on their way (PO-251). -->
|
|
336
|
+
<LoadingAnnouncement
|
|
337
|
+
:label="t('integration.history.heading.title')"
|
|
338
|
+
:loading="listLoading && !entries.length"
|
|
339
|
+
:text="t('integration.history.state.loading')"
|
|
340
|
+
/>
|
|
341
|
+
|
|
331
342
|
<div class="flex-1 overflow-y-auto p-2 space-y-1">
|
|
332
343
|
<!-- Current (live) version -->
|
|
333
344
|
<button
|
|
@@ -385,7 +396,7 @@ onMounted(() => {
|
|
|
385
396
|
{{ revisionsApi.error.value }}
|
|
386
397
|
</p>
|
|
387
398
|
|
|
388
|
-
<div v-if="listLoading && !entries.length" class="space-y-2 px-2 py-2">
|
|
399
|
+
<div v-if="listLoading && !entries.length" aria-hidden="true" class="space-y-2 px-2 py-2">
|
|
389
400
|
<div v-for="n in 5" :key="`sk-rev-${n}`" class="flex items-center gap-3 py-1.5">
|
|
390
401
|
<div class="size-7 shrink-0 rounded-md bg-black/[0.07] animate-pulse" />
|
|
391
402
|
<div class="min-w-0 flex-1 space-y-1.5">
|
|
@@ -5,8 +5,12 @@ import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
6
|
import { Copy, DangerTriangle as AlertTriangle, RefreshCircle as Loader2, Repeat, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
7
|
import { NODE_CATEGORIES, findManifest, isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
8
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
8
9
|
import { isTerminalManifest } from "../../data/nodeInputData";
|
|
9
|
-
import {
|
|
10
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
11
|
+
import { SIDE_POSITION, distributedStyle } from "./canvasSides";
|
|
12
|
+
import { fieldDisplayLabel } from "../../utils/validationFindings";
|
|
13
|
+
import { nodeDisplayName } from "../../utils/nodeDisplayName";
|
|
10
14
|
const props = defineProps({
|
|
11
15
|
id: { type: String, required: true },
|
|
12
16
|
data: { type: Object, required: true },
|
|
@@ -17,11 +21,11 @@ defineEmits(["updateNodeInternals"]);
|
|
|
17
21
|
const t = useIntegrationsT();
|
|
18
22
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
19
23
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
20
|
-
const title = computed(
|
|
21
|
-
() => resolveDisplayName(props.data.name) || resolveLocalized(manifest.value?.name) || props.data.nodeSlug
|
|
22
|
-
);
|
|
24
|
+
const title = computed(() => nodeDisplayName(props.data));
|
|
23
25
|
const invalidFields = computed(() => props.data.__invalidFields ?? []);
|
|
24
|
-
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, {
|
|
26
|
+
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, {
|
|
27
|
+
fields: invalidFields.value.map((key) => fieldDisplayLabel(props.data, key)).join(", ")
|
|
28
|
+
}));
|
|
25
29
|
const insideLoops = computed(() => props.data.__loop?.inside ?? []);
|
|
26
30
|
const repeatsLabel = computed(() => t("integration.canvas.node.badge.repeats", insideLoops.value.length, { iterators: insideLoops.value.join(", ") }));
|
|
27
31
|
const bodySize = computed(() => props.data.__loop?.bodySize);
|
|
@@ -111,14 +115,6 @@ function portTone(p) {
|
|
|
111
115
|
const showPortLabels = computed(() => resolvePending.value || outputs.value.length > 1 || outputs.value.some((p) => p.kind !== "default"));
|
|
112
116
|
const IN_SIDES = ["top"];
|
|
113
117
|
const OUT_SIDES = ["bottom"];
|
|
114
|
-
function sideToPosition(s) {
|
|
115
|
-
return s === "top" ? Position.Top : s === "right" ? Position.Right : s === "bottom" ? Position.Bottom : Position.Left;
|
|
116
|
-
}
|
|
117
|
-
function distributedStyle(side, i, total) {
|
|
118
|
-
const pct = `${(i + 1) / (total + 1) * 100}%`;
|
|
119
|
-
if (side === "top" || side === "bottom") return { left: pct };
|
|
120
|
-
return { top: pct };
|
|
121
|
-
}
|
|
122
118
|
const flow = useVueFlow();
|
|
123
119
|
const hovered = ref(false);
|
|
124
120
|
let hoverTimer = null;
|
|
@@ -192,22 +188,22 @@ function remove() {
|
|
|
192
188
|
<NodeToolbar v-if="nodeActions" :is-visible="hovered || selected" :position="Position.Top" :offset="8" class="flex items-center gap-1 rounded-md border border-black/[0.08] bg-white px-1 py-1 shadow-md" @mouseenter="onHoverEnter" @mouseleave="onHoverLeave">
|
|
193
189
|
<button
|
|
194
190
|
type="button"
|
|
195
|
-
class="flex
|
|
191
|
+
class="flex size-6 items-center justify-center rounded text-sm text-foreground/80 hover:bg-muted/50"
|
|
196
192
|
:title="t('integration.canvas.node.action.duplicate')"
|
|
197
193
|
:aria-label="t('integration.canvas.node.action.duplicateAria')"
|
|
198
194
|
@click="duplicate"
|
|
199
195
|
>
|
|
200
|
-
<Copy :size="
|
|
196
|
+
<Copy :size="13" />
|
|
201
197
|
</button>
|
|
202
198
|
<div class="h-4 w-px bg-black/10" />
|
|
203
199
|
<button
|
|
204
200
|
type="button"
|
|
205
|
-
class="flex
|
|
201
|
+
class="flex size-6 items-center justify-center rounded text-sm text-rose-600 hover:bg-rose-50"
|
|
206
202
|
:title="t('integration.common.action.delete')"
|
|
207
203
|
:aria-label="t('integration.canvas.node.action.deleteAria')"
|
|
208
204
|
@click="remove"
|
|
209
205
|
>
|
|
210
|
-
<TrashBin :size="
|
|
206
|
+
<TrashBin :size="13" />
|
|
211
207
|
</button>
|
|
212
208
|
</NodeToolbar>
|
|
213
209
|
|
|
@@ -239,10 +235,10 @@ function remove() {
|
|
|
239
235
|
dots feed the same `__in` port. Clearly visible, ring + shadow. -->
|
|
240
236
|
<Handle
|
|
241
237
|
v-for="side in IN_SIDES"
|
|
242
|
-
:id="
|
|
238
|
+
:id="inputHandleId(side)"
|
|
243
239
|
:key="`in-${side}`"
|
|
244
240
|
type="target"
|
|
245
|
-
:position="
|
|
241
|
+
:position="SIDE_POSITION[side]"
|
|
246
242
|
:title="t('integration.canvas.node.handle.input')"
|
|
247
243
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-slate-700"
|
|
248
244
|
/>
|
|
@@ -252,10 +248,10 @@ function remove() {
|
|
|
252
248
|
<template v-for="side in OUT_SIDES" :key="`side-${side}`">
|
|
253
249
|
<Handle
|
|
254
250
|
v-for="(p, i) in outputs"
|
|
255
|
-
:id="
|
|
251
|
+
:id="outputHandleId(p.name, side)"
|
|
256
252
|
:key="`${p.name}-${side}`"
|
|
257
253
|
type="source"
|
|
258
|
-
:position="
|
|
254
|
+
:position="SIDE_POSITION[side]"
|
|
259
255
|
:title="handleTitle(p)"
|
|
260
256
|
:style="distributedStyle(side, i, outputs.length)"
|
|
261
257
|
class="!size-3 !rounded-full !border-2 !border-white !shadow-sm transition-all hover:!size-3.5"
|
|
@@ -24,10 +24,14 @@ import StickyNoteView from "./StickyNoteView.vue";
|
|
|
24
24
|
import GroupFrameView from "./GroupFrameView.vue";
|
|
25
25
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
26
26
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
27
|
+
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
28
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
27
29
|
import { nodeLoopData } from "../../data/loopBody";
|
|
28
30
|
import { triggerGlyph } from "../../data/triggerGlyphs";
|
|
31
|
+
import { formatDuration } from "../../utils/formatDuration";
|
|
29
32
|
import { SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
30
33
|
import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
|
|
34
|
+
import { errorReason } from "../../utils/apiError";
|
|
31
35
|
const props = defineProps({
|
|
32
36
|
workflow: { type: Object, required: true },
|
|
33
37
|
initialRunId: { type: [String, null], required: false },
|
|
@@ -59,7 +63,7 @@ async function loadRuns(reset = false) {
|
|
|
59
63
|
runsPage.value = res.page;
|
|
60
64
|
runsLastPage.value = res.lastPage;
|
|
61
65
|
} catch (e) {
|
|
62
|
-
toast.error(e
|
|
66
|
+
toast.error(errorReason(e, t("integration.replay.toast.loadRunsFailed")));
|
|
63
67
|
} finally {
|
|
64
68
|
runsLoading.value = false;
|
|
65
69
|
}
|
|
@@ -101,7 +105,7 @@ async function loadSteps(runId, isPoll = false) {
|
|
|
101
105
|
}
|
|
102
106
|
} catch (e) {
|
|
103
107
|
if (selectedRunId.value === runId) {
|
|
104
|
-
stepsError.value = e
|
|
108
|
+
stepsError.value = errorReason(e, t("integration.replay.error.loadStepsFailed"));
|
|
105
109
|
}
|
|
106
110
|
} finally {
|
|
107
111
|
if (!isPoll) stepsLoading.value = false;
|
|
@@ -293,7 +297,6 @@ const vfEdges = computed(() => {
|
|
|
293
297
|
return { x: 0, y: 0 };
|
|
294
298
|
};
|
|
295
299
|
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
296
|
-
const opposite = (s) => s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
297
300
|
const edgeKind = (sourceNodeId, output) => {
|
|
298
301
|
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
299
302
|
if (!wn) return "default";
|
|
@@ -331,8 +334,8 @@ const vfEdges = computed(() => {
|
|
|
331
334
|
id: e.id,
|
|
332
335
|
source: e.from.nodeId,
|
|
333
336
|
target: e.to.nodeId,
|
|
334
|
-
sourceHandle:
|
|
335
|
-
targetHandle:
|
|
337
|
+
sourceHandle: outputHandleId(e.from.output, srcSide),
|
|
338
|
+
targetHandle: inputHandleId(OPPOSITE_SIDE[srcSide]),
|
|
336
339
|
type: "flow",
|
|
337
340
|
selectable: false,
|
|
338
341
|
animated: traversed && !targetReached,
|
|
@@ -395,12 +398,6 @@ function formatTime(iso) {
|
|
|
395
398
|
return String(iso);
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
|
-
function formatDuration(ms) {
|
|
399
|
-
if (ms == null) return "\u2014";
|
|
400
|
-
if (ms < 1e3) return `${ms} ms`;
|
|
401
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)} s`;
|
|
402
|
-
return `${Math.floor(ms / 6e4)}m ${Math.round(ms % 6e4 / 1e3)}s`;
|
|
403
|
-
}
|
|
404
401
|
function pretty(v) {
|
|
405
402
|
try {
|
|
406
403
|
return JSON.stringify(v, null, 2);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WorkflowStateNamespace } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
state: WorkflowStateNamespace[];
|
|
5
|
+
};
|
|
6
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
+
"update:open": (args_0: boolean) => any;
|
|
8
|
+
"update:state": (next: WorkflowStateNamespace[]) => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
"onUpdate:open"?: ((args_0: boolean) => any) | undefined;
|
|
11
|
+
"onUpdate:state"?: ((next: WorkflowStateNamespace[]) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
export default _default;
|