@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,364 @@
1
+ <script setup>
2
+ import { computed, reactive, ref, watch } from "vue";
3
+ import {
4
+ AddCircle as Plus,
5
+ CheckCircle as Check,
6
+ RefreshCircle as Loader2
7
+ } from "#solar-icons";
8
+ import {
9
+ Button,
10
+ Dialog,
11
+ DialogContent,
12
+ DialogDescription,
13
+ DialogFooter,
14
+ DialogHeader,
15
+ DialogTitle,
16
+ Input,
17
+ Label,
18
+ Switch,
19
+ Select,
20
+ SelectContent,
21
+ SelectItem,
22
+ SelectTrigger,
23
+ SelectValue
24
+ } from "@revenexx/studio";
25
+ import { toast } from "vue-sonner";
26
+ const props = defineProps({
27
+ type: { type: [Object, null], required: true }
28
+ });
29
+ const emit = defineEmits(["update:active", "apply"]);
30
+ const KIND_BY_SLUG = {
31
+ "revenexx:sftp": "sftp",
32
+ "revenexx:smtp": "smtp",
33
+ "revenexx:api": "api"
34
+ };
35
+ const kind = computed(() => props.type ? KIND_BY_SLUG[props.type.slug] ?? null : null);
36
+ const SWITCH_LABEL = {
37
+ sftp: "Use revenexx SFTP Servers",
38
+ smtp: "Use revenexx Mail Servers",
39
+ api: "Use revenexx API Keys"
40
+ };
41
+ const NEW_LABEL = {
42
+ sftp: "New SFTP account",
43
+ smtp: "New mail credential",
44
+ api: "New API key"
45
+ };
46
+ const { tenants, onlyTenantSlug, load: loadTenants } = useConsoleTenants();
47
+ const { markets, activeCode, isGlobal, GLOBAL_MARKET_CODE, fetchMarkets } = useActiveMarket();
48
+ const sftp = useConsoleSftp();
49
+ const mail = useMailAdmin();
50
+ const consoleKeys = useConsoleApiKeys();
51
+ const config = useRuntimeConfig();
52
+ const sftpHost = config.public.sftpHost ?? "share.revenexx.com";
53
+ const sftpPort = Number(config.public.sftpPort ?? 42);
54
+ const mailSmtpHost = config.public.mailSmtpHost ?? "mail.rvnxx.com";
55
+ const mailSmtpPort = Number(config.public.mailSmtpPort ?? 587);
56
+ const active = ref(false);
57
+ const tenant = ref(null);
58
+ const marketCode = ref(GLOBAL_MARKET_CODE);
59
+ const selectedId = ref("");
60
+ const loadingList = ref(false);
61
+ const applying = ref(false);
62
+ const applied = ref(null);
63
+ const smtpCreds = ref([]);
64
+ watch(active, (v) => emit("update:active", v));
65
+ watch(kind, () => {
66
+ active.value = false;
67
+ selectedId.value = "";
68
+ applied.value = null;
69
+ });
70
+ const sftpOptions = computed(() => sftp.accounts.value.filter((a) => (a.is_active || a.status === "active") && a.login_username).map((a) => ({ value: String(a.id), label: a.login_username })));
71
+ const marketOptions = computed(() => [
72
+ { code: GLOBAL_MARKET_CODE, label: "Global (tenant baseline)" },
73
+ ...markets.value.map((m) => ({ code: m.code, label: `${m.name} (${m.code})` }))
74
+ ]);
75
+ async function ensureLists() {
76
+ loadingList.value = true;
77
+ try {
78
+ await loadTenants();
79
+ if (!tenant.value) tenant.value = onlyTenantSlug.value ?? tenants.value[0]?.slug ?? null;
80
+ if (kind.value === "sftp") await sftp.load(tenant.value);
81
+ else if (kind.value === "smtp") {
82
+ const list = await mail.listCredentials();
83
+ smtpCreds.value = (list ?? []).filter((c) => c.type === "SMTP").map((c) => ({ uuid: c.uuid, name: c.name }));
84
+ } else if (kind.value === "api") {
85
+ marketCode.value = activeCode.value;
86
+ await fetchMarkets();
87
+ }
88
+ } catch (e) {
89
+ toast.error(e?.message ?? "Could not load revenexx resources.");
90
+ } finally {
91
+ loadingList.value = false;
92
+ }
93
+ }
94
+ watch(active, (on) => {
95
+ if (on) void ensureLists();
96
+ });
97
+ function marketSlug() {
98
+ return marketCode.value === GLOBAL_MARKET_CODE ? "" : marketCode.value;
99
+ }
100
+ async function applySftp() {
101
+ const t = tenant.value;
102
+ const acc = sftp.accounts.value.find((a) => String(a.id) === selectedId.value);
103
+ if (!t || !acc) return;
104
+ applying.value = true;
105
+ try {
106
+ const creds = await sftp.revealPassword(t, acc.id);
107
+ if (!creds) {
108
+ toast.error("Account still provisioning \u2014 try again in a moment.");
109
+ return;
110
+ }
111
+ emit("apply", {
112
+ host: sftpHost,
113
+ port: sftpPort,
114
+ username: acc.login_username ?? creds.username,
115
+ password: creds.password
116
+ });
117
+ applied.value = `${acc.login_username}@${sftpHost}:${sftpPort}`;
118
+ } catch (e) {
119
+ toast.error(e?.message ?? "Could not read the SFTP password.");
120
+ } finally {
121
+ applying.value = false;
122
+ }
123
+ }
124
+ async function applySmtp() {
125
+ applying.value = true;
126
+ try {
127
+ const cred = await mail.getCredential(selectedId.value);
128
+ emit("apply", {
129
+ host: mailSmtpHost,
130
+ port: mailSmtpPort,
131
+ user: cred.smtp_username ?? "",
132
+ password: cred.smtp_password ?? "",
133
+ secure: mailSmtpPort === 465
134
+ });
135
+ applied.value = `${cred.smtp_username}@${mailSmtpHost}:${mailSmtpPort}`;
136
+ } catch (e) {
137
+ toast.error(e?.message ?? "Could not read the mail credential.");
138
+ } finally {
139
+ applying.value = false;
140
+ }
141
+ }
142
+ function onSelect(id) {
143
+ selectedId.value = id;
144
+ if (!id) {
145
+ applied.value = null;
146
+ return;
147
+ }
148
+ if (kind.value === "sftp") void applySftp();
149
+ else if (kind.value === "smtp") void applySmtp();
150
+ }
151
+ watch([tenant, marketCode], () => {
152
+ if (active.value && kind.value === "api" && apiKeyReady.value) {
153
+ emit("apply", { tenant: tenant.value ?? "", market: marketSlug() });
154
+ }
155
+ });
156
+ const createOpen = ref(false);
157
+ const creating = ref(false);
158
+ const createName = ref("");
159
+ const provisioningNote = ref("");
160
+ const apiKeyReady = ref(false);
161
+ function openCreate() {
162
+ createName.value = "";
163
+ provisioningNote.value = "";
164
+ createOpen.value = true;
165
+ }
166
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
167
+ async function createSftp() {
168
+ const t = tenant.value;
169
+ if (!t) {
170
+ toast.error("No tenant selected.");
171
+ return;
172
+ }
173
+ creating.value = true;
174
+ try {
175
+ let acc = await sftp.create(t, createName.value.trim() || void 0);
176
+ for (let i = 0; i < 24 && !(acc.is_active || acc.status === "active"); i++) {
177
+ provisioningNote.value = "Provisioning the account\u2026";
178
+ await sleep(2500);
179
+ acc = await sftp.refresh(t, acc.id);
180
+ if (acc.status === "failed") throw new Error(acc.error_message || "Provisioning failed.");
181
+ }
182
+ if (!(acc.is_active || acc.status === "active")) {
183
+ throw new Error("Still provisioning \u2014 pick it from the list once it turns active.");
184
+ }
185
+ await sftp.load(t);
186
+ selectedId.value = String(acc.id);
187
+ await applySftp();
188
+ createOpen.value = false;
189
+ toast.success("SFTP account created");
190
+ } catch (e) {
191
+ toast.error(e?.message ?? "Could not create the SFTP account.");
192
+ } finally {
193
+ creating.value = false;
194
+ provisioningNote.value = "";
195
+ }
196
+ }
197
+ async function createSmtp() {
198
+ creating.value = true;
199
+ try {
200
+ const cred = await mail.createCredential({ name: createName.value.trim() || "SMTP", type: "SMTP" });
201
+ emit("apply", {
202
+ host: mailSmtpHost,
203
+ port: mailSmtpPort,
204
+ user: cred.smtp_username ?? "",
205
+ password: cred.smtp_password ?? "",
206
+ secure: mailSmtpPort === 465
207
+ });
208
+ applied.value = `${cred.smtp_username}@${mailSmtpHost}:${mailSmtpPort}`;
209
+ smtpCreds.value = [{ uuid: cred.uuid, name: cred.name }, ...smtpCreds.value];
210
+ selectedId.value = cred.uuid;
211
+ createOpen.value = false;
212
+ toast.success("Mail credential created");
213
+ } catch (e) {
214
+ toast.error(e?.message ?? "Could not create the mail credential.");
215
+ } finally {
216
+ creating.value = false;
217
+ }
218
+ }
219
+ async function createApiKey() {
220
+ creating.value = true;
221
+ try {
222
+ const { secret } = await consoleKeys.create(createName.value.trim() || "Integrations key", ["*"]);
223
+ emit("apply", { apiKey: secret, tenant: tenant.value ?? "", market: marketSlug() });
224
+ apiKeyReady.value = true;
225
+ applied.value = `Key for ${tenant.value}${marketSlug() ? ` \xB7 ${marketSlug()}` : " \xB7 global"}`;
226
+ createOpen.value = false;
227
+ toast.success("API key created");
228
+ } catch (e) {
229
+ toast.error(e?.message ?? "Could not create the API key.");
230
+ } finally {
231
+ creating.value = false;
232
+ }
233
+ }
234
+ function submitCreate() {
235
+ if (kind.value === "sftp") void createSftp();
236
+ else if (kind.value === "smtp") void createSmtp();
237
+ else if (kind.value === "api") void createApiKey();
238
+ }
239
+ </script>
240
+
241
+ <template>
242
+ <div v-if="kind" class="rounded-lg border border-black/[0.08] bg-muted/20 p-3">
243
+ <!-- Switch header -->
244
+ <div class="flex items-center justify-between gap-3">
245
+ <div class="min-w-0">
246
+ <div class="text-sm font-medium">{{ SWITCH_LABEL[kind] }}</div>
247
+ <p class="text-xs text-muted-foreground">
248
+ Reuse an existing revenexx resource instead of typing access data by hand.
249
+ </p>
250
+ </div>
251
+ <Switch :model-value="active" @update:model-value="(v) => active = v" />
252
+ </div>
253
+
254
+ <div v-if="active" class="mt-3 space-y-3">
255
+ <!-- Tenant (preselected) -->
256
+ <div class="space-y-1.5">
257
+ <Label>Tenant</Label>
258
+ <Select :model-value="tenant ?? ''" @update:model-value="(v) => tenant = v">
259
+ <SelectTrigger class="bg-white">
260
+ <SelectValue placeholder="Select tenant" />
261
+ </SelectTrigger>
262
+ <SelectContent>
263
+ <SelectItem v-for="t in tenants" :key="t.slug" :value="t.slug">
264
+ {{ t.name }} ({{ t.slug }})
265
+ </SelectItem>
266
+ </SelectContent>
267
+ </Select>
268
+ </div>
269
+
270
+ <!-- Market scope — only the Revenexx API credential is market-scoped -->
271
+ <div v-if="kind === 'api'" class="space-y-1.5">
272
+ <Label>Market</Label>
273
+ <Select :model-value="marketCode" @update:model-value="(v) => marketCode = v">
274
+ <SelectTrigger class="bg-white">
275
+ <SelectValue placeholder="Select market" />
276
+ </SelectTrigger>
277
+ <SelectContent>
278
+ <SelectItem v-for="m in marketOptions" :key="m.code" :value="m.code">
279
+ {{ m.label }}
280
+ </SelectItem>
281
+ </SelectContent>
282
+ </Select>
283
+ </div>
284
+
285
+ <!-- Resource select (SFTP / Mail) -->
286
+ <div v-if="kind === 'sftp' || kind === 'smtp'" class="space-y-1.5">
287
+ <Label>{{ kind === "sftp" ? "SFTP account" : "Mail credential" }}</Label>
288
+ <div class="flex items-center gap-2">
289
+ <Select :model-value="selectedId" class="flex-1" @update:model-value="(v) => onSelect(v)">
290
+ <SelectTrigger class="bg-white">
291
+ <SelectValue :placeholder="loadingList ? 'Loading\u2026' : 'Select one'" />
292
+ </SelectTrigger>
293
+ <SelectContent>
294
+ <template v-if="kind === 'sftp'">
295
+ <SelectItem v-for="o in sftpOptions" :key="o.value" :value="o.value">{{ o.label }}</SelectItem>
296
+ </template>
297
+ <template v-else>
298
+ <SelectItem v-for="c in smtpCreds" :key="c.uuid" :value="c.uuid">{{ c.name }}</SelectItem>
299
+ </template>
300
+ </SelectContent>
301
+ </Select>
302
+ <Button variant="outline" size="sm" class="shrink-0 bg-white" :disabled="!tenant" @click="openCreate">
303
+ <Plus :size="14" class="mr-1" />
304
+ New
305
+ </Button>
306
+ </div>
307
+ </div>
308
+
309
+ <!-- API key: no existing selection (secret can't be read back) -->
310
+ <div v-else-if="kind === 'api'">
311
+ <Button variant="outline" size="sm" class="bg-white" :disabled="!tenant" @click="openCreate">
312
+ <Plus :size="14" class="mr-1" />
313
+ Create new API key
314
+ </Button>
315
+ </div>
316
+
317
+ <!-- Applied summary -->
318
+ <div v-if="applied || applying" class="flex items-center gap-2 rounded-md bg-emerald-500/10 px-3 py-2 text-sm text-emerald-700">
319
+ <Loader2 v-if="applying" :size="14" class="animate-spin" />
320
+ <Check v-else :size="14" />
321
+ <span class="truncate">{{ applying ? "Copying access data\u2026" : `Using ${applied}` }}</span>
322
+ </div>
323
+ </div>
324
+
325
+ <!-- Stacked "New …" lightbox -->
326
+ <Dialog v-model:open="createOpen">
327
+ <DialogContent class="sm:max-w-md">
328
+ <DialogHeader>
329
+ <DialogTitle>{{ kind ? NEW_LABEL[kind] : "New" }}</DialogTitle>
330
+ <DialogDescription class="text-sm">
331
+ <template v-if="kind === 'api'">
332
+ A full-access key is minted for <b>{{ tenant }}</b> and copied straight into this credential.
333
+ </template>
334
+ <template v-else-if="kind === 'sftp'">
335
+ Reserves a new SFTP account for <b>{{ tenant }}</b> and fills its login here.
336
+ </template>
337
+ <template v-else>
338
+ Creates a new SMTP credential and fills its login here.
339
+ </template>
340
+ </DialogDescription>
341
+ </DialogHeader>
342
+
343
+ <div class="space-y-1.5 py-1">
344
+ <Label for="rvnxx-new-name">{{ kind === "sftp" ? "Label" : "Name" }}</Label>
345
+ <Input
346
+ id="rvnxx-new-name"
347
+ v-model="createName"
348
+ class="bg-white"
349
+ :placeholder="kind === 'sftp' ? 'e.g. imports' : 'e.g. Production'"
350
+ />
351
+ <p v-if="provisioningNote" class="text-xs text-muted-foreground">{{ provisioningNote }}</p>
352
+ </div>
353
+
354
+ <DialogFooter>
355
+ <Button variant="ghost" :disabled="creating" @click="createOpen = false">Cancel</Button>
356
+ <Button :disabled="creating" @click="submitCreate">
357
+ <Loader2 v-if="creating" :size="14" class="mr-1 animate-spin" />
358
+ Create
359
+ </Button>
360
+ </DialogFooter>
361
+ </DialogContent>
362
+ </Dialog>
363
+ </div>
364
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { CredentialType } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ type: CredentialType | null;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ "update:active": (args_0: boolean) => any;
7
+ apply: (args_0: Record<string, unknown>) => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ "onUpdate:active"?: ((args_0: boolean) => any) | undefined;
10
+ onApply?: ((args_0: Record<string, unknown>) => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import type { CredentialType } from '../../types/integrations.js';
2
+ /**
3
+ * Scalable credential-type picker — a searchable list ("Search for app…") that
4
+ * scales to ~1000 types where a card grid would not. Client-side filter over the
5
+ * already-loaded catalogue; no debounce needed.
6
+ */
7
+ type __VLS_Props = {
8
+ types: CredentialType[];
9
+ };
10
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ pick: (args_0: CredentialType) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onPick?: ((args_0: CredentialType) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ export default _default;
@@ -0,0 +1,71 @@
1
+ <script setup>
2
+ import { computed, ref } from "vue";
3
+ import { Magnifier as Search } from "#solar-icons";
4
+ import { Input } from "@revenexx/studio";
5
+ const props = defineProps({
6
+ types: { type: Array, required: true }
7
+ });
8
+ const emit = defineEmits(["pick"]);
9
+ const query = ref("");
10
+ function loc(v) {
11
+ if (!v) return "";
12
+ if (typeof v === "string") return v;
13
+ const o = v;
14
+ return o.en ?? o.de ?? Object.values(o)[0] ?? "";
15
+ }
16
+ function authKindLabel(k) {
17
+ switch (k) {
18
+ case "api-key":
19
+ return "API Key";
20
+ case "oauth2-authcode":
21
+ case "oauth2-client-credentials":
22
+ return "OAuth2";
23
+ case "basic-auth":
24
+ return "Basic Auth";
25
+ case "static":
26
+ return "Static";
27
+ case "secret":
28
+ return "Secret";
29
+ default:
30
+ return k ?? "";
31
+ }
32
+ }
33
+ const filtered = computed(() => {
34
+ const q = query.value.trim().toLowerCase();
35
+ if (!q) return props.types;
36
+ return props.types.filter((t) => loc(t.name).toLowerCase().includes(q));
37
+ });
38
+ </script>
39
+
40
+ <template>
41
+ <div class="py-1">
42
+ <div class="relative">
43
+ <Search :size="15" class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground" />
44
+ <Input
45
+ v-model="query"
46
+ class="bg-white pl-9"
47
+ placeholder="Search for an app…"
48
+ autofocus
49
+ />
50
+ </div>
51
+
52
+ <div class="mt-2 max-h-80 overflow-y-auto -mx-1 px-1">
53
+ <p v-if="!filtered.length" class="px-2 py-6 text-center text-sm text-muted-foreground">
54
+ No credential type matches “{{ query }}”.
55
+ </p>
56
+ <button
57
+ v-for="t in filtered"
58
+ :key="t.slug"
59
+ type="button"
60
+ class="flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left transition hover:bg-muted/50"
61
+ @click="emit('pick', t)"
62
+ >
63
+ <IntegrationsCredentialLogo :type="t" :size="16" />
64
+ <div class="min-w-0 flex-1 truncate text-sm">
65
+ <span class="font-medium">{{ loc(t.name) || t.slug }}</span>
66
+ <span class="ml-1.5 text-muted-foreground">({{ authKindLabel(t.auth_kind) }})</span>
67
+ </div>
68
+ </button>
69
+ </div>
70
+ </div>
71
+ </template>
@@ -0,0 +1,15 @@
1
+ import type { CredentialType } from '../../types/integrations.js';
2
+ /**
3
+ * Scalable credential-type picker — a searchable list ("Search for app…") that
4
+ * scales to ~1000 types where a card grid would not. Client-side filter over the
5
+ * already-loaded catalogue; no debounce needed.
6
+ */
7
+ type __VLS_Props = {
8
+ types: CredentialType[];
9
+ };
10
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
11
+ pick: (args_0: CredentialType) => any;
12
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
13
+ onPick?: ((args_0: CredentialType) => any) | undefined;
14
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
+ export default _default;
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ workflowId: number;
3
+ };
4
+ 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>;
5
+ export default _default;
@@ -0,0 +1,151 @@
1
+ <script setup>
2
+ import { onMounted, ref, watch } from "vue";
3
+ import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as Trash2 } from "#solar-icons";
4
+ import { Badge, Button } from "@revenexx/studio";
5
+ import { toast } from "vue-sonner";
6
+ const props = defineProps({
7
+ workflowId: { type: Number, required: true }
8
+ });
9
+ const { list, replay, discard } = useIntegrationsDeadLetters();
10
+ const PER_PAGE = 25;
11
+ const items = ref([]);
12
+ const loading = ref(false);
13
+ const error = ref(null);
14
+ const page = ref(1);
15
+ const total = ref(0);
16
+ const lastPage = ref(1);
17
+ const busyId = ref(null);
18
+ async function load(p = page.value) {
19
+ loading.value = true;
20
+ error.value = null;
21
+ try {
22
+ const res = await list(props.workflowId, { page: p, perPage: PER_PAGE });
23
+ items.value = res.items;
24
+ total.value = res.total;
25
+ lastPage.value = res.lastPage;
26
+ page.value = res.page;
27
+ } catch (e) {
28
+ error.value = e?.message ?? "Could not load dead letters.";
29
+ } finally {
30
+ loading.value = false;
31
+ }
32
+ }
33
+ function goPage(p) {
34
+ const next = Math.min(Math.max(1, p), lastPage.value);
35
+ if (next !== page.value) void load(next);
36
+ }
37
+ onMounted(() => void load(1));
38
+ watch(() => props.workflowId, () => void load(1));
39
+ async function onReplay(dl) {
40
+ busyId.value = dl.id;
41
+ try {
42
+ const res = await replay(dl.id);
43
+ const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
44
+ toast.success(newId ? `Replayed \u2014 started run #${newId}` : "Replayed");
45
+ await load();
46
+ } catch (e) {
47
+ toast.error(e?.message ?? "Could not replay.");
48
+ } finally {
49
+ busyId.value = null;
50
+ }
51
+ }
52
+ async function onDiscard(dl) {
53
+ busyId.value = dl.id;
54
+ try {
55
+ await discard(dl.id);
56
+ toast.success("Discarded");
57
+ await load();
58
+ } catch (e) {
59
+ toast.error(e?.message ?? "Could not discard.");
60
+ } finally {
61
+ busyId.value = null;
62
+ }
63
+ }
64
+ function statusClass(status) {
65
+ if (status === "pending") return "bg-amber-500/15 text-amber-700 border-amber-500/25";
66
+ if (status === "replayed") return "bg-emerald-500/15 text-emerald-700 border-emerald-500/25";
67
+ return "bg-slate-500/15 text-slate-600 border-slate-500/20";
68
+ }
69
+ function fmt(iso) {
70
+ if (!iso) return "\u2014";
71
+ try {
72
+ return new Date(iso).toLocaleString();
73
+ } catch {
74
+ return iso;
75
+ }
76
+ }
77
+ </script>
78
+
79
+ <template>
80
+ <div class="space-y-3">
81
+ <div class="flex items-center justify-between">
82
+ <div>
83
+ <h3 class="text-sm font-semibold">Missed runs</h3>
84
+ <p class="mt-0.5 text-sm text-muted-foreground">
85
+ Triggers that fired while this workflow couldn't run (bundle building or failed). Replay starts a fresh run from the captured payload.
86
+ </p>
87
+ </div>
88
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="loading" @click="() => load()">
89
+ <Loader2 v-if="loading" :size="12" class="animate-spin" />
90
+ Refresh
91
+ </Button>
92
+ </div>
93
+
94
+ <div v-if="error" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
95
+ {{ error }}
96
+ </div>
97
+
98
+ <div v-if="loading && !items.length">
99
+ <div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
100
+ <div v-for="n in 4" :key="`sk-${n}`" class="flex items-start gap-3 px-3 py-3">
101
+ <div class="h-5 w-16 shrink-0 rounded bg-black/[0.07] animate-pulse" />
102
+ <div class="min-w-0 flex-1 space-y-2">
103
+ <div class="h-3 w-1/3 rounded bg-black/[0.07] animate-pulse" />
104
+ <div class="h-3 w-2/3 rounded bg-black/[0.05] animate-pulse" />
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ <div v-else-if="!items.length" class="rounded-lg border border-black/[0.06] bg-white p-6 text-center text-sm text-muted-foreground">
110
+ No missed runs. Every trigger firing made it into a run.
111
+ </div>
112
+
113
+ <div v-else class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
114
+ <div
115
+ v-for="dl in items"
116
+ :key="dl.id"
117
+ class="flex items-start gap-3 px-3 py-2.5"
118
+ >
119
+ <Badge variant="outline" :class="['border text-xs font-medium uppercase shrink-0 mt-0.5', statusClass(dl.status)]">{{ dl.status }}</Badge>
120
+ <div class="min-w-0 flex-1">
121
+ <div class="text-sm break-words">{{ dl.reason || "No reason recorded." }}</div>
122
+ <div class="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-muted-foreground font-mono">
123
+ <span>#{{ dl.id }}</span>
124
+ <span v-if="dl.source">{{ dl.source }}</span>
125
+ <span v-if="dl.trigger_handle">↳ {{ dl.trigger_handle }}</span>
126
+ <span>{{ fmt(dl.created_at) }}</span>
127
+ </div>
128
+ </div>
129
+ <div v-if="dl.status === 'pending'" class="flex shrink-0 items-center gap-1">
130
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="busyId === dl.id" @click="onReplay(dl)">
131
+ <Loader2 v-if="busyId === dl.id" :size="12" class="animate-spin" />
132
+ <ReplayIcon v-else :size="12" />
133
+ Replay
134
+ </Button>
135
+ <Button variant="ghost" size="sm" class="h-7 text-sm text-rose-600 hover:bg-rose-500/10" :disabled="busyId === dl.id" @click="onDiscard(dl)">
136
+ <Trash2 :size="12" />
137
+ Discard
138
+ </Button>
139
+ </div>
140
+ </div>
141
+ </div>
142
+
143
+ <div v-if="lastPage > 1" class="flex items-center justify-between text-sm text-muted-foreground">
144
+ <span class="tabular-nums">Page {{ page }} / {{ lastPage }} · {{ total }} total</span>
145
+ <div class="flex gap-2">
146
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="page <= 1 || loading" @click="goPage(page - 1)">← Newer</Button>
147
+ <Button variant="outline" size="sm" class="h-7 text-sm" :disabled="page >= lastPage || loading" @click="goPage(page + 1)">Older →</Button>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ </template>
@@ -0,0 +1,5 @@
1
+ type __VLS_Props = {
2
+ workflowId: number;
3
+ };
4
+ 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>;
5
+ export default _default;
@@ -0,0 +1,17 @@
1
+ type __VLS_Props = {
2
+ label: string;
3
+ /** e.g. 'mod+enter', 'shift+alt+t', 'v', '1'. Omit for label-only tips. */
4
+ shortcut?: string;
5
+ };
6
+ declare var __VLS_10: {};
7
+ type __VLS_Slots = {} & {
8
+ default?: (props: typeof __VLS_10) => any;
9
+ };
10
+ declare const __VLS_component: 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>;
11
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
12
+ export default _default;
13
+ type __VLS_WithSlots<T, S> = T & {
14
+ new (): {
15
+ $slots: S;
16
+ };
17
+ };