@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
@@ -1,8 +1,17 @@
1
1
  import { computed } from "vue";
2
- import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
2
+ import { useIntegrationsApi } from "./integrationsApi.js";
3
+ import { IntegrationsApiError } from "../utils/apiError.js";
3
4
  import { resolveLocalized } from "../utils/localize.js";
4
5
  const BLOB_VERSION = "v0-draft";
5
6
  const INDUSTRY_VALUES = ["mechanical-engineering", "chemicals", "wholesale", "manufacturing", "retail", "any"];
7
+ function plainExcerpt(md) {
8
+ for (const raw of md.split("\n")) {
9
+ const line = raw.trim();
10
+ if (!line || line.startsWith("#")) continue;
11
+ return line.replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
12
+ }
13
+ return "";
14
+ }
6
15
  function publisherFor(slug, vendors) {
7
16
  const ns = slug.includes(":") ? slug.split(":")[0] : vendors[0] ?? "revenexx";
8
17
  const kind = ns === "revenexx" ? "revenexx" : "partner";
@@ -15,10 +24,16 @@ function emptyBlob(name) {
15
24
  return { nodeManifestVersion: BLOB_VERSION, name, nodes: [], edges: [], triggerPositions: {}, triggers: [] };
16
25
  }
17
26
  function mapListItem(api) {
27
+ const short = resolveLocalized(api.shortDescription ?? "");
28
+ const md = resolveLocalized(api.description ?? "");
18
29
  return {
19
30
  slug: api.slug,
20
31
  name: resolveLocalized(api.name),
21
- description: resolveLocalized(api.description || api.shortDescription || ""),
32
+ description: short || plainExcerpt(md),
33
+ descriptionMd: md,
34
+ triggerTypes: api.triggerTypes ?? [],
35
+ images: api.images ?? [],
36
+ package: api.package ? { name: api.package.name, label: api.package.label ?? null } : null,
22
37
  category: api.category ?? "sync",
23
38
  industries: mapIndustries(api.industries),
24
39
  vendors: api.vendors ?? [],
@@ -48,13 +63,17 @@ function applyDetail(base, api) {
48
63
  name: def.name ?? base.name,
49
64
  nodes,
50
65
  edges: def.edges ?? [],
51
- triggerPositions: {},
66
+ triggerPositions: def.triggerPositions ?? {},
67
+ variables: def.variables,
52
68
  triggers
53
69
  };
70
+ const md = resolveLocalized(api.description ?? "") || base.descriptionMd;
54
71
  return {
55
72
  ...base,
56
- description: resolveLocalized(api.description || api.shortDescription || "") || base.description,
73
+ description: base.description || plainExcerpt(md),
74
+ descriptionMd: md,
57
75
  blob,
76
+ detailLoaded: true,
58
77
  nodeCount: nodes.length,
59
78
  outline: nodes.map((n) => n.nodeSlug ?? n.id).filter(Boolean)
60
79
  };
@@ -107,6 +126,16 @@ export function useIntegrationsTemplates() {
107
126
  return base;
108
127
  }
109
128
  }
129
+ async function ensureDetails(slugs, concurrency = 4) {
130
+ const queue = slugs.filter((slug) => get(slug)?.fromApi && !get(slug)?.detailLoaded);
131
+ const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
132
+ while (queue.length) {
133
+ const slug = queue.shift();
134
+ if (slug) await fetchDetail(slug);
135
+ }
136
+ });
137
+ await Promise.all(workers);
138
+ }
110
139
  async function requirements(slug) {
111
140
  try {
112
141
  const raw = unwrap(await api.get(`/templates/${slug}/requirements`));
@@ -137,5 +166,5 @@ export function useIntegrationsTemplates() {
137
166
  }
138
167
  const total = computed(() => templates.value.length);
139
168
  const popular = computed(() => [...templates.value].sort((a, b) => b.installs - a.installs).slice(0, 4));
140
- return { templates, loaded, loading, error, total, popular, load, list, get, fetchDetail, requirements, instantiate, install };
169
+ return { templates, loaded, loading, error, total, popular, load, list, get, fetchDetail, ensureDetails, requirements, instantiate, install };
141
170
  }
@@ -1,4 +1,5 @@
1
- import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
1
+ import { useIntegrationsApi } from "./integrationsApi.js";
2
+ import { IntegrationsApiError } from "../utils/apiError.js";
2
3
  export function useIntegrationsTriggers() {
3
4
  const all = useState("integrations.triggers.all", () => []);
4
5
  const loading = useState("integrations.triggers.loading", () => false);
@@ -23,6 +23,7 @@ export declare function useIntegrationsWorkflows(): {
23
23
  active: boolean;
24
24
  executionMode?: WorkflowExecutionMode;
25
25
  }) => Promise<Workflow>;
26
+ duplicate: (id: number, name?: string) => Promise<Workflow>;
26
27
  remove: (id: number) => Promise<void>;
27
28
  toggleActive: (id: number) => Promise<Workflow | null>;
28
29
  };
@@ -1,5 +1,6 @@
1
1
  import { computed } from "vue";
2
- import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
2
+ import { useIntegrationsApi } from "./integrationsApi.js";
3
+ import { IntegrationsApiError } from "../utils/apiError.js";
3
4
  const BLOB_VERSION = "v0-draft";
4
5
  function toCockpit(api) {
5
6
  const apiBlob = api.blob ?? {};
@@ -15,7 +16,12 @@ function toCockpit(api) {
15
16
  name: apiBlob.name ?? api.name,
16
17
  nodes: apiBlob.nodes ?? [],
17
18
  edges: apiBlob.edges ?? [],
19
+ variables: apiBlob.variables ?? void 0,
18
20
  triggerPositions: apiBlob.triggerPositions ?? {},
21
+ stickies: apiBlob.stickies ?? [],
22
+ groups: apiBlob.groups ?? [],
23
+ tags: apiBlob.tags ?? void 0,
24
+ icon: apiBlob.icon ?? void 0,
19
25
  triggers
20
26
  };
21
27
  return {
@@ -27,6 +33,8 @@ function toCockpit(api) {
27
33
  active: api.active ?? false,
28
34
  revision: api.revision ?? 0,
29
35
  execution_mode: api.execution_mode,
36
+ buildStatus: api.build_status ?? null,
37
+ buildError: api.build_error ?? null,
30
38
  created_at: api.created_at ?? "",
31
39
  updated_at: api.updated_at ?? ""
32
40
  };
@@ -42,7 +50,15 @@ function toPayload(meta) {
42
50
  name: typeof b.name === "string" ? b.name : meta.name,
43
51
  nodes: b.nodes ?? [],
44
52
  edges: b.edges ?? [],
45
- triggerPositions: b.triggerPositions ?? {}
53
+ triggerPositions: b.triggerPositions ?? {},
54
+ // Only send `stickies` when non-empty: backends without the stickies
55
+ // schema PR reject unknown blob keys (additionalProperties:false).
56
+ ...b.stickies?.length ? { stickies: b.stickies } : {},
57
+ ...b.groups?.length ? { groups: b.groups } : {},
58
+ // variables were silently dropped before — the schema always allowed them.
59
+ ...b.variables && Object.keys(b.variables).length ? { variables: b.variables } : {},
60
+ ...b.tags?.length ? { tags: b.tags } : {},
61
+ ...b.icon ? { icon: b.icon } : {}
46
62
  },
47
63
  active: meta.active,
48
64
  execution_mode: meta.executionMode ?? "async_only",
@@ -112,6 +128,46 @@ export function useIntegrationsWorkflows() {
112
128
  upsertCache(wf);
113
129
  return wf;
114
130
  }
131
+ async function duplicate(id, name) {
132
+ const source = await get(id);
133
+ if (!source) throw new Error("Workflow not found.");
134
+ const blob = JSON.parse(JSON.stringify(source.blob));
135
+ const handleMap = /* @__PURE__ */ new Map();
136
+ for (const trigger of blob.triggers ?? []) {
137
+ const fresh = crypto.randomUUID();
138
+ handleMap.set(trigger.id, fresh);
139
+ trigger.id = fresh;
140
+ trigger.active = false;
141
+ }
142
+ for (const edge of blob.edges ?? []) {
143
+ const mapped = handleMap.get(edge.from.nodeId);
144
+ if (mapped) edge.from.nodeId = mapped;
145
+ }
146
+ if (blob.triggerPositions) {
147
+ const rekeyed = {};
148
+ for (const [handle, pos] of Object.entries(blob.triggerPositions)) {
149
+ rekeyed[handleMap.get(handle) ?? handle] = pos;
150
+ }
151
+ blob.triggerPositions = rekeyed;
152
+ }
153
+ const base = {
154
+ description: source.description,
155
+ blob,
156
+ active: false,
157
+ executionMode: source.execution_mode ?? void 0
158
+ };
159
+ if (name?.trim()) return await create({ ...base, name: name.trim() });
160
+ const attempts = [`${source.name} (Copy)`, ...Array.from({ length: 8 }, (_, i) => `${source.name} (Copy ${i + 2})`)];
161
+ let lastError = null;
162
+ for (const attempt of attempts) {
163
+ try {
164
+ return await create({ ...base, name: attempt });
165
+ } catch (err) {
166
+ lastError = err;
167
+ }
168
+ }
169
+ throw lastError instanceof Error ? lastError : new Error("Could not duplicate the workflow.");
170
+ }
115
171
  async function update(id, patch) {
116
172
  const wf = toCockpit(unwrap(await api.put(`/workflows/${id}`, toPayload({
117
173
  name: patch.name,
@@ -146,6 +202,7 @@ export function useIntegrationsWorkflows() {
146
202
  get,
147
203
  create,
148
204
  update,
205
+ duplicate,
149
206
  remove,
150
207
  toggleActive
151
208
  };
@@ -0,0 +1,34 @@
1
+ import { type Ref } from 'vue';
2
+ export type MiniMapCorner = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
+ export type MiniMapMode = MiniMapCorner | 'floating';
4
+ /**
5
+ * Layout state for the workflow editor's minimap: pinned to one of the four
6
+ * canvas corners, free-floating, or collapsed into a pill — the same
7
+ * interaction grammar as usePaletteLayout, with corner snapping instead of
8
+ * full-height docks. Drags by its header via pointer events; releasing within
9
+ * SNAP_ZONE of two edges snaps into that corner. Persists to localStorage.
10
+ */
11
+ export declare function useMiniMapLayout(canvasRef: Ref<HTMLElement | null>): {
12
+ mode: Ref<MiniMapMode, MiniMapMode>;
13
+ collapsed: Ref<boolean, boolean>;
14
+ floating: Ref<{
15
+ x: number;
16
+ y: number;
17
+ }, {
18
+ x: number;
19
+ y: number;
20
+ } | {
21
+ x: number;
22
+ y: number;
23
+ }>;
24
+ floatingStyle: import("vue").ComputedRef<{
25
+ left: string;
26
+ top: string;
27
+ }>;
28
+ dragging: Ref<boolean, boolean>;
29
+ dockPreview: Ref<MiniMapCorner | null, MiniMapCorner | null>;
30
+ startDrag: (ev: PointerEvent, shellEl: HTMLElement | null) => void;
31
+ dock: (corner: MiniMapCorner) => void;
32
+ floatHere: (shellEl: HTMLElement | null) => void;
33
+ setCollapsed: (v: boolean) => void;
34
+ };
@@ -0,0 +1,119 @@
1
+ import { computed, onBeforeUnmount, onMounted, ref } from "vue";
2
+ const STORAGE_KEY = "integrations.minimap.layout";
3
+ const CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"];
4
+ const MARGIN = 12;
5
+ const BOTTOM_CLEARANCE = 64;
6
+ const SNAP_ZONE = 56;
7
+ function clampNum(v, lo, hi) {
8
+ return Math.min(hi, Math.max(lo, v));
9
+ }
10
+ export function useMiniMapLayout(canvasRef) {
11
+ const mode = ref("bottom-right");
12
+ const collapsed = ref(false);
13
+ const floating = ref({ x: MARGIN, y: MARGIN });
14
+ const dragging = ref(false);
15
+ const dockPreview = ref(null);
16
+ function persist() {
17
+ try {
18
+ const payload = { mode: mode.value, floating: floating.value, collapsed: collapsed.value };
19
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
20
+ } catch {
21
+ }
22
+ }
23
+ function restore() {
24
+ try {
25
+ const raw = localStorage.getItem(STORAGE_KEY);
26
+ if (!raw) return;
27
+ const saved = JSON.parse(raw);
28
+ if (saved.mode === "floating" || CORNERS.includes(saved.mode)) {
29
+ mode.value = saved.mode;
30
+ }
31
+ if (typeof saved.floating?.x === "number" && typeof saved.floating?.y === "number") {
32
+ floating.value = { x: saved.floating.x, y: saved.floating.y };
33
+ }
34
+ collapsed.value = saved.collapsed === true;
35
+ clampFloating();
36
+ } catch {
37
+ }
38
+ }
39
+ function clampFloating() {
40
+ const rect = canvasRef.value?.getBoundingClientRect();
41
+ if (!rect || rect.width === 0) return;
42
+ floating.value = {
43
+ x: clampNum(floating.value.x, MARGIN, Math.max(MARGIN, rect.width - panelW - MARGIN)),
44
+ y: clampNum(floating.value.y, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - panelH))
45
+ };
46
+ }
47
+ onMounted(restore);
48
+ let grabDX = 0;
49
+ let grabDY = 0;
50
+ let panelW = 200;
51
+ let panelH = 190;
52
+ function startDrag(ev, shellEl) {
53
+ if (ev.button !== 0 || dragging.value) return;
54
+ if (ev.target?.closest("button")) return;
55
+ const canvas = canvasRef.value;
56
+ if (!canvas || !shellEl) return;
57
+ const cRect = canvas.getBoundingClientRect();
58
+ const pRect = shellEl.getBoundingClientRect();
59
+ floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
60
+ mode.value = "floating";
61
+ grabDX = ev.clientX - pRect.left;
62
+ grabDY = ev.clientY - pRect.top;
63
+ panelW = pRect.width;
64
+ panelH = pRect.height;
65
+ dragging.value = true;
66
+ ev.preventDefault();
67
+ window.addEventListener("pointermove", onDragMove);
68
+ window.addEventListener("pointerup", onDragEnd);
69
+ window.addEventListener("pointercancel", onDragEnd);
70
+ }
71
+ function onDragMove(ev) {
72
+ const rect = canvasRef.value?.getBoundingClientRect();
73
+ if (!rect) return;
74
+ const x = clampNum(ev.clientX - rect.left - grabDX, MARGIN, Math.max(MARGIN, rect.width - panelW - MARGIN));
75
+ const y = clampNum(ev.clientY - rect.top - grabDY, MARGIN, Math.max(MARGIN, rect.height - BOTTOM_CLEARANCE - panelH));
76
+ floating.value = { x, y };
77
+ const nearLeft = x <= SNAP_ZONE;
78
+ const nearRight = x + panelW >= rect.width - SNAP_ZONE;
79
+ const nearTop = y <= SNAP_ZONE;
80
+ const nearBottom = y + panelH >= rect.height - BOTTOM_CLEARANCE - SNAP_ZONE;
81
+ dockPreview.value = nearTop && nearLeft ? "top-left" : nearTop && nearRight ? "top-right" : nearBottom && nearLeft ? "bottom-left" : nearBottom && nearRight ? "bottom-right" : null;
82
+ }
83
+ function stopListeners() {
84
+ window.removeEventListener("pointermove", onDragMove);
85
+ window.removeEventListener("pointerup", onDragEnd);
86
+ window.removeEventListener("pointercancel", onDragEnd);
87
+ }
88
+ function onDragEnd() {
89
+ if (dockPreview.value) mode.value = dockPreview.value;
90
+ dockPreview.value = null;
91
+ dragging.value = false;
92
+ stopListeners();
93
+ persist();
94
+ }
95
+ onBeforeUnmount(stopListeners);
96
+ function dock(corner) {
97
+ mode.value = corner;
98
+ persist();
99
+ }
100
+ function floatHere(shellEl) {
101
+ if (mode.value !== "floating") {
102
+ const cRect = canvasRef.value?.getBoundingClientRect();
103
+ const pRect = shellEl?.getBoundingClientRect();
104
+ if (cRect && pRect) floating.value = { x: pRect.left - cRect.left, y: pRect.top - cRect.top };
105
+ mode.value = "floating";
106
+ clampFloating();
107
+ }
108
+ persist();
109
+ }
110
+ function setCollapsed(v) {
111
+ collapsed.value = v;
112
+ persist();
113
+ }
114
+ const floatingStyle = computed(() => ({
115
+ left: `${floating.value.x}px`,
116
+ top: `${floating.value.y}px`
117
+ }));
118
+ return { mode, collapsed, floating, floatingStyle, dragging, dockPreview, startDrag, dock, floatHere, setCollapsed };
119
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Author-time resolution of a node's dynamic options / fields / outputs
3
+ * (PO-143, Phase 1). Ports the colleagues' React-Query hook
4
+ * (`integrations-ui/src/lib/useNodeConfigResolve.ts`) to a plain Vue composable
5
+ * — no query library, hand-rolled debounce + abort + keep-previous-data.
6
+ *
7
+ * Backend: `POST {integrationsApi}/nodes/{slug}/{version}/config:resolve`
8
+ * body `{ target, config, locale? }`. `target` is a config field key
9
+ * (→ `{ options }`), `'*'` (→ `{ fields }`) or `'outputs'` (→ `{ outputs }`).
10
+ *
11
+ * `dependsOn` given (options / dynamic-schema): those keys both gate the query
12
+ * (must be set as literals) and form the cache-key subset sent as `config`.
13
+ * `dependsOn` omitted (outputs): no gate, and the whole `config` keys the query
14
+ * and is sent — so any config edit re-resolves.
15
+ */
16
+ import type { MaybeRefOrGetter, Ref } from 'vue';
17
+ import type { NodeConfigResolveError, NodeConfigResolveResult } from '../types/integrations.js';
18
+ /**
19
+ * A config value counts as "set" for gating a dynamic resolve when it is a
20
+ * non-empty literal. Expression-looking strings (`${{ … }}` / leading `=`) are
21
+ * treated as unset: the backend resolves against literal values only, so a
22
+ * dependency still holding an expression can't drive resolution yet.
23
+ */
24
+ export declare function isResolvableValue(v: unknown): boolean;
25
+ export declare function useNodeConfigResolve(opts: {
26
+ slug: MaybeRefOrGetter<string>;
27
+ version: MaybeRefOrGetter<string>;
28
+ target: MaybeRefOrGetter<string>;
29
+ config: MaybeRefOrGetter<Record<string, unknown>>;
30
+ dependsOn?: MaybeRefOrGetter<string[] | undefined>;
31
+ enabled?: MaybeRefOrGetter<boolean>;
32
+ /** Type-to-search term, forwarded to loadOptions (PE-334). Keys the query. */
33
+ search?: MaybeRefOrGetter<string | undefined>;
34
+ }): {
35
+ data: Ref<NodeConfigResolveResult | null>;
36
+ error: Ref<NodeConfigResolveError | null>;
37
+ isFetching: Ref<boolean>;
38
+ hasData: Ref<boolean>;
39
+ gated: Ref<boolean>;
40
+ unsatisfied: Ref<string[]>;
41
+ isEnabled: Ref<boolean>;
42
+ refetch: () => void;
43
+ };
@@ -0,0 +1,124 @@
1
+ import { computed, onScopeDispose, ref, toValue, watch } from "vue";
2
+ import { useIntegrationsApi } from "./integrationsApi.js";
3
+ import { IntegrationsApiError } from "../utils/apiError.js";
4
+ export function isResolvableValue(v) {
5
+ if (v === void 0 || v === null) return false;
6
+ if (typeof v === "string") {
7
+ const t = v.trim();
8
+ if (t === "") return false;
9
+ if (t.startsWith("=")) return false;
10
+ if (/\$?\{\{[\s\S]*?\}\}/.test(t)) return false;
11
+ return true;
12
+ }
13
+ if (Array.isArray(v)) return v.length > 0;
14
+ return true;
15
+ }
16
+ function classifyError(err) {
17
+ const status = err instanceof IntegrationsApiError ? err.status : void 0;
18
+ const message = err instanceof IntegrationsApiError ? err.message : err?.message ?? "Resolve failed";
19
+ let kind;
20
+ switch (status) {
21
+ case 409:
22
+ kind = "building";
23
+ break;
24
+ case 502:
25
+ kind = "runtime";
26
+ break;
27
+ case 403:
28
+ kind = "forbidden";
29
+ break;
30
+ case 404:
31
+ kind = "notfound";
32
+ break;
33
+ default:
34
+ kind = "generic";
35
+ }
36
+ return { kind, message };
37
+ }
38
+ function isAbortError(err) {
39
+ return err instanceof DOMException ? err.name === "AbortError" : err?.name === "AbortError";
40
+ }
41
+ const DEBOUNCE_MS = 300;
42
+ const STALE_MS = 3e4;
43
+ export function useNodeConfigResolve(opts) {
44
+ const api = useIntegrationsApi();
45
+ const data = ref(null);
46
+ const error = ref(null);
47
+ const isFetching = ref(false);
48
+ const hasData = ref(false);
49
+ const unsatisfied = computed(() => {
50
+ const cfg = toValue(opts.config);
51
+ const gateKeys = toValue(opts.dependsOn) ?? [];
52
+ return gateKeys.filter((k) => !isResolvableValue(cfg[k]));
53
+ });
54
+ const gated = computed(() => unsatisfied.value.length > 0);
55
+ const isEnabled = computed(
56
+ () => (toValue(opts.enabled) ?? true) && !gated.value && !!toValue(opts.slug) && !!toValue(opts.version)
57
+ );
58
+ const subsetKey = computed(() => {
59
+ const cfg = toValue(opts.config);
60
+ const dep = toValue(opts.dependsOn);
61
+ const keyKeys = (dep ?? Object.keys(cfg)).slice().sort();
62
+ const subset = {};
63
+ for (const k of keyKeys) subset[k] = cfg[k];
64
+ const rawSearch = toValue(opts.search);
65
+ const search = typeof rawSearch === "string" ? rawSearch.trim().slice(0, 200) : "";
66
+ return JSON.stringify({ config: subset, search: search || void 0 });
67
+ });
68
+ const debouncedKey = ref(subsetKey.value);
69
+ let debounceTimer = null;
70
+ watch(subsetKey, (next) => {
71
+ if (debounceTimer) clearTimeout(debounceTimer);
72
+ debounceTimer = setTimeout(() => {
73
+ debouncedKey.value = next;
74
+ debounceTimer = null;
75
+ }, DEBOUNCE_MS);
76
+ });
77
+ let controller = null;
78
+ let lastFetchedKey = null;
79
+ let lastFetchedAt = 0;
80
+ async function run(key) {
81
+ if (controller) controller.abort();
82
+ controller = new AbortController();
83
+ const signal = controller.signal;
84
+ const slug = toValue(opts.slug);
85
+ const version = toValue(opts.version);
86
+ const target = toValue(opts.target);
87
+ const { config, search } = JSON.parse(key);
88
+ isFetching.value = true;
89
+ try {
90
+ const result = await api.post(
91
+ `/nodes/${encodeURIComponent(slug)}/${encodeURIComponent(version)}/config:resolve`,
92
+ { target, config, ...search ? { search } : {} },
93
+ { signal }
94
+ );
95
+ data.value = result;
96
+ hasData.value = true;
97
+ error.value = null;
98
+ lastFetchedKey = key;
99
+ lastFetchedAt = Date.now();
100
+ } catch (err) {
101
+ if (isAbortError(err) || signal.aborted) return;
102
+ error.value = classifyError(err);
103
+ } finally {
104
+ if (!signal.aborted) isFetching.value = false;
105
+ }
106
+ }
107
+ function maybeFetch(force) {
108
+ if (!isEnabled.value) return;
109
+ const key = debouncedKey.value;
110
+ if (!force && lastFetchedKey === key && Date.now() - lastFetchedAt < STALE_MS && !error.value) {
111
+ return;
112
+ }
113
+ void run(key);
114
+ }
115
+ watch([debouncedKey, isEnabled], () => maybeFetch(false), { immediate: true });
116
+ function refetch() {
117
+ maybeFetch(true);
118
+ }
119
+ onScopeDispose(() => {
120
+ if (debounceTimer) clearTimeout(debounceTimer);
121
+ if (controller) controller.abort();
122
+ });
123
+ return { data, error, isFetching, hasData, gated, unsatisfied, isEnabled, refetch };
124
+ }
@@ -0,0 +1,22 @@
1
+ import type { NodeManifest } from '../types/integrations.js';
2
+ import type { TriggerTemplate } from '../data/integrationNodeCatalog.js';
3
+ /**
4
+ * Shared state of an in-flight palette drag. The HTML5 drag payload
5
+ * (`dataTransfer`) is unreadable during `dragover`, so the palette mirrors
6
+ * WHAT is being dragged here and the editor reads it to render the
7
+ * grid-snapped placement ghost on the canvas.
8
+ */
9
+ export type NodeDragInfo = {
10
+ kind: 'manifest';
11
+ manifest: NodeManifest;
12
+ } | {
13
+ kind: 'trigger';
14
+ tpl: TriggerTemplate;
15
+ } | {
16
+ kind: 'group';
17
+ };
18
+ export declare function useNodeDrag(): {
19
+ dragging: any;
20
+ start: (info: NodeDragInfo) => void;
21
+ end: () => void;
22
+ };
@@ -0,0 +1,10 @@
1
+ export function useNodeDrag() {
2
+ const dragging = useState("integrations.nodeDrag", () => null);
3
+ function start(info) {
4
+ dragging.value = info;
5
+ }
6
+ function end() {
7
+ dragging.value = null;
8
+ }
9
+ return { dragging, start, end };
10
+ }
@@ -0,0 +1,41 @@
1
+ import { type Ref } from 'vue';
2
+ export type PaletteDock = 'left' | 'right';
3
+ export type PaletteMode = 'docked-left' | 'docked-right' | 'floating';
4
+ /**
5
+ * Layout state for the workflow editor's node palette: docked left/right
6
+ * (full-height inlay), free-floating at a canvas position, or collapsed into
7
+ * a pill. `mode` survives collapse, so collapse/expand round-trips to the
8
+ * same placement.
9
+ *
10
+ * Moving uses plain pointer events — HTML5 drag stays reserved for the
11
+ * palette's node rows and the canvas drop. While dragging, coming within
12
+ * DOCK_ZONE of the left/right canvas edge arms a dock preview; release then
13
+ * snaps into the full-height dock, anywhere else keeps the panel floating.
14
+ *
15
+ * Persists to localStorage (guarded — private mode/quota must not throw) and
16
+ * restores clamped to the canvas on mount.
17
+ */
18
+ export declare function usePaletteLayout(canvasRef: Ref<HTMLElement | null>): {
19
+ mode: Ref<PaletteMode, PaletteMode>;
20
+ collapsed: Ref<boolean, boolean>;
21
+ floating: Ref<{
22
+ x: number;
23
+ y: number;
24
+ }, {
25
+ x: number;
26
+ y: number;
27
+ } | {
28
+ x: number;
29
+ y: number;
30
+ }>;
31
+ floatingStyle: import("vue").ComputedRef<{
32
+ left: string;
33
+ top: string;
34
+ }>;
35
+ dragging: Ref<boolean, boolean>;
36
+ dockPreview: Ref<PaletteDock | null, PaletteDock | null>;
37
+ startDrag: (ev: PointerEvent, shellEl: HTMLElement | null) => void;
38
+ dock: (side: PaletteDock) => void;
39
+ floatHere: (shellEl: HTMLElement | null) => void;
40
+ setCollapsed: (v: boolean) => void;
41
+ };