@revenexx/studio-integrations 0.1.4 → 0.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.
Files changed (188) hide show
  1. package/README.md +31 -0
  2. package/dist/module.d.mts +2 -2
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
  5. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
  8. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
  9. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
  10. package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
  11. package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
  12. package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
  13. package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
  14. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
  15. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
  16. package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
  17. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
  18. package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
  19. package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
  20. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
  21. package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
  22. package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
  23. package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
  24. package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
  25. package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
  26. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
  27. package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
  28. package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
  29. package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
  30. package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
  31. package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
  32. package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
  33. package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
  34. package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
  35. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
  36. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
  37. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
  38. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
  39. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
  40. package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
  41. package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
  42. package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
  43. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
  44. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
  45. package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
  46. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
  47. package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
  48. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
  49. package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
  50. package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
  51. package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
  52. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
  53. package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
  54. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
  55. package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
  56. package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
  57. package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
  58. package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
  59. package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
  60. package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
  61. package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
  62. package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
  63. package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
  64. package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
  65. package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
  66. package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
  67. package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
  68. package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
  69. package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
  70. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
  71. package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
  72. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
  73. package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
  74. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
  75. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
  76. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
  77. package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
  78. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
  79. package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
  80. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
  81. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
  82. package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
  83. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
  84. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
  85. package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
  86. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
  87. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
  88. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
  89. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
  90. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
  91. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
  92. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
  93. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
  94. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
  95. package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
  96. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
  97. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
  98. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
  99. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
  100. package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
  101. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
  102. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
  103. package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
  104. package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
  105. package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
  106. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
  107. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
  108. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
  109. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
  110. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
  111. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
  112. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
  113. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
  114. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
  115. package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
  116. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
  117. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
  118. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
  119. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
  120. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
  121. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
  122. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
  123. package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
  124. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
  125. package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
  126. package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
  127. package/dist/runtime/composables/integrationsApi.d.ts +6 -7
  128. package/dist/runtime/composables/integrationsApi.js +6 -12
  129. package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
  130. package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
  131. package/dist/runtime/composables/useCredentialTypes.js +2 -1
  132. package/dist/runtime/composables/useIntegrationsAudit.js +2 -1
  133. package/dist/runtime/composables/useIntegrationsCredentials.js +2 -1
  134. package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
  135. package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
  136. package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
  137. package/dist/runtime/composables/useIntegrationsNodes.js +8 -4
  138. package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
  139. package/dist/runtime/composables/useIntegrationsRevisions.js +52 -0
  140. package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
  141. package/dist/runtime/composables/useIntegrationsRuns.js +94 -5
  142. package/dist/runtime/composables/useIntegrationsSecrets.js +2 -1
  143. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
  144. package/dist/runtime/composables/useIntegrationsTemplates.js +34 -5
  145. package/dist/runtime/composables/useIntegrationsTriggers.js +2 -1
  146. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
  147. package/dist/runtime/composables/useIntegrationsWorkflows.js +59 -2
  148. package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
  149. package/dist/runtime/composables/useMiniMapLayout.js +119 -0
  150. package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
  151. package/dist/runtime/composables/useNodeConfigResolve.js +124 -0
  152. package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
  153. package/dist/runtime/composables/useNodeDrag.js +10 -0
  154. package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
  155. package/dist/runtime/composables/usePaletteLayout.js +113 -0
  156. package/dist/runtime/data/expressionEditor.d.ts +22 -0
  157. package/dist/runtime/data/expressionEditor.js +40 -0
  158. package/dist/runtime/data/integrationNodeCatalog.js +8 -8
  159. package/dist/runtime/data/nodeInputData.d.ts +37 -0
  160. package/dist/runtime/data/nodeInputData.js +62 -0
  161. package/dist/runtime/data/nodeRunData.d.ts +56 -0
  162. package/dist/runtime/data/nodeRunData.js +91 -0
  163. package/dist/runtime/data/payloadForm.d.ts +44 -0
  164. package/dist/runtime/data/payloadForm.js +126 -0
  165. package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
  166. package/dist/runtime/data/workflowIconCatalog.js +116 -0
  167. package/dist/runtime/pages/integrations/audit.vue +25 -35
  168. package/dist/runtime/pages/integrations/credentials.vue +117 -122
  169. package/dist/runtime/pages/integrations/index.vue +133 -36
  170. package/dist/runtime/pages/integrations/runs.vue +125 -75
  171. package/dist/runtime/pages/integrations/schedules.vue +5 -16
  172. package/dist/runtime/pages/integrations/secrets.vue +1 -3
  173. package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
  174. package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
  175. package/dist/runtime/types/integrations.d.ts +190 -4
  176. package/dist/runtime/utils/apiError.d.ts +17 -0
  177. package/dist/runtime/utils/apiError.js +8 -0
  178. package/dist/runtime/utils/credentialState.d.ts +28 -0
  179. package/dist/runtime/utils/credentialState.js +29 -0
  180. package/dist/runtime/utils/localize.d.ts +3 -1
  181. package/dist/runtime/utils/localize.js +1 -2
  182. package/dist/runtime/utils/statusStyle.d.ts +6 -0
  183. package/dist/runtime/utils/statusStyle.js +15 -0
  184. package/package.json +20 -3
  185. package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
  186. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
  187. package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
  188. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
@@ -0,0 +1,587 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { AddCircle as Plus, TrashBinMinimalistic as Trash2 } from "#solar-icons";
4
+ import {
5
+ Button,
6
+ Input,
7
+ Label,
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ Switch,
14
+ Textarea
15
+ } from "@revenexx/studio";
16
+ const props = defineProps({
17
+ field: { type: Object, required: true },
18
+ config: { type: Object, required: true },
19
+ slug: { type: String, required: true },
20
+ version: { type: String, required: true },
21
+ secretKeys: { type: Array, required: true },
22
+ staticKeys: { type: Array, required: false },
23
+ depth: { type: Number, required: false },
24
+ variables: { type: Object, required: false },
25
+ upstreamOutputs: { type: Array, required: false }
26
+ });
27
+ const emit = defineEmits(["set"]);
28
+ const value = computed(() => props.config[props.field.key]);
29
+ function write(v) {
30
+ emit("set", props.field.key, v);
31
+ }
32
+ function labelText(v) {
33
+ if (!v) return "";
34
+ return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
35
+ }
36
+ const exprHint = "${{ \u2026 }}";
37
+ const descriptionText = computed(() => labelText(props.field.description));
38
+ function isEmpty(v) {
39
+ return v === void 0 || v === null || v === "" || Array.isArray(v) && v.length === 0;
40
+ }
41
+ const requiredMissing = computed(() => !!props.field.required && isEmpty(value.value));
42
+ const formatError = computed(() => {
43
+ const v = value.value;
44
+ if (isEmpty(v)) return null;
45
+ const val = props.field.validation;
46
+ if (!val) return null;
47
+ if (typeof v === "string") {
48
+ if (val.minLength != null && v.length < val.minLength) return `Must be at least ${val.minLength} characters`;
49
+ if (val.maxLength != null && v.length > val.maxLength) return `Must be at most ${val.maxLength} characters`;
50
+ if (val.pattern) {
51
+ try {
52
+ if (!new RegExp(val.pattern).test(v)) return "Does not match the required format";
53
+ } catch {
54
+ }
55
+ }
56
+ }
57
+ if (typeof v === "number") {
58
+ if (val.min != null && v < val.min) return `Must be \u2265 ${val.min}`;
59
+ if (val.max != null && v > val.max) return `Must be \u2264 ${val.max}`;
60
+ }
61
+ return null;
62
+ });
63
+ const invalid = computed(() => !!formatError.value);
64
+ const constraintHint = computed(() => {
65
+ const val = props.field.validation;
66
+ if (!val) return "";
67
+ if (props.field.type === "number") {
68
+ if (val.min != null && val.max != null) return `${val.min}\u2013${val.max}`;
69
+ if (val.min != null) return `\u2265 ${val.min}`;
70
+ if (val.max != null) return `\u2264 ${val.max}`;
71
+ } else {
72
+ if (val.minLength != null && val.maxLength != null) return `${val.minLength}\u2013${val.maxLength} characters`;
73
+ if (val.minLength != null) return `at least ${val.minLength} characters`;
74
+ if (val.maxLength != null) return `up to ${val.maxLength} characters`;
75
+ }
76
+ return "";
77
+ });
78
+ const { credentials, load: loadCredentials } = useIntegrationsCredentials();
79
+ onMounted(() => {
80
+ void loadCredentials();
81
+ });
82
+ function credsOfType(slug) {
83
+ if (!slug) return credentials.value;
84
+ const slugs = (Array.isArray(slug) ? slug : [slug]).map((s) => s.trim()).filter(Boolean);
85
+ if (!slugs.length) return credentials.value;
86
+ const wanted = new Set(slugs);
87
+ return credentials.value.filter((c) => wanted.has(c.credential_type_slug));
88
+ }
89
+ function credsForField(field) {
90
+ const list = credsOfType(field.credentialType);
91
+ const selectedId = value.value;
92
+ if (selectedId && !list.some((c) => c.id === selectedId)) {
93
+ const sel = credentials.value.find((c) => c.id === selectedId);
94
+ if (sel) return [sel, ...list];
95
+ }
96
+ return list;
97
+ }
98
+ function credNeedsReauth(id) {
99
+ return credentials.value.some((c) => c.id === id && c.status === "needs_reauth");
100
+ }
101
+ const credCreateOpen = ref(false);
102
+ const sftpImportOpen = ref(false);
103
+ function acceptsSftp(field) {
104
+ const t = field.credentialType;
105
+ return t === "revenexx:sftp" || Array.isArray(t) && t.includes("revenexx:sftp");
106
+ }
107
+ const EXPR_TOKEN = "${{";
108
+ const canToggleExpression = computed(
109
+ () => !!props.field.dynamic || ["number", "boolean", "select", "multiselect"].includes(props.field.type)
110
+ );
111
+ const exprMode = ref(false);
112
+ const valueIsExpression = computed(() => typeof value.value === "string" && value.value.includes(EXPR_TOKEN));
113
+ const showExpression = computed(() => canToggleExpression.value && (exprMode.value || valueIsExpression.value));
114
+ function toggleExpression() {
115
+ if (showExpression.value) {
116
+ if (valueIsExpression.value) write(void 0);
117
+ exprMode.value = false;
118
+ } else {
119
+ exprMode.value = true;
120
+ }
121
+ }
122
+ const isObjectField = computed(() => props.field.type === "object");
123
+ function isFlatObject(v) {
124
+ return !!v && typeof v === "object" && !Array.isArray(v) && Object.values(v).every((x) => x === null || ["string", "number", "boolean"].includes(typeof x));
125
+ }
126
+ const flatCapable = computed(() => {
127
+ const v = value.value;
128
+ return v === void 0 || v === null || isFlatObject(v);
129
+ });
130
+ const objectMode = ref("form");
131
+ watch(flatCapable, (flat) => {
132
+ if (!flat) objectMode.value = "json";
133
+ }, { immediate: true });
134
+ const kvRows = ref([]);
135
+ function rowsToObject(rows) {
136
+ const out = {};
137
+ for (const r of rows) {
138
+ if (r.key.trim() !== "") out[r.key.trim()] = r.value;
139
+ }
140
+ return out;
141
+ }
142
+ watch(
143
+ () => props.config[props.field.key],
144
+ (v) => {
145
+ if (!isObjectField.value) return;
146
+ const external = v && typeof v === "object" && !Array.isArray(v) ? v : {};
147
+ if (JSON.stringify(rowsToObject(kvRows.value)) !== JSON.stringify(external)) {
148
+ kvRows.value = Object.entries(external).map(([k, val]) => ({
149
+ key: k,
150
+ value: typeof val === "string" ? val : JSON.stringify(val)
151
+ }));
152
+ }
153
+ },
154
+ { immediate: true }
155
+ );
156
+ function commitKvRows() {
157
+ const obj = rowsToObject(kvRows.value);
158
+ write(Object.keys(obj).length || kvRows.value.length ? obj : void 0);
159
+ }
160
+ function setKvRow(idx, patch) {
161
+ kvRows.value = kvRows.value.map((r, i) => i === idx ? { ...r, ...patch } : r);
162
+ commitKvRows();
163
+ }
164
+ function addKvRow() {
165
+ kvRows.value = [...kvRows.value, { key: "", value: "" }];
166
+ }
167
+ function removeKvRow(idx) {
168
+ kvRows.value = kvRows.value.filter((_, i) => i !== idx);
169
+ commitKvRows();
170
+ }
171
+ const isQueryField = computed(
172
+ () => props.field.type === "array" && props.field.items?.type !== "object" && /(^|__)queries$/.test(props.field.key)
173
+ );
174
+ const isJsonField = computed(
175
+ () => isObjectField.value && objectMode.value === "json" || props.field.type === "array" && props.field.items?.type !== "object" && !isQueryField.value
176
+ );
177
+ const jsonError = ref(null);
178
+ const jsonDraft = ref("");
179
+ watch(
180
+ () => [props.config[props.field.key], isJsonField.value],
181
+ ([v]) => {
182
+ if (!isJsonField.value) return;
183
+ const current = (() => {
184
+ try {
185
+ return JSON.parse(jsonDraft.value);
186
+ } catch {
187
+ return /* @__PURE__ */ Symbol("invalid");
188
+ }
189
+ })();
190
+ if (JSON.stringify(current) !== JSON.stringify(v)) {
191
+ jsonDraft.value = v === void 0 || v === null ? "" : JSON.stringify(v, null, 2);
192
+ jsonError.value = null;
193
+ }
194
+ },
195
+ { immediate: true }
196
+ );
197
+ function writeJson(text) {
198
+ jsonDraft.value = text;
199
+ if (text.trim() === "") {
200
+ jsonError.value = null;
201
+ emit("set", props.field.key, void 0);
202
+ return;
203
+ }
204
+ try {
205
+ const parsed = JSON.parse(text);
206
+ jsonError.value = null;
207
+ emit("set", props.field.key, parsed);
208
+ } catch {
209
+ jsonError.value = "Not valid JSON yet \u2014 the value is kept once it parses.";
210
+ }
211
+ }
212
+ function readArray() {
213
+ return Array.isArray(value.value) ? value.value : [];
214
+ }
215
+ function pushArrayItem(defaultItem) {
216
+ write([...readArray(), defaultItem]);
217
+ }
218
+ function removeArrayItem(index) {
219
+ const arr = readArray().slice();
220
+ arr.splice(index, 1);
221
+ write(arr);
222
+ }
223
+ function setArrayItemField(index, fieldKey, v) {
224
+ const arr = readArray().slice();
225
+ arr[index] = { ...arr[index] ?? {}, [fieldKey]: v };
226
+ write(arr);
227
+ }
228
+ </script>
229
+
230
+ <template>
231
+ <div class="space-y-1.5">
232
+ <div class="flex items-center justify-between gap-2">
233
+ <Label class="text-mono text-muted-foreground">
234
+ {{ labelText(field.label) }}<span v-if="field.required" class="text-rose-500" title="Required"> *</span>
235
+ </Label>
236
+ <div class="flex shrink-0 items-center gap-1.5">
237
+ <div v-if="isObjectField && !showExpression" class="flex items-center rounded border border-black/[0.08] bg-white p-px">
238
+ <button
239
+ type="button"
240
+ :disabled="!flatCapable"
241
+ title="Edit as key/value rows"
242
+ class="rounded px-1 py-px text-[10px] font-medium transition disabled:pointer-events-none disabled:opacity-40"
243
+ :class="objectMode === 'form' ? 'bg-slate-100 text-foreground' : 'text-muted-foreground hover:text-foreground'"
244
+ @click="objectMode = 'form'"
245
+ >
246
+ Fields
247
+ </button>
248
+ <button
249
+ type="button"
250
+ title="Edit as raw JSON"
251
+ class="rounded px-1 py-px text-[10px] font-medium transition"
252
+ :class="objectMode === 'json' ? 'bg-slate-100 text-foreground' : 'text-muted-foreground hover:text-foreground'"
253
+ @click="objectMode = 'json'"
254
+ >
255
+ JSON
256
+ </button>
257
+ </div>
258
+ <button
259
+ v-if="canToggleExpression"
260
+ type="button"
261
+ class="rounded border px-1 py-0.5 font-mono text-[10px] transition"
262
+ :class="showExpression ? 'border-emerald-500/40 bg-emerald-500/15 text-emerald-700' : 'border-black/[0.08] bg-white text-muted-foreground hover:text-foreground'"
263
+ :title="showExpression ? 'Back to the regular control (clears the expression)' : 'Map this value from a previous step instead'"
264
+ @click="toggleExpression"
265
+ >
266
+ {{ exprHint }}
267
+ </button>
268
+ <span
269
+ v-else-if="field.expressionAllowed"
270
+ class="rounded border border-emerald-500/25 bg-emerald-500/10 px-1 py-0.5 font-mono text-[10px] text-emerald-600"
271
+ title="Accepts ${{ … }} expressions"
272
+ >
273
+ {{ exprHint }}
274
+ </span>
275
+ </div>
276
+ </div>
277
+ <!-- inline help (boolean uses its description as the toggle label instead) -->
278
+ <p v-if="descriptionText && field.type !== 'boolean'" class="text-sm text-muted-foreground leading-snug">
279
+ {{ descriptionText }}
280
+ </p>
281
+
282
+ <!-- object / plain-array fields: JSON editor (expressions stay usable
283
+ inside string values). Wins over expressionAllowed — an object value
284
+ in a text input would render as [object Object]. -->
285
+ <!-- queries parameter: filter builder -->
286
+ <IntegrationsQueryBuilder
287
+ v-if="isQueryField"
288
+ :model-value="value"
289
+ @update:model-value="(v) => write(v)"
290
+ />
291
+
292
+ <template v-else-if="isJsonField">
293
+ <Textarea
294
+ :model-value="jsonDraft"
295
+ :rows="8"
296
+ spellcheck="false"
297
+ class="font-mono text-xs"
298
+ :placeholder="labelText(field.placeholder) || '{ }'"
299
+ @update:model-value="(v) => writeJson(String(v))"
300
+ />
301
+ <p v-if="jsonError" class="text-sm text-amber-600">{{ jsonError }}</p>
302
+ </template>
303
+
304
+ <!-- object field in key/value mode -->
305
+ <div v-else-if="isObjectField && objectMode === 'form'" class="space-y-1.5">
306
+ <div v-for="(row, idx) in kvRows" :key="idx" class="flex items-center gap-1.5">
307
+ <Input
308
+ :model-value="row.key"
309
+ placeholder="key"
310
+ class="h-7 w-[38%] bg-white font-mono text-sm"
311
+ @update:model-value="(v) => setKvRow(idx, { key: String(v) })"
312
+ />
313
+ <Input
314
+ :model-value="row.value"
315
+ placeholder="value"
316
+ class="h-7 min-w-0 flex-1 bg-white text-sm"
317
+ @update:model-value="(v) => setKvRow(idx, { value: String(v) })"
318
+ />
319
+ <button
320
+ type="button"
321
+ class="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:text-rose-600"
322
+ @click="removeKvRow(idx)"
323
+ >
324
+ <Trash2 :size="11" />
325
+ </button>
326
+ </div>
327
+ <Button
328
+ type="button"
329
+ variant="outline"
330
+ size="sm"
331
+ class="h-7 w-full text-sm"
332
+ @click="addKvRow"
333
+ >
334
+ <Plus :size="11" />
335
+ Add row
336
+ </Button>
337
+ </div>
338
+
339
+ <!-- typed control flipped to expression mode -->
340
+ <IntegrationsExpressionInput
341
+ v-else-if="showExpression"
342
+ :model-value="typeof value === 'string' ? value : ''"
343
+ :placeholder="labelText(field.placeholder)"
344
+ :variables="variables"
345
+ :upstream-outputs="upstreamOutputs"
346
+ @update:model-value="(v) => write(v)"
347
+ />
348
+
349
+ <!-- dynamic options — resolved at author time via config:resolve. Ahead of
350
+ the generic expression branch so a dynamic select with
351
+ expressionAllowed still renders as a dropdown by default. -->
352
+ <IntegrationsNodeConfigDynamicField
353
+ v-else-if="field.dynamic"
354
+ :slug="slug"
355
+ :version="version"
356
+ :field="field"
357
+ :config="config"
358
+ :model-value="value"
359
+ @update:model-value="write"
360
+ />
361
+
362
+ <!-- expression-capable string: token-autocomplete input -->
363
+ <IntegrationsExpressionInput
364
+ v-else-if="field.type === 'expression' || field.expressionAllowed"
365
+ :model-value="value"
366
+ :placeholder="labelText(field.placeholder)"
367
+ :variables="variables"
368
+ :upstream-outputs="upstreamOutputs"
369
+ @update:model-value="(v) => write(v)"
370
+ />
371
+
372
+ <!-- plain string (no expressions) -->
373
+ <Input
374
+ v-else-if="field.type === 'string'"
375
+ :model-value="value ?? ''"
376
+ :placeholder="labelText(field.placeholder)"
377
+ class="h-8 text-sm bg-white"
378
+ :class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
379
+ @update:model-value="(v) => write(v)"
380
+ />
381
+
382
+ <!-- number -->
383
+ <Input
384
+ v-else-if="field.type === 'number'"
385
+ type="number"
386
+ :model-value="value ?? ''"
387
+ class="h-8 text-sm bg-white"
388
+ :class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
389
+ @update:model-value="(v) => write(v === '' ? null : Number(v))"
390
+ />
391
+
392
+ <!-- boolean -->
393
+ <div v-else-if="field.type === 'boolean'" class="flex items-center gap-2 pt-1">
394
+ <Switch
395
+ :model-value="Boolean(value)"
396
+ @update:model-value="(v) => write(v)"
397
+ />
398
+ <span class="text-sm text-muted-foreground">
399
+ {{ labelText(field.description) }}
400
+ </span>
401
+ </div>
402
+
403
+ <!-- dynamic schema — child fields resolved at author time, written into the flat config -->
404
+ <IntegrationsNodeConfigDynamicSchema
405
+ v-else-if="field.type === 'dynamic-schema'"
406
+ :slug="slug"
407
+ :version="version"
408
+ :field="field"
409
+ :config="config"
410
+ :static-keys="staticKeys ?? []"
411
+ :secret-keys="secretKeys"
412
+ :depth="depth ?? 0"
413
+ @set="(k, v) => emit('set', k, v)"
414
+ />
415
+
416
+ <!-- select -->
417
+ <Select
418
+ v-else-if="field.type === 'select'"
419
+ :model-value="value ?? ''"
420
+ @update:model-value="(v) => write(v)"
421
+ >
422
+ <SelectTrigger class="h-8 text-sm bg-white">
423
+ <SelectValue :placeholder="labelText(field.placeholder) || 'Pick one'" />
424
+ </SelectTrigger>
425
+ <SelectContent>
426
+ <SelectItem v-for="opt in field.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
427
+ {{ labelText(opt.label) }}
428
+ </SelectItem>
429
+ </SelectContent>
430
+ </Select>
431
+
432
+ <!-- multiselect — pill toggles for now -->
433
+ <div v-else-if="field.type === 'multiselect'" class="flex flex-wrap gap-1">
434
+ <button
435
+ v-for="opt in field.options ?? []"
436
+ :key="String(opt.value)"
437
+ type="button"
438
+ class="rounded-md border border-black/[0.08] px-1.5 py-0.5 text-sm transition"
439
+ :class="(value ?? []).includes(opt.value) ? 'bg-emerald-500/15 text-emerald-700 border-emerald-500/30' : 'bg-white text-muted-foreground hover:text-foreground'"
440
+ @click="() => {
441
+ const current = value ?? [];
442
+ const has = current.includes(opt.value);
443
+ write(has ? current.filter((v) => v !== opt.value) : [...current, opt.value]);
444
+ }"
445
+ >
446
+ {{ labelText(opt.label) }}
447
+ </button>
448
+ </div>
449
+
450
+ <!-- secret-ref -->
451
+ <Select
452
+ v-else-if="field.type === 'secret-ref'"
453
+ :model-value="value ?? ''"
454
+ @update:model-value="(v) => write(v)"
455
+ >
456
+ <SelectTrigger class="h-8 text-sm bg-white">
457
+ <SelectValue placeholder="Pick a secret" />
458
+ </SelectTrigger>
459
+ <SelectContent>
460
+ <SelectItem v-for="k in secretKeys" :key="k" :value="k">{{ k }}</SelectItem>
461
+ </SelectContent>
462
+ </Select>
463
+
464
+ <!-- credentials-ref — pick a credential of the required type -->
465
+ <template v-else-if="field.type === 'credentials-ref'">
466
+ <Select
467
+ :model-value="value ?? ''"
468
+ @update:model-value="(v) => write(v)"
469
+ >
470
+ <SelectTrigger class="h-8 text-sm bg-white">
471
+ <SelectValue placeholder="Pick a credential" />
472
+ </SelectTrigger>
473
+ <SelectContent>
474
+ <SelectItem v-for="c in credsForField(field)" :key="c.id" :value="c.id">
475
+ <span class="flex items-center gap-1.5">
476
+ <span class="size-1.5 shrink-0 rounded-full" :class="c.status === 'needs_reauth' ? 'bg-amber-500' : 'bg-emerald-500'" />
477
+ <span class="min-w-0 truncate">{{ c.name }}</span>
478
+ <span v-if="c.status === 'needs_reauth'" class="shrink-0 text-[10px] text-amber-600">reconnect</span>
479
+ </span>
480
+ </SelectItem>
481
+ </SelectContent>
482
+ </Select>
483
+ <p v-if="credNeedsReauth(value)" class="text-sm text-amber-600">
484
+ This credential needs reconnect.
485
+ </p>
486
+ <div class="flex items-center gap-3">
487
+ <button type="button" class="text-sm font-medium text-violet-600 hover:underline" @click="credCreateOpen = true">
488
+ + New credential
489
+ </button>
490
+ <button
491
+ v-if="acceptsSftp(field)"
492
+ type="button"
493
+ class="text-sm font-medium text-violet-600 hover:underline"
494
+ @click="sftpImportOpen = true"
495
+ >
496
+ Import SFTP account
497
+ </button>
498
+ <NuxtLink to="/integrations/credentials" class="text-sm text-muted-foreground hover:underline">
499
+ Manage →
500
+ </NuxtLink>
501
+ </div>
502
+ <IntegrationsCredentialCreateDialog
503
+ v-model:open="credCreateOpen"
504
+ :credential-type="field.credentialType"
505
+ @created="(id) => write(id)"
506
+ />
507
+ <IntegrationsSftpAccountImportDialog
508
+ v-if="acceptsSftp(field)"
509
+ v-model:open="sftpImportOpen"
510
+ @created="(id) => write(id)"
511
+ />
512
+ </template>
513
+
514
+ <!-- array of objects (e.g. headers / mappings) -->
515
+ <div v-else-if="field.type === 'array' && field.items?.type === 'object'" class="space-y-2">
516
+ <div
517
+ v-for="(item, idx) in readArray()"
518
+ :key="idx"
519
+ class="rounded-md border border-black/[0.06] bg-muted/20 p-2 space-y-1.5"
520
+ >
521
+ <div class="flex items-center justify-between">
522
+ <span class="text-mono text-muted-foreground">
523
+ #{{ idx + 1 }}
524
+ </span>
525
+ <button
526
+ type="button"
527
+ class="flex size-5 items-center justify-center rounded text-muted-foreground hover:text-rose-600"
528
+ @click="removeArrayItem(idx)"
529
+ >
530
+ <Trash2 :size="11" />
531
+ </button>
532
+ </div>
533
+ <div
534
+ v-for="sub in field.items.properties ?? []"
535
+ :key="sub.key"
536
+ class="space-y-1"
537
+ >
538
+ <Label class="text-mono text-muted-foreground">
539
+ {{ labelText(sub.label) }}
540
+ </Label>
541
+ <Input
542
+ :model-value="item[sub.key] ?? ''"
543
+ :placeholder="labelText(sub.placeholder)"
544
+ class="h-7 text-sm bg-white"
545
+ :class="sub.type === 'expression' ? 'font-mono' : ''"
546
+ @update:model-value="(v) => setArrayItemField(idx, sub.key, v)"
547
+ />
548
+ </div>
549
+ </div>
550
+ <Button
551
+ type="button"
552
+ variant="outline"
553
+ size="sm"
554
+ class="h-7 text-sm w-full"
555
+ @click="pushArrayItem(Object.fromEntries((field.items?.properties ?? []).map((p) => [p.key, ''])))"
556
+ >
557
+ <Plus :size="11" />
558
+ Add row
559
+ </Button>
560
+ </div>
561
+
562
+ <!-- fallback: JSON textarea for complex types -->
563
+ <Textarea
564
+ v-else
565
+ :model-value="value === void 0 ? '' : JSON.stringify(value, null, 2)"
566
+ rows="3"
567
+ class="text-sm font-mono bg-white"
568
+ @update:model-value="(v) => {
569
+ try {
570
+ write(v ? JSON.parse(String(v)) : void 0);
571
+ } catch {
572
+ }
573
+ }"
574
+ />
575
+
576
+ <!-- validation feedback: hard error, else soft required hint, else constraint hint -->
577
+ <p v-if="formatError" class="text-sm text-rose-600">
578
+ {{ formatError }}
579
+ </p>
580
+ <p v-else-if="requiredMissing" class="text-sm text-amber-600">
581
+ Required
582
+ </p>
583
+ <p v-else-if="constraintHint" class="text-sm text-muted-foreground">
584
+ {{ constraintHint }}
585
+ </p>
586
+ </div>
587
+ </template>
@@ -0,0 +1,23 @@
1
+ import type { ConfigField, WorkflowVariable } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ field: ConfigField;
4
+ config: Record<string, unknown>;
5
+ slug: string;
6
+ version: string;
7
+ secretKeys: string[];
8
+ staticKeys?: string[];
9
+ depth?: number;
10
+ variables?: Record<string, WorkflowVariable>;
11
+ upstreamOutputs?: Array<{
12
+ nodeId: string;
13
+ nodeName?: string;
14
+ output: string;
15
+ fields?: string[];
16
+ }>;
17
+ };
18
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ set: (key: string, value: unknown) => any;
20
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
21
+ onSet?: ((key: string, value: unknown) => any) | undefined;
22
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
23
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import type { NodeManifestOutput } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ nodeId: string;
4
+ slug: string;
5
+ version: string;
6
+ config: Record<string, unknown>;
7
+ staticOutputs: NodeManifestOutput[];
8
+ };
9
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
+ resolved: (nodeId: string, outputs: NodeManifestOutput[]) => any;
11
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
12
+ onResolved?: ((nodeId: string, outputs: NodeManifestOutput[]) => any) | undefined;
13
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ export default _default;