@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.
- package/README.md +51 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +45 -0
- package/dist/runtime/components/Integrations/Layout.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/Layout.vue +20 -0
- package/dist/runtime/components/Integrations/Layout.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +450 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +140 -0
- package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/RunDetailDialog.d.vue.ts +11 -0
- package/dist/runtime/components/Integrations/RunDetailDialog.vue +169 -0
- package/dist/runtime/components/Integrations/RunDetailDialog.vue.d.ts +11 -0
- package/dist/runtime/components/Integrations/Sidebar.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/Sidebar.vue +32 -0
- package/dist/runtime/components/Integrations/Sidebar.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +23 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +86 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +23 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +419 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +11 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +200 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +11 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.d.vue.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +82 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue.d.ts +8 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +268 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/workflowLayout.d.ts +17 -0
- package/dist/runtime/components/Integrations/workflowLayout.js +29 -0
- package/dist/runtime/composables/integrationsApi.d.ts +13 -0
- package/dist/runtime/composables/integrationsApi.js +55 -0
- package/dist/runtime/composables/useCredentialTypes.d.ts +19 -0
- package/dist/runtime/composables/useCredentialTypes.js +47 -0
- package/dist/runtime/composables/useIntegrationsAudit.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsAudit.js +39 -0
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +43 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +98 -0
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +16 -0
- package/dist/runtime/composables/useIntegrationsFocus.js +17 -0
- package/dist/runtime/composables/useIntegrationsNodes.d.ts +6 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +32 -0
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +31 -0
- package/dist/runtime/composables/useIntegrationsRuns.js +109 -0
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +14 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +32 -0
- package/dist/runtime/composables/useIntegrationsSecrets.d.ts +27 -0
- package/dist/runtime/composables/useIntegrationsSecrets.js +76 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +19 -0
- package/dist/runtime/composables/useIntegrationsTemplates.js +141 -0
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +39 -0
- package/dist/runtime/composables/useIntegrationsTriggers.js +39 -0
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +28 -0
- package/dist/runtime/composables/useIntegrationsWorkflows.js +152 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +67 -0
- package/dist/runtime/data/integrationRuns.d.ts +7 -0
- package/dist/runtime/data/integrationRuns.js +109 -0
- package/dist/runtime/data/integrationTemplates.d.ts +11 -0
- package/dist/runtime/data/integrationTemplates.js +9 -0
- package/dist/runtime/data/integrationWorkflowSamples.d.ts +18 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +318 -0
- package/dist/runtime/pages/integrations/audit.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/audit.vue +156 -0
- package/dist/runtime/pages/integrations/audit.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/credentials/oauth-complete.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +28 -0
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/credentials.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/credentials.vue +403 -0
- package/dist/runtime/pages/integrations/credentials.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/index.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/index.vue +226 -0
- package/dist/runtime/pages/integrations/index.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/runs.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/runs.vue +100 -0
- package/dist/runtime/pages/integrations/runs.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/schedules.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/schedules.vue +111 -0
- package/dist/runtime/pages/integrations/schedules.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/secrets.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/secrets.vue +248 -0
- package/dist/runtime/pages/integrations/secrets.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/[id].d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/[id].vue +326 -0
- package/dist/runtime/pages/integrations/workflows/[id].vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/index.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/index.vue +31 -0
- package/dist/runtime/pages/integrations/workflows/index.vue.d.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +2 -0
- package/dist/runtime/pages/integrations/workflows/new.vue +153 -0
- package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +2 -0
- package/dist/runtime/types/integrations.d.ts +427 -0
- package/dist/runtime/types/integrations.js +0 -0
- package/dist/runtime/utils/localize.d.ts +9 -0
- package/dist/runtime/utils/localize.js +21 -0
- package/dist/runtime/utils/statusStyle.d.ts +15 -0
- package/dist/runtime/utils/statusStyle.js +78 -0
- package/dist/types.d.mts +9 -0
- 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;
|