@revenexx/studio-integrations 0.1.3 → 0.2.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/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
- package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
- package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
- package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
- package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
- package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
- package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
- package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
- package/dist/runtime/components/Integrations/Sidebar.vue +1 -0
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
- package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
- package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
- package/dist/runtime/composables/integrationsApi.d.ts +6 -2
- package/dist/runtime/composables/integrationsApi.js +5 -4
- package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
- package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
- package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +6 -3
- package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +51 -0
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +92 -4
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsTemplates.js +32 -4
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsWorkflows.js +57 -1
- package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
- package/dist/runtime/composables/useMiniMapLayout.js +119 -0
- package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +123 -0
- package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
- package/dist/runtime/composables/useNodeDrag.js +10 -0
- package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
- package/dist/runtime/composables/usePaletteLayout.js +113 -0
- package/dist/runtime/data/expressionEditor.d.ts +22 -0
- package/dist/runtime/data/expressionEditor.js +40 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -8
- package/dist/runtime/data/nodeInputData.d.ts +37 -0
- package/dist/runtime/data/nodeInputData.js +62 -0
- package/dist/runtime/data/nodeRunData.d.ts +56 -0
- package/dist/runtime/data/nodeRunData.js +91 -0
- package/dist/runtime/data/payloadForm.d.ts +44 -0
- package/dist/runtime/data/payloadForm.js +126 -0
- package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
- package/dist/runtime/data/workflowIconCatalog.js +116 -0
- package/dist/runtime/pages/integrations/audit.vue +25 -35
- package/dist/runtime/pages/integrations/credentials.vue +117 -122
- package/dist/runtime/pages/integrations/index.vue +133 -36
- package/dist/runtime/pages/integrations/runs.vue +125 -75
- package/dist/runtime/pages/integrations/schedules.vue +5 -16
- package/dist/runtime/pages/integrations/secrets.vue +1 -3
- package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
- package/dist/runtime/types/integrations.d.ts +190 -4
- package/dist/runtime/utils/credentialState.d.ts +28 -0
- package/dist/runtime/utils/credentialState.js +29 -0
- package/dist/runtime/utils/localize.d.ts +3 -1
- package/dist/runtime/utils/localize.js +1 -2
- package/dist/runtime/utils/statusStyle.d.ts +6 -0
- package/dist/runtime/utils/statusStyle.js +15 -0
- package/package.json +12 -2
- package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
- package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
- package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
- package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
ContextMenu,
|
|
5
|
+
ContextMenuContent,
|
|
6
|
+
ContextMenuItem,
|
|
7
|
+
ContextMenuLabel,
|
|
8
|
+
ContextMenuSeparator,
|
|
9
|
+
ContextMenuShortcut,
|
|
10
|
+
ContextMenuTrigger
|
|
11
|
+
} from "@revenexx/studio";
|
|
12
|
+
const props = defineProps({
|
|
13
|
+
kind: { type: String, required: true },
|
|
14
|
+
selectionCount: { type: Number, required: true },
|
|
15
|
+
totalNodes: { type: Number, required: true },
|
|
16
|
+
targetIsTrigger: { type: Boolean, required: false },
|
|
17
|
+
runBusy: { type: Boolean, required: false }
|
|
18
|
+
});
|
|
19
|
+
const emit = defineEmits(["action", "update:open"]);
|
|
20
|
+
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "");
|
|
21
|
+
function formatShortcut(tokens) {
|
|
22
|
+
const parts = tokens.split("+");
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const raw of parts) {
|
|
25
|
+
const t = raw.toLowerCase();
|
|
26
|
+
if (t === "mod") out.push(isMac ? "\u2318" : "Ctrl");
|
|
27
|
+
else if (t === "shift") out.push("\u21E7");
|
|
28
|
+
else if (t === "alt") out.push(isMac ? "\u2325" : "Alt");
|
|
29
|
+
else if (t === "enter") out.push("\u21B5");
|
|
30
|
+
else if (t === "backspace") out.push("\u232B");
|
|
31
|
+
else if (t === "delete") out.push("Del");
|
|
32
|
+
else if (t === "escape") out.push("Esc");
|
|
33
|
+
else if (t === "tab") out.push("Tab");
|
|
34
|
+
else if (t === "space") out.push("Space");
|
|
35
|
+
else if (t === "f2") out.push("F2");
|
|
36
|
+
else out.push(t.toUpperCase());
|
|
37
|
+
}
|
|
38
|
+
return isMac ? out.join("") : out.join("+");
|
|
39
|
+
}
|
|
40
|
+
const allSelected = computed(() => props.totalNodes > 0 && props.selectionCount >= props.totalNodes);
|
|
41
|
+
const sections = computed(() => {
|
|
42
|
+
if (props.kind === "node") {
|
|
43
|
+
return [
|
|
44
|
+
[
|
|
45
|
+
{ id: "open", label: "Open", shortcut: "enter" },
|
|
46
|
+
{ id: "rename", label: "Rename", shortcut: "f2" }
|
|
47
|
+
],
|
|
48
|
+
[
|
|
49
|
+
{ id: "copy", label: "Copy", shortcut: "mod+c", disabled: props.targetIsTrigger },
|
|
50
|
+
{ id: "duplicate", label: "Duplicate", shortcut: "mod+d", disabled: props.targetIsTrigger }
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
{ id: "run", label: "Run workflow", shortcut: "mod+enter", disabled: props.runBusy },
|
|
54
|
+
{ id: "tidy-up", label: "Tidy up", shortcut: "shift+alt+t" }
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
{ id: "delete", label: "Delete", shortcut: "backspace", danger: true }
|
|
58
|
+
]
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
if (props.kind === "selection") {
|
|
62
|
+
const n = props.selectionCount;
|
|
63
|
+
return [
|
|
64
|
+
[
|
|
65
|
+
{ id: "copy", label: `Copy ${n} nodes`, shortcut: "mod+c" },
|
|
66
|
+
{ id: "duplicate", label: `Duplicate ${n} nodes`, shortcut: "mod+d" }
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
{ id: "group", label: `Group ${n} nodes`, shortcut: "mod+g" },
|
|
70
|
+
{ id: "tidy-up", label: "Tidy up", shortcut: "shift+alt+t" },
|
|
71
|
+
{ id: "select-all", label: "Select all", shortcut: "mod+a", disabled: allSelected.value },
|
|
72
|
+
{ id: "clear-selection", label: "Clear selection", shortcut: "escape" }
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
{ id: "delete", label: `Delete ${n} nodes`, shortcut: "backspace", danger: true }
|
|
76
|
+
]
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
[
|
|
81
|
+
{ id: "add-node", label: "Add node\u2026", shortcut: "tab" },
|
|
82
|
+
{ id: "add-sticky", label: "Add sticky", shortcut: "shift+s" },
|
|
83
|
+
{ id: "paste", label: "Paste", shortcut: "mod+v" }
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
{ id: "tidy-up", label: "Tidy up", shortcut: "shift+alt+t" },
|
|
87
|
+
{ id: "fit-view", label: "Fit view", shortcut: "1" }
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
{ id: "select-all", label: "Select all", shortcut: "mod+a", disabled: allSelected.value || props.totalNodes === 0 }
|
|
91
|
+
]
|
|
92
|
+
];
|
|
93
|
+
});
|
|
94
|
+
const headerLabel = computed(
|
|
95
|
+
() => props.kind === "selection" ? `${props.selectionCount} nodes selected` : null
|
|
96
|
+
);
|
|
97
|
+
function onSelect(item) {
|
|
98
|
+
if (item.disabled) return;
|
|
99
|
+
emit("action", item.id);
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<ContextMenu @update:open="(v) => emit('update:open', v)">
|
|
105
|
+
<!-- The canvas. as-child avoids an extra wrapper element so vue-flow's
|
|
106
|
+
100%-height layout is untouched. -->
|
|
107
|
+
<ContextMenuTrigger as-child>
|
|
108
|
+
<slot />
|
|
109
|
+
</ContextMenuTrigger>
|
|
110
|
+
|
|
111
|
+
<ContextMenuContent class="w-60 rounded-lg border border-black/[0.06] bg-white p-1 shadow-lg">
|
|
112
|
+
<template v-if="headerLabel">
|
|
113
|
+
<ContextMenuLabel class="px-2 py-1.5 text-sm font-medium text-muted-foreground">
|
|
114
|
+
{{ headerLabel }}
|
|
115
|
+
</ContextMenuLabel>
|
|
116
|
+
<ContextMenuSeparator class="my-1 h-px bg-black/[0.06]" />
|
|
117
|
+
</template>
|
|
118
|
+
|
|
119
|
+
<template v-for="(group, gi) in sections" :key="gi">
|
|
120
|
+
<ContextMenuSeparator v-if="gi > 0" class="my-1 h-px bg-black/[0.06]" />
|
|
121
|
+
<ContextMenuItem
|
|
122
|
+
v-for="item in group"
|
|
123
|
+
:key="item.id"
|
|
124
|
+
:disabled="item.disabled"
|
|
125
|
+
:variant="item.danger ? 'destructive' : 'default'"
|
|
126
|
+
class="rounded-md px-2 py-1.5 text-sm"
|
|
127
|
+
@select="onSelect(item)"
|
|
128
|
+
>
|
|
129
|
+
<span class="flex-1 truncate">{{ item.label }}</span>
|
|
130
|
+
<span v-if="item.hint" class="ml-2 text-sm text-muted-foreground">{{ item.hint }}</span>
|
|
131
|
+
<ContextMenuShortcut v-if="item.shortcut" class="ml-auto pl-4 text-sm tabular-nums">
|
|
132
|
+
{{ formatShortcut(item.shortcut) }}
|
|
133
|
+
</ContextMenuShortcut>
|
|
134
|
+
</ContextMenuItem>
|
|
135
|
+
</template>
|
|
136
|
+
</ContextMenuContent>
|
|
137
|
+
</ContextMenu>
|
|
138
|
+
</template>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type MenuKind = 'node' | 'selection' | 'canvas';
|
|
2
|
+
export type MenuActionId = 'open' | 'rename' | 'copy' | 'duplicate' | 'paste' | 'run' | 'tidy-up' | 'fit-view' | 'add-node' | 'add-sticky' | 'group' | 'select-all' | 'clear-selection' | 'delete';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
/** Which context was right-clicked. The editor sets this in the vue-flow hooks. */
|
|
5
|
+
kind: MenuKind;
|
|
6
|
+
/** Number of currently selected nodes (drives selection labels/guards). */
|
|
7
|
+
selectionCount: number;
|
|
8
|
+
/** Total nodes on the canvas (disables "Select all" when everything is selected). */
|
|
9
|
+
totalNodes: number;
|
|
10
|
+
/** True when the single targeted node is a trigger (duplicate disabled in v1). */
|
|
11
|
+
targetIsTrigger?: boolean;
|
|
12
|
+
/** Busy flag for the Run item. */
|
|
13
|
+
runBusy?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare var __VLS_14: {};
|
|
16
|
+
type __VLS_Slots = {} & {
|
|
17
|
+
default?: (props: typeof __VLS_14) => any;
|
|
18
|
+
};
|
|
19
|
+
declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
20
|
+
action: (id: MenuActionId) => any;
|
|
21
|
+
"update:open": (open: boolean) => any;
|
|
22
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
onAction?: ((id: MenuActionId) => any) | undefined;
|
|
24
|
+
"onUpdate:open"?: ((open: boolean) => any) | undefined;
|
|
25
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
26
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
27
|
+
export default _default;
|
|
28
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
29
|
+
new (): {
|
|
30
|
+
$slots: S;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type __VLS_PublicProps = {
|
|
2
|
+
'open'?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:open": (value: boolean) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
7
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { RefreshCircle as Loader2, MagicStick as Sparkles } from "#solar-icons";
|
|
4
|
+
import * as Lucide from "lucide-vue-next";
|
|
5
|
+
import {
|
|
6
|
+
Badge,
|
|
7
|
+
Button,
|
|
8
|
+
Dialog,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
Input,
|
|
13
|
+
Label,
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue,
|
|
19
|
+
Textarea
|
|
20
|
+
} from "@revenexx/studio";
|
|
21
|
+
import { toast } from "vue-sonner";
|
|
22
|
+
import { TRIGGER_TEMPLATES } from "../../data/integrationNodeCatalog";
|
|
23
|
+
import TemplateFlowPreview from "./TemplateFlowPreview.vue";
|
|
24
|
+
import WorkflowIconPicker from "./WorkflowIconPicker.vue";
|
|
25
|
+
import WorkflowTagsInput from "./WorkflowTagsInput.vue";
|
|
26
|
+
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const { create, workflows } = useIntegrationsWorkflows();
|
|
29
|
+
const { templates, loading: templatesLoading, error: templatesError, load: loadTemplates, ensureDetails, instantiate } = useIntegrationsTemplates();
|
|
30
|
+
const mode = ref("blank");
|
|
31
|
+
const modeTabs = [
|
|
32
|
+
{ key: "blank", label: "Start blank" },
|
|
33
|
+
{ key: "template", label: "Start from template" }
|
|
34
|
+
];
|
|
35
|
+
function loc(v) {
|
|
36
|
+
if (!v) return "";
|
|
37
|
+
return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
|
|
38
|
+
}
|
|
39
|
+
function iconFor(name2) {
|
|
40
|
+
if (!name2) return Lucide.Box;
|
|
41
|
+
return Lucide[name2] || Lucide.Box;
|
|
42
|
+
}
|
|
43
|
+
const name = ref("");
|
|
44
|
+
const description = ref("");
|
|
45
|
+
const triggerType = ref("manual");
|
|
46
|
+
const executionMode = ref("async_only");
|
|
47
|
+
const tags = ref([]);
|
|
48
|
+
const icon = ref(null);
|
|
49
|
+
const tagSuggestions = computed(() => {
|
|
50
|
+
const all = /* @__PURE__ */ new Set();
|
|
51
|
+
for (const wf of workflows.value) for (const t of wf.blob.tags ?? []) all.add(t);
|
|
52
|
+
return [...all].sort();
|
|
53
|
+
});
|
|
54
|
+
const submitting = ref(false);
|
|
55
|
+
const error = ref(null);
|
|
56
|
+
const executionModes = [
|
|
57
|
+
{ key: "async_only", label: "Async only", hint: "Always queued.", icon: "Inbox" },
|
|
58
|
+
{ key: "sync_only", label: "Sync only", hint: "Waits for the result.", icon: "Timer" },
|
|
59
|
+
{ key: "caller_decides", label: "Caller decides", hint: "The request chooses.", icon: "SlidersHorizontal" }
|
|
60
|
+
];
|
|
61
|
+
const selectedTrigger = computed(() => TRIGGER_TEMPLATES.find((t) => t.type === triggerType.value) ?? null);
|
|
62
|
+
const selectedMode = computed(() => executionModes.find((m) => m.key === executionMode.value) ?? executionModes[0]);
|
|
63
|
+
watch(open, (v) => {
|
|
64
|
+
if (!v) return;
|
|
65
|
+
mode.value = "blank";
|
|
66
|
+
name.value = "";
|
|
67
|
+
description.value = "";
|
|
68
|
+
triggerType.value = "manual";
|
|
69
|
+
executionMode.value = "async_only";
|
|
70
|
+
tags.value = [];
|
|
71
|
+
icon.value = null;
|
|
72
|
+
error.value = null;
|
|
73
|
+
templateSearch.value = "";
|
|
74
|
+
void loadTemplates();
|
|
75
|
+
});
|
|
76
|
+
async function submit() {
|
|
77
|
+
if (!name.value.trim()) {
|
|
78
|
+
error.value = "Name is required.";
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
submitting.value = true;
|
|
82
|
+
error.value = null;
|
|
83
|
+
try {
|
|
84
|
+
const tpl = TRIGGER_TEMPLATES.find((t) => t.type === triggerType.value);
|
|
85
|
+
const handle = globalThis.crypto?.randomUUID?.() ?? `trg-${Date.now()}`;
|
|
86
|
+
const blob = {
|
|
87
|
+
nodeManifestVersion: "v0-draft",
|
|
88
|
+
name: name.value.trim(),
|
|
89
|
+
description: description.value.trim() || void 0,
|
|
90
|
+
triggers: [{ id: handle, type: triggerType.value, active: true, config: { ...tpl?.defaultConfig ?? {} } }],
|
|
91
|
+
nodes: [],
|
|
92
|
+
edges: [],
|
|
93
|
+
...tags.value.length ? { tags: [...tags.value] } : {},
|
|
94
|
+
...icon.value ? { icon: icon.value } : {}
|
|
95
|
+
};
|
|
96
|
+
const created = await create({
|
|
97
|
+
name: name.value.trim(),
|
|
98
|
+
description: description.value.trim() || null,
|
|
99
|
+
blob,
|
|
100
|
+
active: false,
|
|
101
|
+
executionMode: executionMode.value
|
|
102
|
+
});
|
|
103
|
+
open.value = false;
|
|
104
|
+
await router.push(`/integrations/workflows/${created.id}`);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
error.value = e.message || "Could not create the workflow.";
|
|
107
|
+
} finally {
|
|
108
|
+
submitting.value = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const instantiatingSlug = ref(null);
|
|
112
|
+
const templateSearch = ref("");
|
|
113
|
+
watch(() => templates.value.length, (n) => {
|
|
114
|
+
if (n) void ensureDetails(templates.value.map((t) => t.slug));
|
|
115
|
+
}, { immediate: true });
|
|
116
|
+
const filteredTemplates = computed(() => {
|
|
117
|
+
const q = templateSearch.value.trim().toLowerCase();
|
|
118
|
+
if (!q) return templates.value;
|
|
119
|
+
return templates.value.filter((t) => `${t.name} ${t.description} ${t.category}`.toLowerCase().includes(q));
|
|
120
|
+
});
|
|
121
|
+
async function useTemplate(slug) {
|
|
122
|
+
if (instantiatingSlug.value) return;
|
|
123
|
+
instantiatingSlug.value = slug;
|
|
124
|
+
try {
|
|
125
|
+
const id = await instantiate(slug);
|
|
126
|
+
if (id == null) throw new Error("The template could not be instantiated.");
|
|
127
|
+
open.value = false;
|
|
128
|
+
await router.push(`/integrations/workflows/${id}`);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
toast.error(e?.message ?? "Could not create the workflow from this template.");
|
|
131
|
+
} finally {
|
|
132
|
+
instantiatingSlug.value = null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<Dialog v-model:open="open">
|
|
139
|
+
<DialogContent class="sm:max-w-4xl">
|
|
140
|
+
<DialogHeader>
|
|
141
|
+
<DialogTitle>New workflow</DialogTitle>
|
|
142
|
+
</DialogHeader>
|
|
143
|
+
|
|
144
|
+
<!-- Switcher: the same TabBar the studio surfaces use. -->
|
|
145
|
+
<TabBar v-model="mode" :tabs="modeTabs" class="-mt-1 shrink-0" />
|
|
146
|
+
|
|
147
|
+
<!-- ── Blank ─────────────────────────────────────────────────────────── -->
|
|
148
|
+
<!-- -m-1/p-1 keeps the selection rings clear of the scroll clipping. -->
|
|
149
|
+
<form v-if="mode === 'blank'" class="-m-1 max-h-[65vh] space-y-3 overflow-y-auto p-1" @submit.prevent="submit">
|
|
150
|
+
<!-- Two-column rows: label rail left, field right. -->
|
|
151
|
+
<div class="grid grid-cols-[130px_1fr] items-center gap-x-4">
|
|
152
|
+
<Label for="wf-name">Name</Label>
|
|
153
|
+
<Input id="wf-name" v-model="name" placeholder="Sync orders nightly" class="bg-white" />
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="grid grid-cols-[130px_1fr] items-start gap-x-4">
|
|
157
|
+
<Label for="wf-desc" class="pt-2.5">Description</Label>
|
|
158
|
+
<Textarea
|
|
159
|
+
id="wf-desc"
|
|
160
|
+
v-model="description"
|
|
161
|
+
rows="2"
|
|
162
|
+
placeholder="Optional: what this workflow does and why it exists."
|
|
163
|
+
class="bg-white"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div class="grid grid-cols-[130px_1fr] items-center gap-x-4">
|
|
168
|
+
<Label>Tags</Label>
|
|
169
|
+
<WorkflowTagsInput v-model="tags" :suggestions="tagSuggestions" />
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="grid grid-cols-[130px_1fr] items-center gap-x-4">
|
|
173
|
+
<Label>Icon</Label>
|
|
174
|
+
<WorkflowIconPicker v-model="icon" />
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div class="grid grid-cols-[130px_1fr] items-center gap-x-4">
|
|
178
|
+
<Label>Trigger</Label>
|
|
179
|
+
<Select v-model="triggerType">
|
|
180
|
+
<SelectTrigger class="w-full bg-white">
|
|
181
|
+
<SelectValue>
|
|
182
|
+
<span class="flex min-w-0 items-center gap-2">
|
|
183
|
+
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
184
|
+
<component :is="iconFor(selectedTrigger?.icon)" :size="13" />
|
|
185
|
+
</span>
|
|
186
|
+
<span class="shrink-0 text-sm font-medium">{{ loc(selectedTrigger?.name) }}</span>
|
|
187
|
+
<span class="truncate text-sm text-muted-foreground">{{ loc(selectedTrigger?.description) }}</span>
|
|
188
|
+
</span>
|
|
189
|
+
</SelectValue>
|
|
190
|
+
</SelectTrigger>
|
|
191
|
+
<SelectContent>
|
|
192
|
+
<SelectItem v-for="t in TRIGGER_TEMPLATES" :key="t.id" :value="t.type">
|
|
193
|
+
<span class="flex items-center gap-2.5 py-0.5">
|
|
194
|
+
<span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-emerald-100 text-emerald-600">
|
|
195
|
+
<component :is="iconFor(t.icon)" :size="14" />
|
|
196
|
+
</span>
|
|
197
|
+
<span class="min-w-0">
|
|
198
|
+
<span class="block text-sm font-medium">{{ loc(t.name) }}</span>
|
|
199
|
+
<span class="block text-sm text-muted-foreground">{{ loc(t.description) }}</span>
|
|
200
|
+
</span>
|
|
201
|
+
</span>
|
|
202
|
+
</SelectItem>
|
|
203
|
+
</SelectContent>
|
|
204
|
+
</Select>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="grid grid-cols-[130px_1fr] items-center gap-x-4">
|
|
208
|
+
<Label>Execution mode</Label>
|
|
209
|
+
<Select v-model="executionMode">
|
|
210
|
+
<SelectTrigger class="w-full bg-white">
|
|
211
|
+
<SelectValue>
|
|
212
|
+
<span class="flex min-w-0 items-center gap-2">
|
|
213
|
+
<span class="flex size-6 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
214
|
+
<component :is="iconFor(selectedMode.icon)" :size="13" />
|
|
215
|
+
</span>
|
|
216
|
+
<span class="shrink-0 text-sm font-medium">{{ selectedMode.label }}</span>
|
|
217
|
+
<span class="truncate text-sm text-muted-foreground">{{ selectedMode.hint }}</span>
|
|
218
|
+
</span>
|
|
219
|
+
</SelectValue>
|
|
220
|
+
</SelectTrigger>
|
|
221
|
+
<SelectContent>
|
|
222
|
+
<SelectItem v-for="opt in executionModes" :key="opt.key" :value="opt.key">
|
|
223
|
+
<span class="flex items-center gap-2.5 py-0.5">
|
|
224
|
+
<span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
225
|
+
<component :is="iconFor(opt.icon)" :size="14" />
|
|
226
|
+
</span>
|
|
227
|
+
<span class="min-w-0">
|
|
228
|
+
<span class="block text-sm font-medium">{{ opt.label }}</span>
|
|
229
|
+
<span class="block text-sm text-muted-foreground">{{ opt.hint }}</span>
|
|
230
|
+
</span>
|
|
231
|
+
</span>
|
|
232
|
+
</SelectItem>
|
|
233
|
+
</SelectContent>
|
|
234
|
+
</Select>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div v-if="error" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
238
|
+
{{ error }}
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="flex gap-2 pt-1">
|
|
242
|
+
<Button type="submit" :disabled="submitting" class="h-8 text-sm">
|
|
243
|
+
<Loader2 v-if="submitting" :size="14" class="animate-spin" />
|
|
244
|
+
{{ submitting ? "Creating\u2026" : "Create and open editor" }}
|
|
245
|
+
</Button>
|
|
246
|
+
<Button type="button" variant="outline" class="h-8 text-sm" @click="open = false">
|
|
247
|
+
Cancel
|
|
248
|
+
</Button>
|
|
249
|
+
</div>
|
|
250
|
+
</form>
|
|
251
|
+
|
|
252
|
+
<!-- ── From template ─────────────────────────────────────────────────── -->
|
|
253
|
+
<div v-else class="-m-1 max-h-[65vh] space-y-3 overflow-y-auto p-1">
|
|
254
|
+
<div class="relative">
|
|
255
|
+
<Lucide.Search :size="12" class="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
256
|
+
<input
|
|
257
|
+
v-model="templateSearch"
|
|
258
|
+
type="text"
|
|
259
|
+
placeholder="Search templates…"
|
|
260
|
+
class="h-8 w-full rounded-md border border-black/[0.08] bg-white pl-7 pr-2 text-sm focus:outline-none focus:ring-2 focus:ring-emerald-500/40"
|
|
261
|
+
>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div v-if="templatesLoading && !templates.length" class="grid gap-2 p-1 sm:grid-cols-2">
|
|
265
|
+
<div v-for="n in 6" :key="`sk-tpl-${n}`" class="rounded-lg border border-black/[0.06] bg-white p-3 space-y-2">
|
|
266
|
+
<div class="flex items-center gap-2">
|
|
267
|
+
<div class="size-7 rounded-md bg-black/[0.07] animate-pulse" />
|
|
268
|
+
<div class="h-3 w-1/2 rounded bg-black/[0.07] animate-pulse" />
|
|
269
|
+
</div>
|
|
270
|
+
<div class="h-3 w-full rounded bg-black/[0.05] animate-pulse" />
|
|
271
|
+
<div class="h-3 w-2/3 rounded bg-black/[0.05] animate-pulse" />
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
<div v-else-if="templatesError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
275
|
+
{{ templatesError }}
|
|
276
|
+
</div>
|
|
277
|
+
<p v-else-if="!templates.length" class="p-6 text-center text-sm text-muted-foreground">
|
|
278
|
+
No templates in the catalogue yet.
|
|
279
|
+
</p>
|
|
280
|
+
<p v-else-if="!filteredTemplates.length" class="p-6 text-center text-sm text-muted-foreground">
|
|
281
|
+
Nothing matches “{{ templateSearch }}”.
|
|
282
|
+
</p>
|
|
283
|
+
|
|
284
|
+
<div v-else class="grid gap-2.5 sm:grid-cols-2 lg:grid-cols-3">
|
|
285
|
+
<button
|
|
286
|
+
v-for="tpl in filteredTemplates"
|
|
287
|
+
:key="tpl.slug"
|
|
288
|
+
type="button"
|
|
289
|
+
class="flex flex-col overflow-hidden rounded-lg border border-black/[0.06] bg-white text-left transition hover:border-black/20 hover:shadow-sm disabled:opacity-60"
|
|
290
|
+
:disabled="!!instantiatingSlug"
|
|
291
|
+
@click="useTemplate(tpl.slug)"
|
|
292
|
+
>
|
|
293
|
+
<div class="relative h-24 shrink-0 border-b border-black/[0.06] bg-slate-50/70 [background-image:radial-gradient(circle,rgba(15,23,42,0.07)_1px,transparent_1px)] [background-size:12px_12px]">
|
|
294
|
+
<TemplateFlowPreview v-if="tpl.detailLoaded" :blob="tpl.blob" :height="96" class="px-1" />
|
|
295
|
+
<div v-else class="flex h-full items-center justify-center">
|
|
296
|
+
<Loader2 :size="14" class="animate-spin text-muted-foreground/40" />
|
|
297
|
+
</div>
|
|
298
|
+
<span class="absolute left-1.5 top-1.5 inline-flex items-center rounded-full bg-slate-100 px-1.5 py-px uppercase text-[11px] font-medium text-slate-700">
|
|
299
|
+
{{ tpl.category.charAt(0).toUpperCase() + tpl.category.slice(1) }}
|
|
300
|
+
</span>
|
|
301
|
+
<Loader2 v-if="instantiatingSlug === tpl.slug" :size="13" class="absolute right-1.5 top-1.5 animate-spin text-muted-foreground" />
|
|
302
|
+
</div>
|
|
303
|
+
<div class="flex min-h-0 flex-1 flex-col p-3">
|
|
304
|
+
<div class="truncate text-sm font-medium">{{ tpl.name }}</div>
|
|
305
|
+
<p class="mt-0.5 line-clamp-2 text-sm text-muted-foreground">{{ tpl.description }}</p>
|
|
306
|
+
</div>
|
|
307
|
+
</button>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div class="flex items-center justify-between pt-1">
|
|
311
|
+
<NuxtLink to="/integrations/templates" class="inline-flex items-center gap-1 text-sm text-muted-foreground transition-colors hover:text-primary" @click="open = false">
|
|
312
|
+
<Sparkles :size="13" />
|
|
313
|
+
Browse the full template catalogue
|
|
314
|
+
</NuxtLink>
|
|
315
|
+
<Button type="button" variant="outline" class="h-8 text-sm" @click="open = false">
|
|
316
|
+
Cancel
|
|
317
|
+
</Button>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
</DialogContent>
|
|
321
|
+
</Dialog>
|
|
322
|
+
</template>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type __VLS_PublicProps = {
|
|
2
|
+
'open'?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
5
|
+
"update:open": (value: boolean) => any;
|
|
6
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
7
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
8
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Workflow } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
workflow: {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
} | null;
|
|
7
|
+
};
|
|
8
|
+
type __VLS_PublicProps = __VLS_Props & {
|
|
9
|
+
'open'?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:open": (value: boolean) => any;
|
|
13
|
+
duplicated: (copy: Workflow) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
15
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
16
|
+
onDuplicated?: ((copy: Workflow) => any) | undefined;
|
|
17
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { RefreshCircle as Loader2 } from "#solar-icons";
|
|
4
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input } from "@revenexx/studio";
|
|
5
|
+
import { useIntegrationsWorkflows } from "../../composables/useIntegrationsWorkflows";
|
|
6
|
+
const open = defineModel("open", { type: Boolean, ...{ default: false } });
|
|
7
|
+
const props = defineProps({
|
|
8
|
+
workflow: { type: [Object, null], required: true }
|
|
9
|
+
});
|
|
10
|
+
const emit = defineEmits(["duplicated"]);
|
|
11
|
+
const { duplicate } = useIntegrationsWorkflows();
|
|
12
|
+
const name = ref("");
|
|
13
|
+
const busy = ref(false);
|
|
14
|
+
const error = ref(null);
|
|
15
|
+
watch(open, (o) => {
|
|
16
|
+
if (o) {
|
|
17
|
+
name.value = props.workflow ? `${props.workflow.name} (Copy)` : "";
|
|
18
|
+
error.value = null;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const canSubmit = computed(() => !!props.workflow && name.value.trim().length > 0 && !busy.value);
|
|
22
|
+
async function submit() {
|
|
23
|
+
if (!props.workflow || !canSubmit.value) return;
|
|
24
|
+
busy.value = true;
|
|
25
|
+
error.value = null;
|
|
26
|
+
try {
|
|
27
|
+
const copy = await duplicate(props.workflow.id, name.value);
|
|
28
|
+
open.value = false;
|
|
29
|
+
emit("duplicated", copy);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
error.value = e?.message || "Could not duplicate the workflow.";
|
|
32
|
+
} finally {
|
|
33
|
+
busy.value = false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<Dialog v-model:open="open">
|
|
40
|
+
<DialogContent class="sm:max-w-md">
|
|
41
|
+
<DialogHeader>
|
|
42
|
+
<DialogTitle>Duplicate workflow</DialogTitle>
|
|
43
|
+
<DialogDescription class="text-sm">
|
|
44
|
+
Creates a paused copy of “{{ props.workflow?.name }}” with all triggers off.
|
|
45
|
+
</DialogDescription>
|
|
46
|
+
</DialogHeader>
|
|
47
|
+
|
|
48
|
+
<form class="space-y-2" @submit.prevent="submit">
|
|
49
|
+
<label class="text-sm font-medium" for="wf-duplicate-name">Name</label>
|
|
50
|
+
<Input id="wf-duplicate-name" v-model="name" autofocus :disabled="busy" />
|
|
51
|
+
<p v-if="error" class="text-sm text-rose-600">{{ error }}</p>
|
|
52
|
+
</form>
|
|
53
|
+
|
|
54
|
+
<DialogFooter>
|
|
55
|
+
<Button variant="outline" size="sm" class="h-8 text-sm" :disabled="busy" @click="open = false">Cancel</Button>
|
|
56
|
+
<Button size="sm" class="h-8 text-sm" :disabled="!canSubmit" @click="submit">
|
|
57
|
+
<Loader2 v-if="busy" :size="14" class="animate-spin" />
|
|
58
|
+
Duplicate
|
|
59
|
+
</Button>
|
|
60
|
+
</DialogFooter>
|
|
61
|
+
</DialogContent>
|
|
62
|
+
</Dialog>
|
|
63
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Workflow } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
workflow: {
|
|
4
|
+
id: number;
|
|
5
|
+
name: string;
|
|
6
|
+
} | null;
|
|
7
|
+
};
|
|
8
|
+
type __VLS_PublicProps = __VLS_Props & {
|
|
9
|
+
'open'?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
12
|
+
"update:open": (value: boolean) => any;
|
|
13
|
+
duplicated: (copy: Workflow) => any;
|
|
14
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
15
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
16
|
+
onDuplicated?: ((copy: Workflow) => any) | undefined;
|
|
17
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
18
|
+
export default _default;
|
|
@@ -2,12 +2,14 @@ import { type EdgeProps } from '@vue-flow/core';
|
|
|
2
2
|
/**
|
|
3
3
|
* Enterprise-style smoothstep edge:
|
|
4
4
|
*
|
|
5
|
-
* -
|
|
5
|
+
* - hard right-angle routing (`getSmoothStepPath`, radius 0)
|
|
6
6
|
* - arrow-head pointing into the target
|
|
7
7
|
* - color encodes the *source port kind* — branch outputs (`true`/`false`,
|
|
8
8
|
* switch cases) get a soft color, error outputs get rose, defaults stay
|
|
9
9
|
* slate
|
|
10
10
|
* - optional center label (used by the canvas to print branch names)
|
|
11
|
+
* - n8n-style midpoint tools on hover/selection: [+] splices a node into
|
|
12
|
+
* the connection (editor-provided action), [trash] deletes the edge
|
|
11
13
|
*
|
|
12
14
|
* The canvas creates these via `edgeTypes.flow = WorkflowEdgeView`. Edge
|
|
13
15
|
* data is passed through Vue Flow's `data` prop verbatim.
|
|
@@ -17,6 +19,15 @@ interface Data {
|
|
|
17
19
|
kind: EdgeKind;
|
|
18
20
|
/** Optional human label shown mid-edge (e.g. "true", "cases:paid"). */
|
|
19
21
|
label?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Run-replay overlay (WorkflowRunReplayPanel): the edge is on the path the
|
|
24
|
+
* run actually took — drawn blue and thicker. `ahead` marks the part of the
|
|
25
|
+
* route the cursor hasn't reached yet (the panel sets `animated` on the edge
|
|
26
|
+
* so the default theme dashes it).
|
|
27
|
+
*/
|
|
28
|
+
replay?: {
|
|
29
|
+
ahead: boolean;
|
|
30
|
+
};
|
|
20
31
|
}
|
|
21
32
|
type __VLS_Props = EdgeProps<Data>;
|
|
22
33
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|