@revenexx/studio-integrations 0.1.4 → 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.
Files changed (179) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  3. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  6. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  9. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  12. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  15. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  17. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  18. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  20. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  21. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  22. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  23. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  24. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  26. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  27. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  29. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  30. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  32. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  33. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  39. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  42. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  45. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  46. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  48. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  49. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  51. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  52. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  53. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  54. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  55. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  56. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  57. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  60. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  61. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  63. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  66. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  69. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  70. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  71. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  72. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  74. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  75. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  77. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  78. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  80. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  81. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  84. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  87. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  90. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  93. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  94. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  96. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  99. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  105. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  108. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  111. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  114. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  117. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  118. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  121. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  122. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  123. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  124. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  125. package/dist/runtime/composables/integrationsApi.d.ts +6 -2
  126. package/dist/runtime/composables/integrationsApi.js +5 -4
  127. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  128. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  129. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  130. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  131. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  132. package/dist/runtime/composables/useIntegrationsNodes.js +6 -3
  133. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  134. package/dist/runtime/composables/useIntegrationsRevisions.js +51 -0
  135. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  136. package/dist/runtime/composables/useIntegrationsRuns.js +92 -4
  137. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  138. package/dist/runtime/composables/useIntegrationsTemplates.js +32 -4
  139. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  140. package/dist/runtime/composables/useIntegrationsWorkflows.js +57 -1
  141. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  142. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  143. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  144. package/dist/runtime/composables/useNodeConfigResolve.js +123 -0
  145. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  146. package/dist/runtime/composables/useNodeDrag.js +10 -0
  147. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  148. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  149. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  150. package/dist/runtime/data/expressionEditor.js +40 -0
  151. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  152. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  153. package/dist/runtime/data/nodeInputData.js +62 -0
  154. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  155. package/dist/runtime/data/nodeRunData.js +91 -0
  156. package/dist/runtime/data/payloadForm.d.ts +44 -0
  157. package/dist/runtime/data/payloadForm.js +126 -0
  158. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  159. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  160. package/dist/runtime/pages/integrations/audit.vue +25 -35
  161. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  162. package/dist/runtime/pages/integrations/index.vue +133 -36
  163. package/dist/runtime/pages/integrations/runs.vue +125 -75
  164. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  165. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  166. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  167. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  168. package/dist/runtime/types/integrations.d.ts +190 -4
  169. package/dist/runtime/utils/credentialState.d.ts +28 -0
  170. package/dist/runtime/utils/credentialState.js +29 -0
  171. package/dist/runtime/utils/localize.d.ts +3 -1
  172. package/dist/runtime/utils/localize.js +1 -2
  173. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  174. package/dist/runtime/utils/statusStyle.js +15 -0
  175. package/package.json +11 -1
  176. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  177. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  178. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  179. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -0,0 +1,265 @@
1
+ <script setup>
2
+ import { computed, reactive, ref, watch } from "vue";
3
+ import { AddCircle as Plus, Copy, TrashBinMinimalistic as Trash2 } from "#solar-icons";
4
+ import {
5
+ Button,
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ Input,
12
+ Label,
13
+ Switch,
14
+ Select,
15
+ SelectContent,
16
+ SelectItem,
17
+ SelectTrigger,
18
+ SelectValue
19
+ } from "@revenexx/studio";
20
+ import { toast } from "vue-sonner";
21
+ const props = defineProps({
22
+ open: { type: Boolean, required: true },
23
+ variables: { type: Object, required: true }
24
+ });
25
+ const emit = defineEmits(["update:open", "update:variables"]);
26
+ const KEY_PATTERN = /^[a-z_][a-z0-9_]*$/i;
27
+ const TYPES = ["string", "number", "boolean", "object", "array"];
28
+ const jsonDrafts = reactive({});
29
+ const jsonErrors = reactive({});
30
+ watch(() => props.open, (o) => {
31
+ if (!o) return;
32
+ for (const k of Object.keys(jsonDrafts)) delete jsonDrafts[k];
33
+ for (const k of Object.keys(jsonErrors)) delete jsonErrors[k];
34
+ for (const [key, v] of Object.entries(props.variables)) {
35
+ if (v.type === "object" || v.type === "array") {
36
+ jsonDrafts[key] = v.default === void 0 ? "" : JSON.stringify(v.default, null, 2);
37
+ jsonErrors[key] = null;
38
+ }
39
+ }
40
+ });
41
+ const entries = computed(() => Object.entries(props.variables));
42
+ function commit(next) {
43
+ emit("update:variables", next);
44
+ }
45
+ function patchVar(key, patch) {
46
+ const cur = props.variables[key];
47
+ if (!cur) return;
48
+ commit({ ...props.variables, [key]: { ...cur, ...patch } });
49
+ }
50
+ function removeVar(key) {
51
+ const next = { ...props.variables };
52
+ delete next[key];
53
+ delete jsonDrafts[key];
54
+ delete jsonErrors[key];
55
+ commit(next);
56
+ }
57
+ function changeType(key, type) {
58
+ const blank = {
59
+ string: "",
60
+ number: 0,
61
+ boolean: false,
62
+ object: void 0,
63
+ array: void 0
64
+ };
65
+ patchVar(key, { type, default: blank[type] });
66
+ if (type === "object" || type === "array") {
67
+ jsonDrafts[key] = "";
68
+ jsonErrors[key] = null;
69
+ }
70
+ }
71
+ function applyJsonDraft(key, type) {
72
+ const raw = (jsonDrafts[key] ?? "").trim();
73
+ if (!raw) {
74
+ jsonErrors[key] = null;
75
+ patchVar(key, { default: void 0 });
76
+ return;
77
+ }
78
+ try {
79
+ const parsed = JSON.parse(raw);
80
+ const ok = type === "array" ? Array.isArray(parsed) : parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
81
+ if (!ok) {
82
+ jsonErrors[key] = type === "array" ? "Must be a JSON array." : "Must be a JSON object.";
83
+ return;
84
+ }
85
+ jsonErrors[key] = null;
86
+ patchVar(key, { default: parsed });
87
+ } catch {
88
+ jsonErrors[key] = "Not valid JSON.";
89
+ }
90
+ }
91
+ function descriptionText(v) {
92
+ const d = v.description;
93
+ if (!d) return "";
94
+ return typeof d === "string" ? d : d.en ?? Object.values(d)[0] ?? "";
95
+ }
96
+ function writeDescription(key, text) {
97
+ const cur = props.variables[key];
98
+ if (!cur) return;
99
+ if (!text.trim()) {
100
+ const { description: _drop, ...rest } = cur;
101
+ commit({ ...props.variables, [key]: rest });
102
+ return;
103
+ }
104
+ const existing = typeof cur.description === "object" && cur.description !== null ? cur.description : {};
105
+ patchVar(key, { description: { ...existing, en: text } });
106
+ }
107
+ const newKey = ref("");
108
+ const newKeyError = computed(() => {
109
+ const k = newKey.value.trim();
110
+ if (!k) return null;
111
+ if (!KEY_PATTERN.test(k)) return "Letters, digits and _ only; must not start with a digit.";
112
+ if (props.variables[k]) return "Key already exists.";
113
+ return null;
114
+ });
115
+ function addVariable() {
116
+ const k = newKey.value.trim();
117
+ if (!k || newKeyError.value) return;
118
+ commit({ ...props.variables, [k]: { type: "string", default: "" } });
119
+ newKey.value = "";
120
+ }
121
+ async function copyUsage(key) {
122
+ try {
123
+ await navigator.clipboard.writeText(`\${{ vars.${key} }}`);
124
+ toast.success("Expression copied");
125
+ } catch {
126
+ toast.error("Could not copy");
127
+ }
128
+ }
129
+ </script>
130
+
131
+ <template>
132
+ <Dialog :open="open" @update:open="(v2) => emit('update:open', v2)">
133
+ <DialogContent class="max-w-[94vw] sm:max-w-3xl">
134
+ <DialogHeader>
135
+ <DialogTitle>Workflow variables</DialogTitle>
136
+ <DialogDescription class="text-sm">
137
+ Reusable values for this workflow — reference them in any config field as
138
+ <code v-pre class="rounded bg-black/[0.05] px-1 py-0.5 font-mono text-xs">${{ vars.key }}</code>.
139
+ Changes persist with the workflow Save.
140
+ </DialogDescription>
141
+ </DialogHeader>
142
+
143
+ <div class="max-h-[62vh] space-y-3 overflow-y-auto pr-1">
144
+ <p v-if="!entries.length" class="rounded-lg border border-dashed border-black/[0.12] px-4 py-6 text-center text-sm text-muted-foreground">
145
+ No variables yet — add the first one below.
146
+ </p>
147
+
148
+ <div
149
+ v-for="[key, v] in entries"
150
+ :key="key"
151
+ class="rounded-lg border border-black/[0.06] bg-white p-3"
152
+ >
153
+ <div class="flex items-center gap-2">
154
+ <code class="min-w-0 truncate font-mono text-sm font-semibold">{{ key }}</code>
155
+ <button
156
+ type="button"
157
+ class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
158
+ :title="`Copy \${{ vars.${key} }}`"
159
+ @click="copyUsage(key)"
160
+ >
161
+ <Copy :size="12" />
162
+ </button>
163
+ <div class="flex-1" />
164
+ <label class="flex items-center gap-1.5 text-sm text-muted-foreground">
165
+ Required
166
+ <Switch
167
+ :model-value="v.required === true"
168
+ @update:model-value="(val) => patchVar(key, { required: val || void 0 })"
169
+ />
170
+ </label>
171
+ <button
172
+ type="button"
173
+ class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
174
+ title="Delete variable"
175
+ @click="removeVar(key)"
176
+ >
177
+ <Trash2 :size="13" />
178
+ </button>
179
+ </div>
180
+
181
+ <div class="mt-2 grid gap-2 sm:grid-cols-[8rem_1fr]">
182
+ <div class="space-y-1">
183
+ <Label class="text-mono text-muted-foreground">Type</Label>
184
+ <Select :model-value="v.type" @update:model-value="(t) => changeType(key, t)">
185
+ <SelectTrigger class="h-8 bg-white text-sm">
186
+ <SelectValue />
187
+ </SelectTrigger>
188
+ <SelectContent>
189
+ <SelectItem v-for="t in TYPES" :key="t" :value="t">{{ t }}</SelectItem>
190
+ </SelectContent>
191
+ </Select>
192
+ </div>
193
+
194
+ <div class="space-y-1">
195
+ <Label class="text-mono text-muted-foreground">
196
+ Default{{ v.required ? " (ignored while required)" : "" }}
197
+ </Label>
198
+
199
+ <Input
200
+ v-if="v.type === 'string'"
201
+ :model-value="typeof v.default === 'string' ? v.default : ''"
202
+ class="h-8 bg-white text-sm"
203
+ @update:model-value="(val) => patchVar(key, { default: String(val) })"
204
+ />
205
+ <Input
206
+ v-else-if="v.type === 'number'"
207
+ type="number"
208
+ :model-value="typeof v.default === 'number' ? v.default : ''"
209
+ class="h-8 bg-white text-sm"
210
+ @update:model-value="(val) => patchVar(key, { default: val === '' ? void 0 : Number(val) })"
211
+ />
212
+ <div v-else-if="v.type === 'boolean'" class="flex h-8 items-center">
213
+ <Switch
214
+ :model-value="v.default === true"
215
+ @update:model-value="(val) => patchVar(key, { default: val })"
216
+ />
217
+ </div>
218
+ <div v-else>
219
+ <textarea
220
+ :value="jsonDrafts[key] ?? ''"
221
+ rows="3"
222
+ spellcheck="false"
223
+ class="w-full rounded-md border bg-white p-2 font-mono text-xs focus:outline-none focus:ring-2 focus:ring-primary/40"
224
+ :class="jsonErrors[key] ? 'border-rose-400' : 'border-black/[0.08]'"
225
+ :placeholder="v.type === 'array' ? 'JSON array, e.g. [1, 2]' : 'JSON object, e.g. { \u2026 }'"
226
+ @input="(e) => jsonDrafts[key] = e.target.value"
227
+ @blur="applyJsonDraft(key, v.type)"
228
+ />
229
+ <p v-if="jsonErrors[key]" class="text-xs text-rose-600">{{ jsonErrors[key] }}</p>
230
+ </div>
231
+ </div>
232
+ </div>
233
+
234
+ <div class="mt-2 space-y-1">
235
+ <Label class="text-mono text-muted-foreground">Description</Label>
236
+ <Input
237
+ :model-value="descriptionText(v)"
238
+ class="h-8 bg-white text-sm"
239
+ placeholder="What this variable is for"
240
+ @update:model-value="(val) => writeDescription(key, String(val))"
241
+ />
242
+ </div>
243
+ </div>
244
+
245
+ <!-- Add row -->
246
+ <div class="rounded-lg border border-dashed border-black/[0.12] p-3">
247
+ <Label class="text-mono text-muted-foreground">New variable</Label>
248
+ <div class="mt-1 flex items-center gap-2">
249
+ <Input
250
+ v-model="newKey"
251
+ class="h-8 flex-1 bg-white font-mono text-sm"
252
+ placeholder="e.g. base_url"
253
+ @keydown.enter.prevent="addVariable"
254
+ />
255
+ <Button size="sm" class="h-8" :disabled="!newKey.trim() || !!newKeyError" @click="addVariable">
256
+ <Plus :size="14" class="mr-1" />
257
+ Add
258
+ </Button>
259
+ </div>
260
+ <p v-if="newKeyError" class="mt-1 text-xs text-rose-600">{{ newKeyError }}</p>
261
+ </div>
262
+ </div>
263
+ </DialogContent>
264
+ </Dialog>
265
+ </template>
@@ -0,0 +1,13 @@
1
+ import type { WorkflowVariable } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ variables: Record<string, WorkflowVariable>;
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:variables": (next: Record<string, WorkflowVariable>) => any;
9
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
+ "onUpdate:open"?: ((args_0: boolean) => any) | undefined;
11
+ "onUpdate:variables"?: ((next: Record<string, WorkflowVariable>) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ export default _default;
@@ -1,6 +1,9 @@
1
1
  import type { Workflow as WorkflowResource } from '../../types/integrations.js';
2
2
  type __VLS_Props = {
3
3
  workflows: WorkflowResource[];
4
+ loading?: boolean;
4
5
  };
5
- 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>;
6
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
7
+ loading: boolean;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
9
  export default _default;
@@ -1,5 +1,9 @@
1
1
  <script setup>
2
- import { ClockCircle as Clock, Globus as Globe, RefreshCircle as Loader2, MenuDots as MoreHorizontal, Pause, Play, TrashBinMinimalistic as Trash2, LinkRound as Webhook, Programming as Workflow, Bolt } from "#solar-icons";
2
+ import { RefreshCircle as Loader2, MenuDots as MoreHorizontal } from "#solar-icons";
3
+ import { toast } from "vue-sonner";
4
+ import WorkflowDuplicateDialog from "./WorkflowDuplicateDialog.vue";
5
+ import WorkflowIconView from "./WorkflowIconView.vue";
6
+ import { Layers } from "#solar-icons";
3
7
  import {
4
8
  AlertDialog,
5
9
  AlertDialogContent,
@@ -17,10 +21,33 @@ import {
17
21
  DropdownMenuTrigger
18
22
  } from "@revenexx/studio";
19
23
  defineProps({
20
- workflows: { type: Array, required: true }
24
+ workflows: { type: Array, required: true },
25
+ loading: { type: Boolean, required: false, default: false }
21
26
  });
22
27
  const router = useRouter();
23
28
  const { remove, toggleActive } = useIntegrationsWorkflows();
29
+ const duplicateTarget = ref(null);
30
+ const duplicateOpen = ref(false);
31
+ function askDuplicate(wf) {
32
+ duplicateTarget.value = { id: wf.id, name: wf.name };
33
+ duplicateOpen.value = true;
34
+ }
35
+ async function onDuplicated(copy) {
36
+ toast.success(`Duplicated as \u201C${copy.name}\u201D`);
37
+ await router.push(`/integrations/workflows/${copy.id}`);
38
+ }
39
+ const GROUP_BY_TAGS_KEY = "integrations.workflows.groupByTags";
40
+ const groupByTags = ref(false);
41
+ try {
42
+ groupByTags.value = localStorage.getItem(GROUP_BY_TAGS_KEY) === "1";
43
+ } catch {
44
+ }
45
+ watch(groupByTags, (on) => {
46
+ try {
47
+ localStorage.setItem(GROUP_BY_TAGS_KEY, on ? "1" : "0");
48
+ } catch {
49
+ }
50
+ });
24
51
  const { runs, load: loadRuns, list: listRuns } = useIntegrationsRuns();
25
52
  onMounted(() => {
26
53
  void loadRuns();
@@ -59,14 +86,37 @@ async function confirmDelete() {
59
86
  deleting.value = false;
60
87
  }
61
88
  }
62
- function triggerIcon(type) {
63
- return type === "schedule" ? Clock : type === "webhook" ? Webhook : type === "event" ? Bolt : Play;
64
- }
65
89
  function statusBadge(wf) {
66
90
  if (!wf.active) return { label: "Paused", class: "bg-slate-100 text-slate-700 border-slate-200" };
67
91
  if (wf.revision === 0) return { label: "Draft", class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
68
92
  return { label: "Active", class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
69
93
  }
94
+ function buildState(wf) {
95
+ switch (wf.buildStatus) {
96
+ case "building":
97
+ return { label: "Building\u2026", class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
98
+ case "pending":
99
+ return { label: "Pending", class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
100
+ case "failed":
101
+ return { label: "Failed", class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
102
+ case "ready":
103
+ return { label: "Ready", class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
104
+ default:
105
+ return { label: "\u2014", class: "bg-slate-100 text-slate-700 border-slate-200" };
106
+ }
107
+ }
108
+ function modeLabel(m) {
109
+ switch (m) {
110
+ case "sync_only":
111
+ return "sync";
112
+ case "caller_decides":
113
+ return "caller";
114
+ case "async_only":
115
+ return "async";
116
+ default:
117
+ return "\u2014";
118
+ }
119
+ }
70
120
  function formatDateTime(iso) {
71
121
  if (!iso) return "\u2014";
72
122
  try {
@@ -86,7 +136,7 @@ const columns = [
86
136
  label: "Name",
87
137
  sortable: true,
88
138
  sortGetter: (w) => w.name.toLowerCase(),
89
- searchGetter: (w) => `${w.name} ${w.description ?? ""}`
139
+ searchGetter: (w) => `${w.name} ${w.description ?? ""} ${(w.blob.tags ?? []).join(" ")}`
90
140
  },
91
141
  {
92
142
  key: "status",
@@ -96,30 +146,32 @@ const columns = [
96
146
  sortGetter: (w) => w.active ? w.revision === 0 ? "draft" : "active" : "paused"
97
147
  },
98
148
  {
99
- key: "triggers",
100
- label: "Triggers",
101
- width: "140px",
149
+ key: "build",
150
+ label: "Build",
151
+ width: "120px",
102
152
  sortable: true,
103
- sortGetter: (w) => w.blob.triggers.map((t) => t.type).join(",")
153
+ sortGetter: (w) => w.buildStatus ?? ""
104
154
  },
105
155
  {
106
- key: "nodes",
107
- label: "Nodes",
108
- width: "100px",
156
+ key: "mode",
157
+ label: "Mode",
158
+ width: "120px",
109
159
  sortable: true,
110
- sortGetter: (w) => w.blob.nodes.length
160
+ sortGetter: (w) => w.execution_mode ?? ""
111
161
  },
112
162
  {
113
- key: "revision",
114
- label: "Revision",
115
- width: "100px",
163
+ key: "triggers",
164
+ label: "Triggers",
165
+ width: "140px",
116
166
  sortable: true,
117
- sortGetter: (w) => w.revision
167
+ sortGetter: (w) => w.blob.triggers.map((t) => t.type).join(",")
118
168
  },
119
169
  {
120
170
  key: "lastRun",
121
171
  label: "Last run",
122
- width: "150px",
172
+ // Wide enough for badge + full timestamp — the flexible Name column has
173
+ // the slack to give; a truncated "18. Juli, 1…" helps nobody.
174
+ width: "210px",
123
175
  sortable: true,
124
176
  sortGetter: (w) => lastRunFor(w.id)?.startedAt ?? ""
125
177
  },
@@ -142,59 +194,86 @@ const columns = [
142
194
  <template>
143
195
  <DataTable
144
196
  :data="workflows"
197
+ :group-by="groupByTags ? (w) => w.blob.tags ?? [] : null"
198
+ group-empty-label="No tags"
145
199
  :columns="columns"
146
200
  :row-key="(wf2) => wf2.id"
147
201
  :default-sort="{ column: 'updated', direction: 'desc' }"
148
202
  :search="{ placeholder: 'Search workflows\u2026' }"
149
203
  :filter="true"
204
+ :loading="loading"
150
205
  empty-text="No workflows yet."
151
206
  @row-click="(wf2) => router.push(`/integrations/workflows/${wf2.id}`)"
152
207
  >
208
+ <template #toolbar-left>
209
+ <Button
210
+ variant="outline"
211
+ size="sm"
212
+ class="h-7 gap-1.5 text-sm"
213
+ :class="groupByTags ? 'border-primary/30 text-primary' : ''"
214
+ @click="groupByTags = !groupByTags"
215
+ >
216
+ <Layers :size="13" />
217
+ Group by tags
218
+ </Button>
219
+ </template>
220
+
153
221
  <!-- Name (icon + title + description) -->
154
222
  <template #cell-name="{ row: wf }">
155
223
  <div class="flex items-center gap-2 min-w-0">
156
- <div class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
157
- <Workflow :size="14" />
224
+ <div class="flex size-8 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
225
+ <WorkflowIconView :icon="wf.blob.icon" :size="18" />
158
226
  </div>
159
227
  <div class="flex flex-col min-w-0">
160
228
  <span class="font-medium text-sm truncate">{{ wf.name }}</span>
161
229
  <span v-if="wf.description" class="text-sm text-muted-foreground truncate">{{ wf.description }}</span>
230
+ <div v-if="wf.blob.tags?.length" class="mt-0.5 flex flex-wrap gap-1">
231
+ <span
232
+ v-for="t in wf.blob.tags"
233
+ :key="t"
234
+ class="rounded border border-black/[0.06] bg-muted/50 px-1 text-[10px] leading-4 text-muted-foreground"
235
+ >{{ t }}</span>
236
+ </div>
162
237
  </div>
163
238
  </div>
164
239
  </template>
165
240
 
166
241
  <template #cell-status="{ row: wf }">
167
- <Badge variant="outline" :class="['text-sm border', statusBadge(wf).class]">
242
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', statusBadge(wf).class]">
168
243
  {{ statusBadge(wf).label }}
169
244
  </Badge>
170
245
  </template>
171
246
 
247
+ <template #cell-build="{ row: wf }">
248
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase', buildState(wf).class]">
249
+ {{ buildState(wf).label }}
250
+ </Badge>
251
+ </template>
252
+
253
+ <template #cell-mode="{ row: wf }">
254
+ <Badge v-if="wf.execution_mode" variant="outline" class="border text-xs font-medium uppercase text-muted-foreground">
255
+ {{ modeLabel(wf.execution_mode) }}
256
+ </Badge>
257
+ <span v-else class="text-sm text-muted-foreground">—</span>
258
+ </template>
259
+
260
+ <!-- Small badges, stacked vertically — several triggers must never
261
+ overflow the column into their neighbors. -->
172
262
  <template #cell-triggers="{ row: wf }">
173
- <div class="flex items-center gap-1 text-sm text-muted-foreground">
263
+ <div class="flex min-w-0 flex-col items-start gap-1">
174
264
  <span
175
265
  v-for="t in wf.blob.triggers"
176
266
  :key="t.id"
177
- class="inline-flex items-center gap-1 rounded-md bg-muted/60 px-1.5 py-0.5"
267
+ class="inline-flex items-center whitespace-nowrap rounded-md border border-black/[0.06] bg-muted/50 px-1.5 py-0.5 text-xs font-medium uppercase text-muted-foreground"
178
268
  >
179
- <component :is="triggerIcon(t.type)" :size="11" />
180
269
  {{ t.type }}
181
270
  </span>
182
271
  </div>
183
272
  </template>
184
273
 
185
- <template #cell-nodes="{ row: wf }">
186
- <span class="text-sm text-muted-foreground tabular-nums">
187
- {{ wf.blob.nodes.length }} node<span v-if="wf.blob.nodes.length !== 1">s</span>
188
- </span>
189
- </template>
190
-
191
- <template #cell-revision="{ row: wf }">
192
- <span class="text-sm text-muted-foreground tabular-nums">r{{ wf.revision }}</span>
193
- </template>
194
-
195
274
  <template #cell-lastRun="{ row: wf }">
196
275
  <div v-if="lastRunFor(wf.id)" class="flex items-center gap-2 min-w-0">
197
- <Badge variant="outline" :class="['text-sm border shrink-0', runBadge(lastRunFor(wf.id).status).class]">
276
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0', runBadge(lastRunFor(wf.id).status).class]">
198
277
  {{ runBadge(lastRunFor(wf.id).status).label }}
199
278
  </Badge>
200
279
  <span class="text-sm tabular-nums text-muted-foreground truncate">
@@ -222,10 +301,12 @@ const columns = [
222
301
  <DropdownMenuLabel>Actions</DropdownMenuLabel>
223
302
  <DropdownMenuSeparator />
224
303
  <DropdownMenuItem @select="router.push(`/integrations/workflows/${wf.id}`)">
225
- <Globe :size="14" /> Open editor
304
+ Open editor
305
+ </DropdownMenuItem>
306
+ <DropdownMenuItem @select="askDuplicate(wf)">
307
+ Duplicate
226
308
  </DropdownMenuItem>
227
309
  <DropdownMenuItem @select="toggleActive(wf.id)">
228
- <component :is="wf.active ? Pause : Play" :size="14" />
229
310
  {{ wf.active ? "Pause" : "Activate" }}
230
311
  </DropdownMenuItem>
231
312
  <DropdownMenuSeparator />
@@ -233,7 +314,7 @@ const columns = [
233
314
  class="text-destructive focus:text-destructive"
234
315
  @select="askDelete(wf)"
235
316
  >
236
- <Trash2 :size="14" /> Delete
317
+ Delete
237
318
  </DropdownMenuItem>
238
319
  </DropdownMenuContent>
239
320
  </DropdownMenu>
@@ -265,4 +346,6 @@ const columns = [
265
346
  </AlertDialogFooter>
266
347
  </AlertDialogContent>
267
348
  </AlertDialog>
349
+
350
+ <WorkflowDuplicateDialog v-model:open="duplicateOpen" :workflow="duplicateTarget" @duplicated="onDuplicated" />
268
351
  </template>
@@ -1,6 +1,9 @@
1
1
  import type { Workflow as WorkflowResource } from '../../types/integrations.js';
2
2
  type __VLS_Props = {
3
3
  workflows: WorkflowResource[];
4
+ loading?: boolean;
4
5
  };
5
- 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>;
6
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
7
+ loading: boolean;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
9
  export default _default;
@@ -14,4 +14,9 @@ export declare function layoutWorkflowNodes(nodes: VFNode[], edges: VFEdge[], op
14
14
  nodeHeight?: number;
15
15
  rankSeparation?: number;
16
16
  nodeSeparation?: number;
17
+ /** Per-node size overrides (e.g. group clusters laid out as one unit). */
18
+ sizes?: Record<string, {
19
+ width: number;
20
+ height: number;
21
+ }>;
17
22
  }): VFNode[];
@@ -8,8 +8,9 @@ export function layoutWorkflowNodes(nodes, edges, options = {}) {
8
8
  const g = new dagre.graphlib.Graph();
9
9
  g.setDefaultEdgeLabel(() => ({}));
10
10
  g.setGraph({ rankdir: dir, ranksep, nodesep, marginx: 20, marginy: 20 });
11
+ const sizeOf = (id) => options.sizes?.[id] ?? { width: nodeWidth, height: nodeHeight };
11
12
  for (const n of nodes) {
12
- g.setNode(n.id, { width: nodeWidth, height: nodeHeight });
13
+ g.setNode(n.id, sizeOf(n.id));
13
14
  }
14
15
  for (const e of edges) {
15
16
  g.setEdge(e.source, e.target);
@@ -18,11 +19,12 @@ export function layoutWorkflowNodes(nodes, edges, options = {}) {
18
19
  return nodes.map((n) => {
19
20
  const laid = g.node(n.id);
20
21
  if (!laid) return n;
22
+ const size = sizeOf(n.id);
21
23
  return {
22
24
  ...n,
23
25
  position: {
24
- x: laid.x - nodeWidth / 2,
25
- y: laid.y - nodeHeight / 2
26
+ x: laid.x - size.width / 2,
27
+ y: laid.y - size.height / 2
26
28
  }
27
29
  };
28
30
  });
@@ -5,8 +5,12 @@ export declare class IntegrationsApiError extends Error {
5
5
  }
6
6
  export declare function useIntegrationsApi(): {
7
7
  baseUrl: import("vue").ComputedRef<string>;
8
- get: <T>(path: string) => Promise<T>;
9
- post: <T>(path: string, body?: unknown) => Promise<T>;
8
+ get: <T>(path: string, opts?: {
9
+ signal?: AbortSignal;
10
+ }) => Promise<T>;
11
+ post: <T>(path: string, body?: unknown, opts?: {
12
+ signal?: AbortSignal;
13
+ }) => Promise<T>;
10
14
  put: <T>(path: string, body?: unknown) => Promise<T>;
11
15
  patch: <T>(path: string, body?: unknown) => Promise<T>;
12
16
  del: <T>(path: string) => Promise<T>;
@@ -29,13 +29,14 @@ export function useIntegrationsApi() {
29
29
  function url(path) {
30
30
  return `${baseUrl.value.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
31
31
  }
32
- async function request(method, path, body) {
32
+ async function request(method, path, body, opts) {
33
33
  const headers = await buildHeaders(body !== void 0 ? { "Content-Type": "application/json" } : {});
34
34
  try {
35
35
  return await $fetch(url(path), {
36
36
  method,
37
37
  headers,
38
- body: body !== void 0 ? body : void 0
38
+ body: body !== void 0 ? body : void 0,
39
+ signal: opts?.signal
39
40
  });
40
41
  } catch (err) {
41
42
  const fe = err;
@@ -46,8 +47,8 @@ export function useIntegrationsApi() {
46
47
  }
47
48
  return {
48
49
  baseUrl,
49
- get: (path) => request("GET", path),
50
- post: (path, body) => request("POST", path, body ?? {}),
50
+ get: (path, opts) => request("GET", path, void 0, opts),
51
+ post: (path, body, opts) => request("POST", path, body ?? {}, opts),
51
52
  put: (path, body) => request("PUT", path, body ?? {}),
52
53
  patch: (path, body) => request("PATCH", path, body ?? {}),
53
54
  del: (path) => request("DELETE", path)