@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,326 @@
1
+ <script setup>
2
+ import { computed, onMounted, ref, watch } from "vue";
3
+ import { ArrowLeft, ClockCircle as Clock, History as HistoryIcon, RefreshCircle as Loader2, SidebarMinimalistic as PanelLeftOpen, Pause, Play, Diskette as Save, LinkRound as Webhook } from "#solar-icons";
4
+ import { Button } from "@revenexx/studio";
5
+ import WorkflowEditor from "../../../components/Integrations/WorkflowEditor.vue";
6
+ const route = useRoute();
7
+ const router = useRouter();
8
+ const { workflows, get, update, toggleActive } = useIntegrationsWorkflows();
9
+ const { focused, toggle: toggleFocus, autoEnable } = useIntegrationsFocus();
10
+ autoEnable();
11
+ const id = computed(() => Number(route.params.id));
12
+ const workflow = computed(() => workflows.value.find((w) => w.id === id.value) ?? null);
13
+ useHead({ title: () => `${workflow.value?.name ?? "Workflow"} \xB7 Revenexx Cockpit` });
14
+ const tab = ref("editor");
15
+ const tabs = [
16
+ { key: "editor", label: "Editor" },
17
+ { key: "runs", label: "Runs" },
18
+ { key: "triggers", label: "Triggers" },
19
+ { key: "variables", label: "Variables" },
20
+ { key: "settings", label: "Settings" },
21
+ { key: "history", label: "History" }
22
+ ];
23
+ const dirtyBlob = ref(null);
24
+ const isDirty = computed(() => dirtyBlob.value !== null);
25
+ const saving = ref(false);
26
+ const saveError = ref(null);
27
+ const { load: loadCredentials, needsReauth } = useIntegrationsCredentials();
28
+ onMounted(() => {
29
+ void loadCredentials();
30
+ });
31
+ const reauthCredentials = computed(() => {
32
+ const blob = JSON.stringify(dirtyBlob.value ?? workflow.value?.blob ?? {});
33
+ return needsReauth.value.filter((c) => blob.includes(c.id));
34
+ });
35
+ watch(id, async () => {
36
+ const w = await get(id.value);
37
+ if (!w) await router.push("/integrations/workflows");
38
+ dirtyBlob.value = null;
39
+ }, { immediate: true });
40
+ function onChange(blob) {
41
+ dirtyBlob.value = blob;
42
+ }
43
+ const togglingTrigger = ref(null);
44
+ async function toggleTrigger(trg) {
45
+ if (!workflow.value) return;
46
+ togglingTrigger.value = trg.id;
47
+ saveError.value = null;
48
+ trg.active = !trg.active;
49
+ try {
50
+ await update(workflow.value.id, {
51
+ name: workflow.value.name,
52
+ description: workflow.value.description,
53
+ blob: workflow.value.blob,
54
+ active: workflow.value.active,
55
+ executionMode: workflow.value.execution_mode
56
+ });
57
+ } catch (e) {
58
+ trg.active = !trg.active;
59
+ saveError.value = e.message || "Could not update the trigger.";
60
+ } finally {
61
+ togglingTrigger.value = null;
62
+ }
63
+ }
64
+ const { trigger: triggerRun } = useIntegrationsRuns();
65
+ const running = ref(false);
66
+ const runResult = ref(null);
67
+ const runError = ref(null);
68
+ async function runNow() {
69
+ if (!workflow.value) return;
70
+ running.value = true;
71
+ runError.value = null;
72
+ runResult.value = null;
73
+ try {
74
+ runResult.value = await triggerRun(workflow.value.id, {});
75
+ } catch (e) {
76
+ runError.value = e.message || "Could not start the run.";
77
+ } finally {
78
+ running.value = false;
79
+ }
80
+ }
81
+ async function save() {
82
+ if (!workflow.value || !dirtyBlob.value) return;
83
+ saving.value = true;
84
+ saveError.value = null;
85
+ try {
86
+ await update(workflow.value.id, {
87
+ name: workflow.value.name,
88
+ description: workflow.value.description,
89
+ blob: dirtyBlob.value,
90
+ active: workflow.value.active,
91
+ executionMode: workflow.value.execution_mode
92
+ });
93
+ dirtyBlob.value = null;
94
+ } catch (e) {
95
+ saveError.value = e.message || "Save failed.";
96
+ } finally {
97
+ saving.value = false;
98
+ }
99
+ }
100
+ function formatDateTime(iso) {
101
+ if (!iso) return "\u2014";
102
+ try {
103
+ return new Date(iso).toLocaleString();
104
+ } catch {
105
+ return iso;
106
+ }
107
+ }
108
+ </script>
109
+
110
+ <template>
111
+ <IntegrationsLayout>
112
+ <!-- Detail surface uses a flex-col so the editor tab can claim the
113
+ remaining height below the page header. -->
114
+ <div class="flex h-full flex-col">
115
+ <template v-if="workflow">
116
+ <PageHeader
117
+ :title="workflow.name"
118
+ >
119
+ <template #actions>
120
+ <span v-if="isDirty" class="text-sm text-amber-600">Unsaved changes</span>
121
+ <Button
122
+ variant="ghost"
123
+ size="icon"
124
+ class="size-7"
125
+ :title="focused ? 'Show studio sidebar' : 'Hide studio sidebar'"
126
+ @click="toggleFocus"
127
+ >
128
+ <PanelLeftOpen :size="14" :class="focused ? '' : 'rotate-180'" />
129
+ </Button>
130
+ <Button variant="outline" class="h-7 text-sm" @click="toggleActive(workflow.id)">
131
+ <component :is="workflow.active ? Pause : Play" :size="12" />
132
+ {{ workflow.active ? "Pause" : "Activate" }}
133
+ </Button>
134
+ <Button class="h-7 text-sm" :disabled="!isDirty || saving" @click="save">
135
+ <Loader2 v-if="saving" :size="12" class="animate-spin" />
136
+ <Save v-else :size="12" />
137
+ {{ saving ? "Saving\u2026" : "Save" }}
138
+ </Button>
139
+ <NuxtLink to="/integrations/workflows">
140
+ <Button variant="ghost" class="h-7 text-sm">
141
+ <ArrowLeft :size="12" />
142
+ Workflows
143
+ </Button>
144
+ </NuxtLink>
145
+ </template>
146
+
147
+ </PageHeader>
148
+
149
+ <TabBar v-model="tab" :tabs="tabs" class="shrink-0 px-4" />
150
+
151
+ <div v-if="saveError" class="border-b border-rose-500/20 bg-rose-500/5 px-4 py-2 text-sm text-rose-700">
152
+ {{ saveError }}
153
+ </div>
154
+
155
+ <div v-if="reauthCredentials.length" class="border-b border-amber-500/20 bg-amber-500/5 px-4 py-2 text-sm text-amber-800">
156
+ {{ reauthCredentials.length }} credential{{ reauthCredentials.length === 1 ? "" : "s" }} used here
157
+ need{{ reauthCredentials.length === 1 ? "s" : "" }} reconnecting
158
+ ({{ reauthCredentials.map((c) => c.name).join(", ") }}) —
159
+ <NuxtLink to="/integrations/credentials" class="font-medium underline">
160
+ fix in Credentials
161
+ </NuxtLink>.
162
+ </div>
163
+
164
+ <!-- Editor tab — fills the remaining height inside the panel via
165
+ the flex wrapper in the studio layout. -->
166
+ <div v-if="tab === 'editor'" class="flex-1 min-h-0">
167
+ <WorkflowEditor :workflow="workflow" @change="onChange" />
168
+ </div>
169
+
170
+ <!-- Runs — trigger a run now; full run list lives on the Runs page. -->
171
+ <div v-else-if="tab === 'runs'" class="p-6 space-y-3">
172
+ <div class="rounded-lg border border-black/[0.06] bg-white p-4 flex items-center justify-between gap-3">
173
+ <div>
174
+ <h3 class="text-sm font-semibold">
175
+ Run this workflow
176
+ </h3>
177
+ <p class="mt-0.5 text-sm text-muted-foreground">
178
+ Triggers a manual run via the API. See all executions on the Runs page.
179
+ </p>
180
+ </div>
181
+ <Button :disabled="running" class="h-8 text-sm" @click="runNow">
182
+ <Loader2 v-if="running" :size="14" class="animate-spin" />
183
+ <Play v-else :size="14" />
184
+ {{ running ? "Starting\u2026" : "Run now" }}
185
+ </Button>
186
+ </div>
187
+ <div v-if="runError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
188
+ {{ runError }}
189
+ </div>
190
+ <pre v-if="runResult" class="overflow-auto rounded-lg border border-black/[0.06] bg-slate-50 p-3 text-xs">{{ runResult }}</pre>
191
+ </div>
192
+
193
+ <!-- Triggers tab — list of all triggers with sync to webhooks page -->
194
+ <div v-else-if="tab === 'triggers'" class="p-6 space-y-3">
195
+ <div
196
+ v-for="trg in workflow.blob.triggers"
197
+ :key="trg.id"
198
+ class="rounded-lg border border-black/[0.06] bg-white p-3 flex items-center gap-3"
199
+ >
200
+ <component
201
+ :is="trg.type === 'schedule' ? Clock : trg.type === 'webhook' ? Webhook : Play"
202
+ :size="18"
203
+ class="text-emerald-600"
204
+ />
205
+ <div class="flex-1 min-w-0">
206
+ <div class="text-sm font-medium">
207
+ {{ trg.type === "schedule" ? "Schedule" : trg.type === "webhook" ? "Webhook" : "Manual" }}
208
+ </div>
209
+ <div class="text-sm text-muted-foreground truncate font-mono">
210
+ <template v-if="trg.type === 'schedule'">
211
+ cron: {{ trg.config.cron }} ({{ trg.config.timezone ?? "UTC" }})
212
+ </template>
213
+ <template v-else-if="trg.type === 'webhook'">
214
+ {{ trg.config.method }} {{ trg.config.path }}
215
+ </template>
216
+ <template v-else>on demand</template>
217
+ </div>
218
+ </div>
219
+ <div class="flex shrink-0 items-center gap-2">
220
+ <span class="text-xs" :class="trg.active ? 'text-emerald-600' : 'text-muted-foreground'">
221
+ {{ trg.active ? "Active" : "Inactive" }}
222
+ </span>
223
+ <button
224
+ type="button"
225
+ :disabled="togglingTrigger === trg.id"
226
+ class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:opacity-50"
227
+ :class="trg.active ? 'bg-emerald-500' : 'bg-slate-300'"
228
+ :aria-pressed="trg.active"
229
+ :title="trg.active ? 'Deactivate trigger' : 'Activate trigger'"
230
+ @click="toggleTrigger(trg)"
231
+ >
232
+ <span
233
+ class="inline-block size-4 transform rounded-full bg-white shadow transition-transform"
234
+ :class="trg.active ? 'translate-x-[18px]' : 'translate-x-0.5'"
235
+ />
236
+ </button>
237
+ </div>
238
+ </div>
239
+ <p class="text-xs text-muted-foreground">
240
+ A workflow run needs at least one <span class="font-medium">active</span> trigger.
241
+ </p>
242
+ </div>
243
+
244
+ <!-- Variables tab -->
245
+ <div v-else-if="tab === 'variables'" class="p-6">
246
+ <div v-if="!Object.keys(workflow.blob.variables ?? {}).length" class="rounded-lg border border-black/[0.06] bg-white p-6 text-center text-sm text-muted-foreground">
247
+ No variables defined for this workflow.
248
+ </div>
249
+ <div v-else class="rounded-lg border border-black/[0.06] bg-white">
250
+ <div class="grid grid-cols-[1fr_120px_1fr] gap-2 border-b border-black/[0.06] px-3 py-2 text-mono text-muted-foreground">
251
+ <div>Key</div>
252
+ <div>Type</div>
253
+ <div>Default</div>
254
+ </div>
255
+ <div
256
+ v-for="(v, key) in workflow.blob.variables ?? {}"
257
+ :key="key"
258
+ class="grid grid-cols-[1fr_120px_1fr] gap-2 border-b border-black/[0.04] px-3 py-2 text-sm last:border-b-0"
259
+ >
260
+ <div class="font-mono">
261
+ {{ key }}
262
+ </div>
263
+ <div class="text-muted-foreground">
264
+ {{ v.type }}
265
+ </div>
266
+ <div class="font-mono truncate">
267
+ {{ JSON.stringify(v.default) }}
268
+ </div>
269
+ </div>
270
+ </div>
271
+ </div>
272
+
273
+ <!-- Settings -->
274
+ <div v-else-if="tab === 'settings'" class="p-6 space-y-4 text-sm">
275
+ <div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-2">
276
+ <div class="text-mono text-muted-foreground">
277
+ Metadata
278
+ </div>
279
+ <div class="grid grid-cols-[120px_1fr] gap-y-2 gap-x-3 text-sm">
280
+ <div class="text-muted-foreground">
281
+ ID
282
+ </div><div class="font-mono">
283
+ {{ workflow.id }}
284
+ </div>
285
+ <div class="text-muted-foreground">
286
+ Definition
287
+ </div><div class="font-mono">
288
+ {{ workflow.blob_definition_version }}
289
+ </div>
290
+ <div class="text-muted-foreground">
291
+ Revision
292
+ </div><div class="font-mono">
293
+ r{{ workflow.revision }}
294
+ </div>
295
+ <div class="text-muted-foreground">
296
+ Created
297
+ </div><div>{{ formatDateTime(workflow.created_at) }}</div>
298
+ <div class="text-muted-foreground">
299
+ Updated
300
+ </div><div>{{ formatDateTime(workflow.updated_at) }}</div>
301
+ </div>
302
+ </div>
303
+ </div>
304
+
305
+ <!-- History -->
306
+ <div v-else-if="tab === 'history'" class="p-6">
307
+ <div class="rounded-lg border border-black/[0.06] bg-white p-6 text-center">
308
+ <div class="flex size-12 items-center justify-center rounded-full bg-slate-100 text-slate-500 mb-3 mx-auto">
309
+ <HistoryIcon :size="22" />
310
+ </div>
311
+ <h3 class="text-sm font-semibold">
312
+ {{ workflow.revision }} revision{{ workflow.revision === 1 ? "" : "s" }}
313
+ </h3>
314
+ <p class="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
315
+ Every save that changes the blob bumps the revision and snapshots the previous one. Once history endpoints land you'll be able to diff and roll back from here.
316
+ </p>
317
+ </div>
318
+ </div>
319
+ </template>
320
+
321
+ <div v-else class="p-12 text-center text-sm text-muted-foreground">
322
+ Loading…
323
+ </div>
324
+ </div>
325
+ </IntegrationsLayout>
326
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,31 @@
1
+ <script setup>
2
+ import { AddCircle as Plus } from "#solar-icons";
3
+ import { Button } from "@revenexx/studio";
4
+ import WorkflowsTable from "../../../components/Integrations/WorkflowsTable.vue";
5
+ useHead({ title: "Workflows \xB7 Revenexx Cockpit" });
6
+ const { workflows, load } = useIntegrationsWorkflows();
7
+ onMounted(() => {
8
+ void load();
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <IntegrationsLayout>
14
+ <PageHeader
15
+ title="Workflows"
16
+ >
17
+ <template #actions>
18
+ <NuxtLink to="/integrations/workflows/new">
19
+ <Button class="h-7 text-sm">
20
+ <Plus :size="14" />
21
+ New workflow
22
+ </Button>
23
+ </NuxtLink>
24
+ </template>
25
+ </PageHeader>
26
+
27
+ <div class="p-4">
28
+ <WorkflowsTable :workflows="workflows" />
29
+ </div>
30
+ </IntegrationsLayout>
31
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,153 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ import { ArrowLeft, ClockCircle as Clock, RefreshCircle as Loader2, Play, LinkRound as Webhook } from "#solar-icons";
4
+ import { Button, Input, Label, Textarea } from "@revenexx/studio";
5
+ useHead({ title: "New workflow \xB7 Revenexx Cockpit" });
6
+ const router = useRouter();
7
+ const { create } = useIntegrationsWorkflows();
8
+ const name = ref("");
9
+ const description = ref("");
10
+ const triggerType = ref("manual");
11
+ const executionMode = ref("async_only");
12
+ const submitting = ref(false);
13
+ const error = ref(null);
14
+ const executionModes = [
15
+ { key: "async_only", label: "Async only", hint: "Always queued." },
16
+ { key: "sync_only", label: "Sync only", hint: "Waits for the result." },
17
+ { key: "caller_decides", label: "Caller decides", hint: "The request chooses." }
18
+ ];
19
+ const triggerOptions = [
20
+ { key: "manual", label: "Manual", hint: "Run on demand from the editor or API.", icon: Play, tile: "bg-slate-100 text-slate-500" },
21
+ { key: "schedule", label: "Schedule", hint: "Cron-based recurring execution.", icon: Clock, tile: "bg-slate-100 text-slate-500" },
22
+ { key: "webhook", label: "Webhook", hint: "Trigger from an inbound HTTP call.", icon: Webhook, tile: "bg-slate-100 text-slate-500" }
23
+ ];
24
+ async function submit() {
25
+ if (!name.value.trim()) {
26
+ error.value = "Name is required.";
27
+ return;
28
+ }
29
+ submitting.value = true;
30
+ error.value = null;
31
+ try {
32
+ const handle = globalThis.crypto?.randomUUID?.() ?? `trg-${Date.now()}`;
33
+ const config = triggerType.value === "schedule" ? { cron: "0 * * * *", timezone: "Europe/Berlin" } : triggerType.value === "webhook" ? { path: "/incoming", method: "POST" } : {};
34
+ const blob = {
35
+ nodeManifestVersion: "v0-draft",
36
+ name: name.value.trim(),
37
+ description: description.value.trim() || void 0,
38
+ triggers: [{ id: handle, type: triggerType.value, active: true, config }],
39
+ nodes: [],
40
+ edges: []
41
+ };
42
+ const created = await create({
43
+ name: name.value.trim(),
44
+ description: description.value.trim() || null,
45
+ blob,
46
+ active: false,
47
+ executionMode: executionMode.value
48
+ });
49
+ await router.push(`/integrations/workflows/${created.id}`);
50
+ } catch (e) {
51
+ error.value = e.message || "Could not create the workflow.";
52
+ } finally {
53
+ submitting.value = false;
54
+ }
55
+ }
56
+ </script>
57
+
58
+ <template>
59
+ <IntegrationsLayout>
60
+ <PageHeader
61
+ title="New workflow"
62
+ description="Give your workflow a name, pick a trigger, and you'll land in the editor."
63
+ >
64
+ <template #actions>
65
+ <NuxtLink to="/integrations/workflows">
66
+ <Button variant="outline" class="h-7 text-sm">
67
+ <ArrowLeft :size="14" />
68
+ Back
69
+ </Button>
70
+ </NuxtLink>
71
+ </template>
72
+ </PageHeader>
73
+
74
+ <form class=" p-6 space-y-6" @submit.prevent="submit">
75
+ <div class="space-y-1.5">
76
+ <Label for="wf-name">Name</Label>
77
+ <Input id="wf-name" v-model="name" placeholder="Sync orders nightly" class="bg-white" />
78
+ </div>
79
+
80
+ <div class="space-y-1.5">
81
+ <Label for="wf-desc">Description</Label>
82
+ <Textarea
83
+ id="wf-desc"
84
+ v-model="description"
85
+ rows="3"
86
+ placeholder="Optional — what this workflow does and why it exists."
87
+ class="bg-white"
88
+ />
89
+ </div>
90
+
91
+ <div class="space-y-2">
92
+ <Label>Trigger</Label>
93
+ <div class="grid gap-2 sm:grid-cols-3">
94
+ <button
95
+ v-for="opt in triggerOptions"
96
+ :key="opt.key"
97
+ type="button"
98
+ class="rounded-lg border border-black/[0.06] bg-white p-3 text-left transition hover:border-black/20 hover:shadow-sm"
99
+ :class="triggerType === opt.key ? 'ring-2 ring-emerald-500/40 border-emerald-500/30' : ''"
100
+ @click="triggerType = opt.key"
101
+ >
102
+ <span class="flex size-7 items-center justify-center rounded-md mb-2" :class="opt.tile">
103
+ <component :is="opt.icon" :size="14" :stroke-width="2.25" />
104
+ </span>
105
+ <div class="text-sm font-medium">
106
+ {{ opt.label }}
107
+ </div>
108
+ <p class="mt-0.5 text-sm text-muted-foreground">
109
+ {{ opt.hint }}
110
+ </p>
111
+ </button>
112
+ </div>
113
+ </div>
114
+
115
+ <div class="space-y-2">
116
+ <Label>Execution mode</Label>
117
+ <div class="grid gap-2 sm:grid-cols-3">
118
+ <button
119
+ v-for="opt in executionModes"
120
+ :key="opt.key"
121
+ type="button"
122
+ class="rounded-lg border border-black/[0.06] bg-white p-3 text-left transition hover:border-black/20 hover:shadow-sm"
123
+ :class="executionMode === opt.key ? 'ring-2 ring-emerald-500/40 border-emerald-500/30' : ''"
124
+ @click="executionMode = opt.key"
125
+ >
126
+ <div class="text-sm font-medium">
127
+ {{ opt.label }}
128
+ </div>
129
+ <p class="mt-0.5 text-sm text-muted-foreground">
130
+ {{ opt.hint }}
131
+ </p>
132
+ </button>
133
+ </div>
134
+ </div>
135
+
136
+ <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">
137
+ {{ error }}
138
+ </div>
139
+
140
+ <div class="flex gap-2 pt-2">
141
+ <Button type="submit" :disabled="submitting" class="h-8 text-sm">
142
+ <Loader2 v-if="submitting" :size="14" class="animate-spin" />
143
+ {{ submitting ? "Creating\u2026" : "Create and open editor" }}
144
+ </Button>
145
+ <NuxtLink to="/integrations/workflows">
146
+ <Button type="button" variant="outline" class="h-8 text-sm">
147
+ Cancel
148
+ </Button>
149
+ </NuxtLink>
150
+ </div>
151
+ </form>
152
+ </IntegrationsLayout>
153
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;