@revenexx/studio-integrations 0.1.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 (106) hide show
  1. package/README.md +51 -0
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +45 -0
  5. package/dist/runtime/components/Integrations/Layout.d.vue.ts +12 -0
  6. package/dist/runtime/components/Integrations/Layout.vue +20 -0
  7. package/dist/runtime/components/Integrations/Layout.vue.d.ts +12 -0
  8. package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +16 -0
  9. package/dist/runtime/components/Integrations/NodeInspector.vue +450 -0
  10. package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +16 -0
  11. package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +2 -0
  12. package/dist/runtime/components/Integrations/NodePalette.vue +140 -0
  13. package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +2 -0
  14. package/dist/runtime/components/Integrations/RunDetailDialog.d.vue.ts +11 -0
  15. package/dist/runtime/components/Integrations/RunDetailDialog.vue +169 -0
  16. package/dist/runtime/components/Integrations/RunDetailDialog.vue.d.ts +11 -0
  17. package/dist/runtime/components/Integrations/Sidebar.d.vue.ts +2 -0
  18. package/dist/runtime/components/Integrations/Sidebar.vue +32 -0
  19. package/dist/runtime/components/Integrations/Sidebar.vue.d.ts +2 -0
  20. package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +23 -0
  21. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +86 -0
  22. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +23 -0
  23. package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +14 -0
  24. package/dist/runtime/components/Integrations/WorkflowEditor.vue +419 -0
  25. package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +14 -0
  26. package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +11 -0
  27. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +200 -0
  28. package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +11 -0
  29. package/dist/runtime/components/Integrations/WorkflowTriggerView.d.vue.ts +8 -0
  30. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +82 -0
  31. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue.d.ts +8 -0
  32. package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +6 -0
  33. package/dist/runtime/components/Integrations/WorkflowsTable.vue +268 -0
  34. package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +6 -0
  35. package/dist/runtime/components/Integrations/workflowLayout.d.ts +17 -0
  36. package/dist/runtime/components/Integrations/workflowLayout.js +29 -0
  37. package/dist/runtime/composables/integrationsApi.d.ts +13 -0
  38. package/dist/runtime/composables/integrationsApi.js +55 -0
  39. package/dist/runtime/composables/useCredentialTypes.d.ts +19 -0
  40. package/dist/runtime/composables/useCredentialTypes.js +47 -0
  41. package/dist/runtime/composables/useIntegrationsAudit.d.ts +26 -0
  42. package/dist/runtime/composables/useIntegrationsAudit.js +39 -0
  43. package/dist/runtime/composables/useIntegrationsCredentials.d.ts +43 -0
  44. package/dist/runtime/composables/useIntegrationsCredentials.js +98 -0
  45. package/dist/runtime/composables/useIntegrationsFocus.d.ts +16 -0
  46. package/dist/runtime/composables/useIntegrationsFocus.js +17 -0
  47. package/dist/runtime/composables/useIntegrationsNodes.d.ts +6 -0
  48. package/dist/runtime/composables/useIntegrationsNodes.js +32 -0
  49. package/dist/runtime/composables/useIntegrationsRuns.d.ts +31 -0
  50. package/dist/runtime/composables/useIntegrationsRuns.js +109 -0
  51. package/dist/runtime/composables/useIntegrationsSchedules.d.ts +14 -0
  52. package/dist/runtime/composables/useIntegrationsSchedules.js +32 -0
  53. package/dist/runtime/composables/useIntegrationsSecrets.d.ts +27 -0
  54. package/dist/runtime/composables/useIntegrationsSecrets.js +76 -0
  55. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +19 -0
  56. package/dist/runtime/composables/useIntegrationsTemplates.js +141 -0
  57. package/dist/runtime/composables/useIntegrationsTriggers.d.ts +39 -0
  58. package/dist/runtime/composables/useIntegrationsTriggers.js +39 -0
  59. package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +28 -0
  60. package/dist/runtime/composables/useIntegrationsWorkflows.js +152 -0
  61. package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
  62. package/dist/runtime/data/integrationNodeCatalog.js +67 -0
  63. package/dist/runtime/data/integrationRuns.d.ts +7 -0
  64. package/dist/runtime/data/integrationRuns.js +109 -0
  65. package/dist/runtime/data/integrationTemplates.d.ts +11 -0
  66. package/dist/runtime/data/integrationTemplates.js +9 -0
  67. package/dist/runtime/data/integrationWorkflowSamples.d.ts +18 -0
  68. package/dist/runtime/data/integrationWorkflowSamples.js +318 -0
  69. package/dist/runtime/pages/integrations/audit.d.vue.ts +2 -0
  70. package/dist/runtime/pages/integrations/audit.vue +156 -0
  71. package/dist/runtime/pages/integrations/audit.vue.d.ts +2 -0
  72. package/dist/runtime/pages/integrations/credentials/oauth-complete.d.vue.ts +2 -0
  73. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +28 -0
  74. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue.d.ts +2 -0
  75. package/dist/runtime/pages/integrations/credentials.d.vue.ts +2 -0
  76. package/dist/runtime/pages/integrations/credentials.vue +403 -0
  77. package/dist/runtime/pages/integrations/credentials.vue.d.ts +2 -0
  78. package/dist/runtime/pages/integrations/index.d.vue.ts +2 -0
  79. package/dist/runtime/pages/integrations/index.vue +226 -0
  80. package/dist/runtime/pages/integrations/index.vue.d.ts +2 -0
  81. package/dist/runtime/pages/integrations/runs.d.vue.ts +2 -0
  82. package/dist/runtime/pages/integrations/runs.vue +100 -0
  83. package/dist/runtime/pages/integrations/runs.vue.d.ts +2 -0
  84. package/dist/runtime/pages/integrations/schedules.d.vue.ts +2 -0
  85. package/dist/runtime/pages/integrations/schedules.vue +111 -0
  86. package/dist/runtime/pages/integrations/schedules.vue.d.ts +2 -0
  87. package/dist/runtime/pages/integrations/secrets.d.vue.ts +2 -0
  88. package/dist/runtime/pages/integrations/secrets.vue +248 -0
  89. package/dist/runtime/pages/integrations/secrets.vue.d.ts +2 -0
  90. package/dist/runtime/pages/integrations/workflows/[id].d.vue.ts +2 -0
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +326 -0
  92. package/dist/runtime/pages/integrations/workflows/[id].vue.d.ts +2 -0
  93. package/dist/runtime/pages/integrations/workflows/index.d.vue.ts +2 -0
  94. package/dist/runtime/pages/integrations/workflows/index.vue +31 -0
  95. package/dist/runtime/pages/integrations/workflows/index.vue.d.ts +2 -0
  96. package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +2 -0
  97. package/dist/runtime/pages/integrations/workflows/new.vue +153 -0
  98. package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +2 -0
  99. package/dist/runtime/types/integrations.d.ts +427 -0
  100. package/dist/runtime/types/integrations.js +0 -0
  101. package/dist/runtime/utils/localize.d.ts +9 -0
  102. package/dist/runtime/utils/localize.js +21 -0
  103. package/dist/runtime/utils/statusStyle.d.ts +15 -0
  104. package/dist/runtime/utils/statusStyle.js +78 -0
  105. package/dist/types.d.mts +9 -0
  106. package/package.json +46 -0
@@ -0,0 +1,82 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { Handle, Position, useVueFlow } from "@vue-flow/core";
4
+ import { NodeToolbar } from "@vue-flow/node-toolbar";
5
+ import { ClockCircle as Clock, Play, TrashBinMinimalistic as Trash2, LinkRound as Webhook, Bolt } from "#solar-icons";
6
+ const props = defineProps({
7
+ id: { type: String, required: true },
8
+ data: { type: Object, required: true },
9
+ selected: { type: Boolean, required: false }
10
+ });
11
+ defineOptions({ inheritAttrs: false });
12
+ defineEmits(["updateNodeInternals"]);
13
+ const icon = computed(() => {
14
+ return props.data.type === "schedule" ? Clock : props.data.type === "webhook" ? Webhook : props.data.type === "event" ? Bolt : Play;
15
+ });
16
+ const subtitle = computed(() => {
17
+ const c = props.data.config;
18
+ if (props.data.type === "schedule") return c.cron ?? "no cron set";
19
+ if (props.data.type === "webhook") return `${c.method ?? "POST"} ${c.path ?? "/"}`;
20
+ if (props.data.type === "event") return c.event_type ? `on ${c.event_type}` : "no event set";
21
+ return "On demand";
22
+ });
23
+ const title = computed(() => {
24
+ const n = props.data.name;
25
+ if (!n) return props.data.type.charAt(0).toUpperCase() + props.data.type.slice(1);
26
+ return typeof n === "string" ? n : Object.values(n)[0];
27
+ });
28
+ const flow = useVueFlow();
29
+ function remove() {
30
+ flow.removeNodes([props.id]);
31
+ }
32
+ const SIDES = ["top", "right", "bottom", "left"];
33
+ function sideToPosition(s) {
34
+ return s === "top" ? Position.Top : s === "right" ? Position.Right : s === "bottom" ? Position.Bottom : Position.Left;
35
+ }
36
+ </script>
37
+
38
+ <template>
39
+ <NodeToolbar :position="Position.Top" :offset="36" class="flex items-center gap-1 rounded-md border border-black/[0.08] bg-white px-1 py-1 shadow-md">
40
+ <button
41
+ type="button"
42
+ class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-rose-600 hover:bg-rose-50"
43
+ title="Delete"
44
+ @click="remove"
45
+ >
46
+ <Trash2 :size="11" />
47
+ </button>
48
+ </NodeToolbar>
49
+
50
+ <div
51
+ class="w-[200px] rounded-lg border-2 bg-gradient-to-br from-emerald-50 to-white transition-all duration-150"
52
+ :class="selected ? 'border-emerald-500 shadow-[0_0_0_2px_rgba(16,185,129,0.18)]' : 'border-emerald-400/40 shadow-sm hover:shadow-md hover:border-emerald-500/60'"
53
+ >
54
+ <div class="flex items-center gap-2 px-3 py-2">
55
+ <span class="flex size-7 items-center justify-center rounded-md bg-emerald-500 text-white shadow-sm">
56
+ <component :is="icon" :size="14" :stroke-width="2.5" />
57
+ </span>
58
+ <div class="min-w-0">
59
+ <div class="text-sm font-semibold text-foreground leading-tight truncate">
60
+ {{ title }}
61
+ </div>
62
+ <div class="text-sm text-muted-foreground/80 truncate font-mono">
63
+ {{ subtitle }}
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="border-t border-emerald-500/15 bg-emerald-500/[0.04] rounded-b-md px-3 py-1 text-mono text-emerald-700/80 font-semibold">
68
+ Trigger · {{ data.type }}
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Output handles on all 4 sides. The blob's `from.output` stays
73
+ "default"; the side suffix is stripped on persist. -->
74
+ <Handle
75
+ v-for="side in SIDES"
76
+ :id="`default__${side}`"
77
+ :key="side"
78
+ type="source"
79
+ :position="sideToPosition(side)"
80
+ class="!size-2 !rounded-full !border-2 !border-white !bg-emerald-500 opacity-80 transition-all hover:!size-2.5 hover:!bg-emerald-600 hover:opacity-100"
81
+ />
82
+ </template>
@@ -0,0 +1,8 @@
1
+ import type { WorkflowTrigger } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ id: string;
4
+ data: WorkflowTrigger;
5
+ selected?: boolean;
6
+ };
7
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, any, string, import("vue").PublicProps, any, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import type { Workflow as WorkflowResource } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ workflows: WorkflowResource[];
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,268 @@
1
+ <script setup>
2
+ import { ClockCircle as Clock, Globus as Globe, RefreshCircle as Loader2, MenuDots as MoreHorizontal, Pause, Play, TrashBinMinimalistic as Trash2, LinkRound as Webhook, Programming as Workflow, Bolt } from "#solar-icons";
3
+ import {
4
+ AlertDialog,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogFooter,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ Badge,
11
+ Button,
12
+ DropdownMenu,
13
+ DropdownMenuContent,
14
+ DropdownMenuItem,
15
+ DropdownMenuLabel,
16
+ DropdownMenuSeparator,
17
+ DropdownMenuTrigger
18
+ } from "@revenexx/studio";
19
+ defineProps({
20
+ workflows: { type: Array, required: true }
21
+ });
22
+ const router = useRouter();
23
+ const { remove, toggleActive } = useIntegrationsWorkflows();
24
+ const { runs, load: loadRuns, list: listRuns } = useIntegrationsRuns();
25
+ onMounted(() => {
26
+ void loadRuns();
27
+ });
28
+ function lastRunFor(id) {
29
+ void runs.value.length;
30
+ return listRuns({ workflowId: id })[0] ?? null;
31
+ }
32
+ function runBadge(status) {
33
+ switch (status) {
34
+ case "succeeded":
35
+ return { label: "Success", class: "bg-emerald-500/15 text-emerald-700 border-emerald-500/20" };
36
+ case "failed":
37
+ return { label: "Failed", class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
38
+ case "running":
39
+ return { label: "Running", class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
40
+ case "queued":
41
+ return { label: "Queued", class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
42
+ case "cancelled":
43
+ return { label: "Cancelled", class: "bg-slate-100 text-slate-700 border-slate-200" };
44
+ }
45
+ }
46
+ const pendingDelete = ref(null);
47
+ const deleting = ref(false);
48
+ function askDelete(wf) {
49
+ pendingDelete.value = wf;
50
+ }
51
+ async function confirmDelete() {
52
+ const wf = pendingDelete.value;
53
+ if (!wf) return;
54
+ deleting.value = true;
55
+ try {
56
+ await remove(wf.id);
57
+ pendingDelete.value = null;
58
+ } finally {
59
+ deleting.value = false;
60
+ }
61
+ }
62
+ function triggerIcon(type) {
63
+ return type === "schedule" ? Clock : type === "webhook" ? Webhook : type === "event" ? Bolt : Play;
64
+ }
65
+ function statusBadge(wf) {
66
+ if (!wf.active) return { label: "Paused", class: "bg-slate-100 text-slate-700 border-slate-200" };
67
+ if (wf.revision === 0) return { label: "Draft", class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
68
+ return { label: "Active", class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
69
+ }
70
+ function formatDateTime(iso) {
71
+ if (!iso) return "\u2014";
72
+ try {
73
+ return new Date(iso).toLocaleString(void 0, {
74
+ month: "short",
75
+ day: "2-digit",
76
+ hour: "2-digit",
77
+ minute: "2-digit"
78
+ });
79
+ } catch {
80
+ return iso;
81
+ }
82
+ }
83
+ const columns = [
84
+ {
85
+ key: "name",
86
+ label: "Name",
87
+ sortable: true,
88
+ sortGetter: (w) => w.name.toLowerCase(),
89
+ searchGetter: (w) => `${w.name} ${w.description ?? ""}`
90
+ },
91
+ {
92
+ key: "status",
93
+ label: "Status",
94
+ width: "100px",
95
+ sortable: true,
96
+ sortGetter: (w) => w.active ? w.revision === 0 ? "draft" : "active" : "paused"
97
+ },
98
+ {
99
+ key: "triggers",
100
+ label: "Triggers",
101
+ width: "140px",
102
+ sortable: true,
103
+ sortGetter: (w) => w.blob.triggers.map((t) => t.type).join(",")
104
+ },
105
+ {
106
+ key: "nodes",
107
+ label: "Nodes",
108
+ width: "100px",
109
+ sortable: true,
110
+ sortGetter: (w) => w.blob.nodes.length
111
+ },
112
+ {
113
+ key: "revision",
114
+ label: "Revision",
115
+ width: "100px",
116
+ sortable: true,
117
+ sortGetter: (w) => w.revision
118
+ },
119
+ {
120
+ key: "lastRun",
121
+ label: "Last run",
122
+ width: "150px",
123
+ sortable: true,
124
+ sortGetter: (w) => lastRunFor(w.id)?.startedAt ?? ""
125
+ },
126
+ {
127
+ key: "updated",
128
+ label: "Updated",
129
+ width: "120px",
130
+ sortable: true,
131
+ sortGetter: (w) => w.updated_at ?? ""
132
+ },
133
+ {
134
+ key: "actions",
135
+ label: "",
136
+ width: "40px",
137
+ toggleable: false
138
+ }
139
+ ];
140
+ </script>
141
+
142
+ <template>
143
+ <DataTable
144
+ :data="workflows"
145
+ :columns="columns"
146
+ :row-key="(wf2) => wf2.id"
147
+ :default-sort="{ column: 'updated', direction: 'desc' }"
148
+ :search="{ placeholder: 'Search workflows\u2026' }"
149
+ :filter="true"
150
+ empty-text="No workflows yet."
151
+ @row-click="(wf2) => router.push(`/integrations/workflows/${wf2.id}`)"
152
+ >
153
+ <!-- Name (icon + title + description) -->
154
+ <template #cell-name="{ row: wf }">
155
+ <div class="flex items-center gap-2 min-w-0">
156
+ <div class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
157
+ <Workflow :size="14" />
158
+ </div>
159
+ <div class="flex flex-col min-w-0">
160
+ <span class="font-medium text-sm truncate">{{ wf.name }}</span>
161
+ <span v-if="wf.description" class="text-sm text-muted-foreground truncate">{{ wf.description }}</span>
162
+ </div>
163
+ </div>
164
+ </template>
165
+
166
+ <template #cell-status="{ row: wf }">
167
+ <Badge variant="outline" :class="['text-sm border', statusBadge(wf).class]">
168
+ {{ statusBadge(wf).label }}
169
+ </Badge>
170
+ </template>
171
+
172
+ <template #cell-triggers="{ row: wf }">
173
+ <div class="flex items-center gap-1 text-sm text-muted-foreground">
174
+ <span
175
+ v-for="t in wf.blob.triggers"
176
+ :key="t.id"
177
+ class="inline-flex items-center gap-1 rounded-md bg-muted/60 px-1.5 py-0.5"
178
+ >
179
+ <component :is="triggerIcon(t.type)" :size="11" />
180
+ {{ t.type }}
181
+ </span>
182
+ </div>
183
+ </template>
184
+
185
+ <template #cell-nodes="{ row: wf }">
186
+ <span class="text-sm text-muted-foreground tabular-nums">
187
+ {{ wf.blob.nodes.length }} node<span v-if="wf.blob.nodes.length !== 1">s</span>
188
+ </span>
189
+ </template>
190
+
191
+ <template #cell-revision="{ row: wf }">
192
+ <span class="text-sm text-muted-foreground tabular-nums">r{{ wf.revision }}</span>
193
+ </template>
194
+
195
+ <template #cell-lastRun="{ row: wf }">
196
+ <div v-if="lastRunFor(wf.id)" class="flex items-center gap-2 min-w-0">
197
+ <Badge variant="outline" :class="['text-sm border shrink-0', runBadge(lastRunFor(wf.id).status).class]">
198
+ {{ runBadge(lastRunFor(wf.id).status).label }}
199
+ </Badge>
200
+ <span class="text-sm tabular-nums text-muted-foreground truncate">
201
+ {{ formatDateTime(lastRunFor(wf.id).startedAt) }}
202
+ </span>
203
+ </div>
204
+ <span v-else class="text-sm text-muted-foreground">—</span>
205
+ </template>
206
+
207
+ <template #cell-updated="{ row: wf }">
208
+ <span class="text-sm tabular-nums text-muted-foreground truncate">
209
+ {{ formatDateTime(wf.updated_at) }}
210
+ </span>
211
+ </template>
212
+
213
+ <template #cell-actions="{ row: wf }">
214
+ <div @click.stop>
215
+ <DropdownMenu>
216
+ <DropdownMenuTrigger as-child>
217
+ <Button variant="ghost" size="icon" class="size-7">
218
+ <MoreHorizontal :size="14" />
219
+ </Button>
220
+ </DropdownMenuTrigger>
221
+ <DropdownMenuContent align="end">
222
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
223
+ <DropdownMenuSeparator />
224
+ <DropdownMenuItem @select="router.push(`/integrations/workflows/${wf.id}`)">
225
+ <Globe :size="14" /> Open editor
226
+ </DropdownMenuItem>
227
+ <DropdownMenuItem @select="toggleActive(wf.id)">
228
+ <component :is="wf.active ? Pause : Play" :size="14" />
229
+ {{ wf.active ? "Pause" : "Activate" }}
230
+ </DropdownMenuItem>
231
+ <DropdownMenuSeparator />
232
+ <DropdownMenuItem
233
+ class="text-destructive focus:text-destructive"
234
+ @select="askDelete(wf)"
235
+ >
236
+ <Trash2 :size="14" /> Delete
237
+ </DropdownMenuItem>
238
+ </DropdownMenuContent>
239
+ </DropdownMenu>
240
+ </div>
241
+ </template>
242
+ </DataTable>
243
+
244
+ <AlertDialog :open="!!pendingDelete" @update:open="(v) => {
245
+ if (!v) pendingDelete = null;
246
+ }">
247
+ <AlertDialogContent>
248
+ <AlertDialogHeader>
249
+ <AlertDialogTitle>Delete workflow?</AlertDialogTitle>
250
+ <AlertDialogDescription>
251
+ <span v-if="pendingDelete">
252
+ <span class="font-medium text-foreground">{{ pendingDelete.name }}</span>
253
+ and all its revision history will be permanently deleted.
254
+ </span>
255
+ </AlertDialogDescription>
256
+ </AlertDialogHeader>
257
+ <AlertDialogFooter>
258
+ <Button variant="outline" :disabled="deleting" @click="pendingDelete = null">
259
+ Cancel
260
+ </Button>
261
+ <Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="confirmDelete">
262
+ <Loader2 v-if="deleting" :size="14" class="animate-spin" />
263
+ {{ deleting ? "Deleting\u2026" : "Delete" }}
264
+ </Button>
265
+ </AlertDialogFooter>
266
+ </AlertDialogContent>
267
+ </AlertDialog>
268
+ </template>
@@ -0,0 +1,6 @@
1
+ import type { Workflow as WorkflowResource } from '../../types/integrations.js';
2
+ type __VLS_Props = {
3
+ workflows: WorkflowResource[];
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
6
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import type { Edge as VFEdge, Node as VFNode } from '@vue-flow/core';
2
+ /**
3
+ * Pure layout helper. Takes the current Vue Flow nodes + edges, runs them
4
+ * through dagre with horizontal direction (LR), and returns a new array
5
+ * of nodes with updated `position`. Edges are unchanged.
6
+ *
7
+ * Dagre's coordinates are center-based; Vue Flow expects top-left origin,
8
+ * so we offset by half the node size on each axis. Sizes default to a
9
+ * comfortable card width — the canvas re-fits after applying.
10
+ */
11
+ export declare function layoutWorkflowNodes(nodes: VFNode[], edges: VFEdge[], options?: {
12
+ direction?: 'LR' | 'TB';
13
+ nodeWidth?: number;
14
+ nodeHeight?: number;
15
+ rankSeparation?: number;
16
+ nodeSeparation?: number;
17
+ }): VFNode[];
@@ -0,0 +1,29 @@
1
+ import dagre from "@dagrejs/dagre";
2
+ export function layoutWorkflowNodes(nodes, edges, options = {}) {
3
+ const dir = options.direction ?? "LR";
4
+ const nodeWidth = options.nodeWidth ?? 260;
5
+ const nodeHeight = options.nodeHeight ?? 120;
6
+ const ranksep = options.rankSeparation ?? 90;
7
+ const nodesep = options.nodeSeparation ?? 40;
8
+ const g = new dagre.graphlib.Graph();
9
+ g.setDefaultEdgeLabel(() => ({}));
10
+ g.setGraph({ rankdir: dir, ranksep, nodesep, marginx: 20, marginy: 20 });
11
+ for (const n of nodes) {
12
+ g.setNode(n.id, { width: nodeWidth, height: nodeHeight });
13
+ }
14
+ for (const e of edges) {
15
+ g.setEdge(e.source, e.target);
16
+ }
17
+ dagre.layout(g);
18
+ return nodes.map((n) => {
19
+ const laid = g.node(n.id);
20
+ if (!laid) return n;
21
+ return {
22
+ ...n,
23
+ position: {
24
+ x: laid.x - nodeWidth / 2,
25
+ y: laid.y - nodeHeight / 2
26
+ }
27
+ };
28
+ });
29
+ }
@@ -0,0 +1,13 @@
1
+ export declare class IntegrationsApiError extends Error {
2
+ status: number;
3
+ body: unknown;
4
+ constructor(status: number, body: unknown, message: string);
5
+ }
6
+ export declare function useIntegrationsApi(): {
7
+ baseUrl: import("vue").ComputedRef<string>;
8
+ get: <T>(path: string) => Promise<T>;
9
+ post: <T>(path: string, body?: unknown) => Promise<T>;
10
+ put: <T>(path: string, body?: unknown) => Promise<T>;
11
+ patch: <T>(path: string, body?: unknown) => Promise<T>;
12
+ del: <T>(path: string) => Promise<T>;
13
+ };
@@ -0,0 +1,55 @@
1
+ import { computed } from "vue";
2
+ export class IntegrationsApiError extends Error {
3
+ constructor(status, body, message) {
4
+ super(message);
5
+ this.status = status;
6
+ this.body = body;
7
+ this.name = "IntegrationsApiError";
8
+ }
9
+ }
10
+ export function useIntegrationsApi() {
11
+ const config = useRuntimeConfig();
12
+ const { getToken } = usePlatformAuth();
13
+ const { slug: onlyTenantSlug, load: loadTenants } = usePlatformTenant();
14
+ const baseUrl = computed(
15
+ () => config.public.integrationsApi ?? "https://integrations.revenexx.com/api/v1"
16
+ );
17
+ async function buildHeaders(extra = {}) {
18
+ const token = await getToken();
19
+ if (!token) throw new IntegrationsApiError(401, null, "Not signed in.");
20
+ if (!onlyTenantSlug.value) await loadTenants();
21
+ const headers = {
22
+ Accept: "application/json",
23
+ Authorization: `Bearer ${token}`,
24
+ ...extra
25
+ };
26
+ if (onlyTenantSlug.value) headers["X-Tenant-Id"] = onlyTenantSlug.value;
27
+ return headers;
28
+ }
29
+ function url(path) {
30
+ return `${baseUrl.value.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
31
+ }
32
+ async function request(method, path, body) {
33
+ const headers = await buildHeaders(body !== void 0 ? { "Content-Type": "application/json" } : {});
34
+ try {
35
+ return await $fetch(url(path), {
36
+ method,
37
+ headers,
38
+ body: body !== void 0 ? body : void 0
39
+ });
40
+ } catch (err) {
41
+ const fe = err;
42
+ const data = fe?.response?._data;
43
+ const detail = data?.message ?? fe?.message ?? "Integrations request failed";
44
+ throw new IntegrationsApiError(fe?.response?.status ?? 0, data, detail);
45
+ }
46
+ }
47
+ return {
48
+ baseUrl,
49
+ get: (path) => request("GET", path),
50
+ post: (path, body) => request("POST", path, body ?? {}),
51
+ put: (path, body) => request("PUT", path, body ?? {}),
52
+ patch: (path, body) => request("PATCH", path, body ?? {}),
53
+ del: (path) => request("DELETE", path)
54
+ };
55
+ }
@@ -0,0 +1,19 @@
1
+ import type { CredentialType } from '../types/integrations.js';
2
+ /**
3
+ * Global credential-type catalogue, backed by the Integrations service
4
+ * (`/api/v1/credential-types`). These drive the dynamic credential forms
5
+ * (descriptor `fields[]` + `auth_kind`). Not tenant-scoped, but fetched with
6
+ * the tenant-scoped client like everything else.
7
+ *
8
+ * GET /credential-types → list latest versions
9
+ * GET /credential-types/{slug} → single type
10
+ */
11
+ export declare function useCredentialTypes(): {
12
+ types: any;
13
+ loading: any;
14
+ error: any;
15
+ loaded: any;
16
+ load: (force?: boolean) => Promise<CredentialType[]>;
17
+ bySlug: (slug: string) => CredentialType | null;
18
+ get: (slug: string) => Promise<CredentialType | null>;
19
+ };
@@ -0,0 +1,47 @@
1
+ import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
2
+ export function useCredentialTypes() {
3
+ const types = useState("integrations.credentialTypes", () => []);
4
+ const loading = useState("integrations.credentialTypes.loading", () => false);
5
+ const error = useState("integrations.credentialTypes.error", () => null);
6
+ const loaded = useState("integrations.credentialTypes.loaded", () => false);
7
+ const api = useIntegrationsApi();
8
+ function normalize(res) {
9
+ const obj = res;
10
+ if (Array.isArray(obj)) return obj;
11
+ if (Array.isArray(obj?.data)) return obj.data;
12
+ return [];
13
+ }
14
+ function captureError(e, fallback) {
15
+ if (e instanceof IntegrationsApiError) return e.message;
16
+ return e?.message ?? fallback;
17
+ }
18
+ async function load(force = false) {
19
+ if (loading.value) return types.value;
20
+ if (loaded.value && !force) return types.value;
21
+ loading.value = true;
22
+ error.value = null;
23
+ try {
24
+ types.value = normalize(await api.get("/credential-types"));
25
+ loaded.value = true;
26
+ } catch (e) {
27
+ error.value = captureError(e, "Could not load credential types.");
28
+ } finally {
29
+ loading.value = false;
30
+ }
31
+ return types.value;
32
+ }
33
+ function bySlug(slug) {
34
+ return types.value.find((t) => t.slug === slug) ?? null;
35
+ }
36
+ async function get(slug) {
37
+ const cached = bySlug(slug);
38
+ if (cached) return cached;
39
+ try {
40
+ const res = await api.get(`/credential-types/${encodeURIComponent(slug)}`);
41
+ return res?.data ?? res ?? null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+ return { types, loading, error, loaded, load, bySlug, get };
47
+ }
@@ -0,0 +1,26 @@
1
+ export type AuditResourceType = 'credential' | 'secret' | 'workflow' | 'trigger' | 'workflow_run';
2
+ export type AuditAction = 'create' | 'update' | 'delete' | 'run.started' | 'run.finished' | 'run.failed';
3
+ export interface AuditEntry {
4
+ id: number;
5
+ org_id?: string;
6
+ tenant_id?: string;
7
+ actor_sub?: string | null;
8
+ action: string;
9
+ resource_type: string;
10
+ resource_id?: string;
11
+ metadata?: Record<string, unknown> | null;
12
+ created_at: string;
13
+ }
14
+ export interface AuditFilters {
15
+ sub?: string;
16
+ resource_type?: string;
17
+ action?: string;
18
+ }
19
+ export declare function useIntegrationsAudit(): {
20
+ entries: any;
21
+ loading: any;
22
+ error: any;
23
+ hasMore: any;
24
+ load: (filters?: AuditFilters, append?: boolean) => Promise<void>;
25
+ loadMore: (filters?: AuditFilters) => Promise<void>;
26
+ };
@@ -0,0 +1,39 @@
1
+ import { IntegrationsApiError, useIntegrationsApi } from "./integrationsApi.js";
2
+ export function useIntegrationsAudit() {
3
+ const api = useIntegrationsApi();
4
+ const entries = useState("int.audit.entries", () => []);
5
+ const nextCursor = useState("int.audit.cursor", () => null);
6
+ const loading = useState("int.audit.loading", () => false);
7
+ const error = useState("int.audit.error", () => null);
8
+ function qs(filters, cursor) {
9
+ const p = new URLSearchParams();
10
+ if (filters.sub) p.set("sub", filters.sub);
11
+ if (filters.resource_type) p.set("resource_type", filters.resource_type);
12
+ if (filters.action) p.set("action", filters.action);
13
+ p.set("limit", "50");
14
+ if (cursor) p.set("cursor", cursor);
15
+ return p.toString();
16
+ }
17
+ async function load(filters = {}, append = false) {
18
+ if (loading.value) return;
19
+ loading.value = true;
20
+ error.value = null;
21
+ try {
22
+ const res = await api.get(
23
+ `/audit-logs?${qs(filters, append ? nextCursor.value : null)}`
24
+ );
25
+ const data = res.data ?? [];
26
+ entries.value = append ? [...entries.value, ...data] : data;
27
+ nextCursor.value = res.meta?.next_cursor ?? null;
28
+ } catch (e) {
29
+ error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? "Could not load the audit log.";
30
+ } finally {
31
+ loading.value = false;
32
+ }
33
+ }
34
+ function loadMore(filters = {}) {
35
+ return nextCursor.value ? load(filters, true) : Promise.resolve();
36
+ }
37
+ const hasMore = computed(() => !!nextCursor.value);
38
+ return { entries, loading, error, hasMore, load, loadMore };
39
+ }
@@ -0,0 +1,43 @@
1
+ import type { Credential } from '../types/integrations.js';
2
+ /**
3
+ * Typed, per-tenant credential instances, backed by the Integrations service
4
+ * (`/api/v1/credentials`). Secret field values are NEVER returned — the list
5
+ * carries `public_config` (non-secret fields) + `status` only.
6
+ *
7
+ * GET /credentials[?type=slug] → list
8
+ * POST /credentials → create { credential_type_slug, name, config }
9
+ * GET /credentials/{id} → show (masked)
10
+ * PATCH /credentials/{id} → update { name?, config? } (blank secret = keep)
11
+ * DELETE /credentials/{id} → remove
12
+ * POST /credentials/test → inline test { credential_type_slug, config }
13
+ * POST /credentials/{id}/test → test saved instance
14
+ * POST /credentials/{id}/oauth/authorize-url → { authorize_url, state }
15
+ */
16
+ export interface CredentialTestResult {
17
+ ok: boolean;
18
+ error?: string;
19
+ }
20
+ export declare function useIntegrationsCredentials(): {
21
+ credentials: any;
22
+ loading: any;
23
+ error: any;
24
+ loaded: any;
25
+ total: import("vue").ComputedRef<any>;
26
+ needsReauth: import("vue").ComputedRef<any>;
27
+ load: (force?: boolean) => Promise<Credential[]>;
28
+ ofType: (slug: string) => Credential[];
29
+ get: (id: string) => Promise<Credential>;
30
+ create: (input: {
31
+ credential_type_slug: string;
32
+ name: string;
33
+ config: Record<string, unknown>;
34
+ }) => Promise<Credential>;
35
+ update: (id: string, input: {
36
+ name?: string;
37
+ config?: Record<string, unknown>;
38
+ }) => Promise<Credential>;
39
+ remove: (id: string) => Promise<void>;
40
+ testInline: (credentialTypeSlug: string, config: Record<string, unknown>) => Promise<CredentialTestResult>;
41
+ test: (id: string) => Promise<CredentialTestResult>;
42
+ oauthAuthorizeUrl: (id: string) => Promise<string>;
43
+ };