@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
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @revenexx/studio-integrations
|
|
2
|
+
|
|
3
|
+
The revenexx **Integration Studio** (core: Dashboard, Workflows + node editor,
|
|
4
|
+
Runs, Schedules, Credentials, Secrets, Audit) as a distributable **Nuxt module**.
|
|
5
|
+
Registers its `/integrations/*` pages, components and composables. Data/auth flow
|
|
6
|
+
through the `usePlatformAuth` / `usePlatformTenant` seam (from
|
|
7
|
+
`@revenexx/studio-shared`).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Public on npm — no registry config or token needed:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @revenexx/studio-integrations @revenexx/studio-shared @revenexx/studio
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Use
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// nuxt.config.ts
|
|
21
|
+
export default defineNuxtConfig({
|
|
22
|
+
extends: ['@revenexx/studio-shared'], // shell primitives + platform provider
|
|
23
|
+
modules: ['@solar-icons/nuxt', '@revenexx/studio-integrations'],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Point it at your Integrations backend via `NUXT_PUBLIC_INTEGRATIONS_API` and
|
|
28
|
+
supply a token/tenant through the platform provider (override
|
|
29
|
+
`usePlatformAuth` / `usePlatformTenant`, or the standalone defaults read
|
|
30
|
+
`NUXT_PUBLIC_DEV_TOKEN` / `NUXT_PUBLIC_DEV_TENANT`).
|
|
31
|
+
|
|
32
|
+
## ⚠️ Tailwind: you MUST scan this package
|
|
33
|
+
|
|
34
|
+
The module ships its runtime as source-ish `.vue`; Tailwind generates the
|
|
35
|
+
classes from **your** build. Scan both this module and `studio-shared`, or
|
|
36
|
+
component styling silently breaks:
|
|
37
|
+
|
|
38
|
+
```css
|
|
39
|
+
@import "tailwindcss";
|
|
40
|
+
@import "@revenexx/studio/tokens";
|
|
41
|
+
@import "@revenexx/studio/fonts";
|
|
42
|
+
@source "../node_modules/@revenexx/studio-shared/components";
|
|
43
|
+
@source "../node_modules/@revenexx/studio-integrations/dist/runtime";
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Adjust the relative paths to reach `node_modules` from your CSS file.
|
|
47
|
+
|
|
48
|
+
## Peers
|
|
49
|
+
`@revenexx/studio`, `@revenexx/studio-shared`, `nuxt` ^4, `vue` ^3.5,
|
|
50
|
+
`@vue-flow/*` + `@dagrejs/dagre` (workflow editor), `lucide-vue-next`; register
|
|
51
|
+
`@solar-icons/nuxt` in the host.
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { defineNuxtModule, createResolver, addComponentsDir, addImportsDir, extendPages } from '@nuxt/kit';
|
|
4
|
+
|
|
5
|
+
function toSegment(name) {
|
|
6
|
+
if (name.startsWith("[...")) return `:${name.slice(4, -1)}(.*)*`;
|
|
7
|
+
if (name.startsWith("[")) return `:${name.slice(1, -1)}`;
|
|
8
|
+
return name;
|
|
9
|
+
}
|
|
10
|
+
function scanRoutes(dir, baseUrl, baseName) {
|
|
11
|
+
const routes = [];
|
|
12
|
+
for (const entry of readdirSync(dir)) {
|
|
13
|
+
const full = join(dir, entry);
|
|
14
|
+
if (statSync(full).isDirectory()) {
|
|
15
|
+
routes.push(...scanRoutes(full, `${baseUrl}/${toSegment(entry)}`, `${baseName}-${entry}`));
|
|
16
|
+
} else if (entry.endsWith(".vue")) {
|
|
17
|
+
const base = entry.slice(0, -4);
|
|
18
|
+
const path = base === "index" ? baseUrl : `${baseUrl}/${toSegment(base)}`;
|
|
19
|
+
const name = `${baseName}-${base}`.replace(/[^a-z0-9]+/gi, "-").replace(/-+$/g, "");
|
|
20
|
+
routes.push({ name, path: path || "/", file: full });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return routes;
|
|
24
|
+
}
|
|
25
|
+
const module$1 = defineNuxtModule({
|
|
26
|
+
meta: {
|
|
27
|
+
name: "@revenexx/studio-integrations",
|
|
28
|
+
configKey: "studioIntegrations"
|
|
29
|
+
},
|
|
30
|
+
setup(_options, _nuxt) {
|
|
31
|
+
const { resolve } = createResolver(import.meta.url);
|
|
32
|
+
const runtime = resolve("./runtime");
|
|
33
|
+
addComponentsDir({ path: join(runtime, "components"), pathPrefix: true });
|
|
34
|
+
addImportsDir(join(runtime, "composables"));
|
|
35
|
+
extendPages((pages) => {
|
|
36
|
+
const pagesDir = join(runtime, "pages", "integrations");
|
|
37
|
+
if (!existsSync(pagesDir)) return;
|
|
38
|
+
for (const route of scanRoutes(pagesDir, "/integrations", "integrations")) {
|
|
39
|
+
pages.push(route);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export { module$1 as default };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare var __VLS_4: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_4) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
7
|
+
export default _default;
|
|
8
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
9
|
+
new (): {
|
|
10
|
+
$slots: S;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Sidebar from "./Sidebar.vue";
|
|
3
|
+
const { focused } = useIntegrationsFocus();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="flex h-[calc(100%-0.75rem)] mb-3 mr-3 ml-1 overflow-hidden rounded-lg border border-gray-400/10 outline-1 outline-white/30">
|
|
8
|
+
<div
|
|
9
|
+
class="shrink-0 overflow-hidden transition-[width] duration-200 ease-out border-r border-black/[0.06] bg-white"
|
|
10
|
+
:class="focused ? 'w-0 !border-r-0' : 'w-64'"
|
|
11
|
+
>
|
|
12
|
+
<div class="h-full w-64 overflow-y-auto">
|
|
13
|
+
<Sidebar />
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex-1 min-w-0 overflow-y-auto bg-white/40 backdrop-blur-sm">
|
|
17
|
+
<slot />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
declare var __VLS_4: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_4) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
7
|
+
export default _default;
|
|
8
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
9
|
+
new (): {
|
|
10
|
+
$slots: S;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WorkflowNode, WorkflowTrigger } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Either a workflow node OR a trigger. Distinguished by presence of `nodeSlug`. */
|
|
4
|
+
selection: WorkflowNode | WorkflowTrigger | null;
|
|
5
|
+
secretKeys: string[];
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
9
|
+
remove: () => any;
|
|
10
|
+
close: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
13
|
+
onRemove?: (() => any) | undefined;
|
|
14
|
+
onClose?: (() => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted } from "vue";
|
|
3
|
+
import { AddCircle as Plus, TrashBinMinimalistic as Trash2, CloseCircle as X } from "#solar-icons";
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Input,
|
|
7
|
+
Label,
|
|
8
|
+
Select,
|
|
9
|
+
SelectContent,
|
|
10
|
+
SelectItem,
|
|
11
|
+
SelectTrigger,
|
|
12
|
+
SelectValue,
|
|
13
|
+
Switch,
|
|
14
|
+
Textarea
|
|
15
|
+
} from "@revenexx/studio";
|
|
16
|
+
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
selection: { type: [Object, null], required: true },
|
|
19
|
+
secretKeys: { type: Array, required: true }
|
|
20
|
+
});
|
|
21
|
+
const emit = defineEmits(["update", "close", "remove"]);
|
|
22
|
+
const isNode = computed(() => !!props.selection && "nodeSlug" in props.selection);
|
|
23
|
+
const { credentials, load: loadCredentials } = useIntegrationsCredentials();
|
|
24
|
+
onMounted(() => {
|
|
25
|
+
void loadCredentials();
|
|
26
|
+
});
|
|
27
|
+
function credsOfType(slug) {
|
|
28
|
+
if (!slug) return credentials.value;
|
|
29
|
+
return credentials.value.filter((c) => c.credential_type_slug === slug);
|
|
30
|
+
}
|
|
31
|
+
function credNeedsReauth(id) {
|
|
32
|
+
return credentials.value.some((c) => c.id === id && c.status === "needs_reauth");
|
|
33
|
+
}
|
|
34
|
+
const exprHint = "${{ \u2026 }} ok";
|
|
35
|
+
const manifest = computed(() => {
|
|
36
|
+
if (!isNode.value || !props.selection) return null;
|
|
37
|
+
const wn = props.selection;
|
|
38
|
+
return findManifest(wn.nodeSlug, wn.nodeVersion);
|
|
39
|
+
});
|
|
40
|
+
function labelText(v) {
|
|
41
|
+
if (!v) return "";
|
|
42
|
+
return typeof v === "string" ? v : Object.values(v)[0] ?? "";
|
|
43
|
+
}
|
|
44
|
+
function readConfig(key) {
|
|
45
|
+
if (!isNode.value || !props.selection) return void 0;
|
|
46
|
+
return props.selection.config?.[key];
|
|
47
|
+
}
|
|
48
|
+
function writeConfig(key, value) {
|
|
49
|
+
if (!isNode.value || !props.selection) return;
|
|
50
|
+
const wn = props.selection;
|
|
51
|
+
emit("update", { ...wn, config: { ...wn.config, [key]: value } });
|
|
52
|
+
}
|
|
53
|
+
function writeName(name) {
|
|
54
|
+
if (!props.selection) return;
|
|
55
|
+
emit("update", { ...props.selection, name });
|
|
56
|
+
}
|
|
57
|
+
function readTriggerConfig(key) {
|
|
58
|
+
if (isNode.value || !props.selection) return void 0;
|
|
59
|
+
return props.selection.config[key];
|
|
60
|
+
}
|
|
61
|
+
function writeTriggerConfig(key, value) {
|
|
62
|
+
if (isNode.value || !props.selection) return;
|
|
63
|
+
const wt = props.selection;
|
|
64
|
+
emit("update", { ...wt, config: { ...wt.config, [key]: value } });
|
|
65
|
+
}
|
|
66
|
+
function readArray(key) {
|
|
67
|
+
const v = readConfig(key);
|
|
68
|
+
return Array.isArray(v) ? v : [];
|
|
69
|
+
}
|
|
70
|
+
function pushArrayItem(key, defaultItem) {
|
|
71
|
+
writeConfig(key, [...readArray(key), defaultItem]);
|
|
72
|
+
}
|
|
73
|
+
function removeArrayItem(key, index) {
|
|
74
|
+
const arr = readArray(key).slice();
|
|
75
|
+
arr.splice(index, 1);
|
|
76
|
+
writeConfig(key, arr);
|
|
77
|
+
}
|
|
78
|
+
function setArrayItemField(key, index, fieldKey, value) {
|
|
79
|
+
const arr = readArray(key).slice();
|
|
80
|
+
arr[index] = { ...arr[index] ?? {}, [fieldKey]: value };
|
|
81
|
+
writeConfig(key, arr);
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<template>
|
|
86
|
+
<aside class="flex h-full w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
87
|
+
<template v-if="selection">
|
|
88
|
+
<div class="flex items-center justify-between border-b border-black/[0.06] px-3 py-2">
|
|
89
|
+
<div class="min-w-0">
|
|
90
|
+
<div class="text-mono text-muted-foreground">
|
|
91
|
+
{{ isNode ? "Node" : "Trigger" }}
|
|
92
|
+
</div>
|
|
93
|
+
<div class="text-sm font-semibold truncate text-foreground">
|
|
94
|
+
{{ labelText(selection.name) || (isNode ? selection.nodeSlug : selection.type) }}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="flex size-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
100
|
+
@click="emit('close')"
|
|
101
|
+
>
|
|
102
|
+
<X :size="14" />
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="flex-1 overflow-y-auto p-3 space-y-4">
|
|
107
|
+
<!-- Display name -->
|
|
108
|
+
<div class="space-y-1.5">
|
|
109
|
+
<Label class="text-mono text-muted-foreground">Display name</Label>
|
|
110
|
+
<Input
|
|
111
|
+
:model-value="labelText(selection.name)"
|
|
112
|
+
class="h-8 text-sm bg-white"
|
|
113
|
+
placeholder="Override the manifest name"
|
|
114
|
+
@update:model-value="(v) => writeName(String(v))"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- Trigger config (manual / schedule / webhook) -->
|
|
119
|
+
<template v-if="!isNode">
|
|
120
|
+
<div class="space-y-3">
|
|
121
|
+
<template v-if="selection.type === 'schedule'">
|
|
122
|
+
<div class="space-y-1.5">
|
|
123
|
+
<Label class="text-mono text-muted-foreground">Cron</Label>
|
|
124
|
+
<Input
|
|
125
|
+
:model-value="readTriggerConfig('cron') ?? ''"
|
|
126
|
+
placeholder="0 2 * * *"
|
|
127
|
+
class="h-8 text-sm bg-white font-mono"
|
|
128
|
+
@update:model-value="(v) => writeTriggerConfig('cron', v)"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="space-y-1.5">
|
|
132
|
+
<Label class="text-mono text-muted-foreground">Timezone</Label>
|
|
133
|
+
<Input
|
|
134
|
+
:model-value="readTriggerConfig('timezone') ?? ''"
|
|
135
|
+
placeholder="Europe/Berlin"
|
|
136
|
+
class="h-8 text-sm bg-white"
|
|
137
|
+
@update:model-value="(v) => writeTriggerConfig('timezone', v)"
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
</template>
|
|
141
|
+
<template v-else-if="selection.type === 'webhook'">
|
|
142
|
+
<div class="space-y-1.5">
|
|
143
|
+
<Label class="text-mono text-muted-foreground">Path</Label>
|
|
144
|
+
<Input
|
|
145
|
+
:model-value="readTriggerConfig('path') ?? ''"
|
|
146
|
+
placeholder="/orders"
|
|
147
|
+
class="h-8 text-sm bg-white font-mono"
|
|
148
|
+
@update:model-value="(v) => writeTriggerConfig('path', v)"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
<div class="space-y-1.5">
|
|
152
|
+
<Label class="text-mono text-muted-foreground">Method</Label>
|
|
153
|
+
<Select
|
|
154
|
+
:model-value="readTriggerConfig('method') ?? 'POST'"
|
|
155
|
+
@update:model-value="(v) => writeTriggerConfig('method', v)"
|
|
156
|
+
>
|
|
157
|
+
<SelectTrigger class="h-8 text-sm bg-white">
|
|
158
|
+
<SelectValue />
|
|
159
|
+
</SelectTrigger>
|
|
160
|
+
<SelectContent>
|
|
161
|
+
<SelectItem value="GET">GET</SelectItem>
|
|
162
|
+
<SelectItem value="POST">POST</SelectItem>
|
|
163
|
+
<SelectItem value="PUT">PUT</SelectItem>
|
|
164
|
+
<SelectItem value="PATCH">PATCH</SelectItem>
|
|
165
|
+
<SelectItem value="DELETE">DELETE</SelectItem>
|
|
166
|
+
</SelectContent>
|
|
167
|
+
</Select>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="space-y-1.5">
|
|
170
|
+
<Label class="text-mono text-muted-foreground">Signing secret</Label>
|
|
171
|
+
<Select
|
|
172
|
+
:model-value="readTriggerConfig('secretRef') ?? ''"
|
|
173
|
+
@update:model-value="(v) => writeTriggerConfig('secretRef', v)"
|
|
174
|
+
>
|
|
175
|
+
<SelectTrigger class="h-8 text-sm bg-white">
|
|
176
|
+
<SelectValue placeholder="None" />
|
|
177
|
+
</SelectTrigger>
|
|
178
|
+
<SelectContent>
|
|
179
|
+
<SelectItem v-for="k in secretKeys" :key="k" :value="k">{{ k }}</SelectItem>
|
|
180
|
+
</SelectContent>
|
|
181
|
+
</Select>
|
|
182
|
+
</div>
|
|
183
|
+
</template>
|
|
184
|
+
<template v-else-if="selection.type === 'event'">
|
|
185
|
+
<div class="space-y-1.5">
|
|
186
|
+
<Label class="text-mono text-muted-foreground">Event</Label>
|
|
187
|
+
<Input
|
|
188
|
+
:model-value="readTriggerConfig('event_type') ?? ''"
|
|
189
|
+
placeholder="customer.created"
|
|
190
|
+
class="h-8 text-sm bg-white font-mono"
|
|
191
|
+
@update:model-value="(v) => writeTriggerConfig('event_type', v)"
|
|
192
|
+
/>
|
|
193
|
+
<p class="text-sm text-muted-foreground">
|
|
194
|
+
Runs whenever this platform event fires (e.g. <code class="font-mono">customer.created</code>, <code class="font-mono">order.placed</code>). The event payload is available to the workflow as <code class="font-mono">trigger.payload</code>.
|
|
195
|
+
</p>
|
|
196
|
+
</div>
|
|
197
|
+
</template>
|
|
198
|
+
<template v-else>
|
|
199
|
+
<p class="text-sm text-muted-foreground">
|
|
200
|
+
Manual triggers have no configuration. The workflow runs when invoked from the editor or via the API.
|
|
201
|
+
</p>
|
|
202
|
+
</template>
|
|
203
|
+
</div>
|
|
204
|
+
</template>
|
|
205
|
+
|
|
206
|
+
<!-- Node config — generated from manifest -->
|
|
207
|
+
<template v-else>
|
|
208
|
+
<div v-if="manifest?.config?.length" class="space-y-3">
|
|
209
|
+
<div
|
|
210
|
+
v-for="field in manifest.config"
|
|
211
|
+
:key="field.key"
|
|
212
|
+
class="space-y-1.5"
|
|
213
|
+
>
|
|
214
|
+
<div class="flex items-center justify-between">
|
|
215
|
+
<Label class="text-mono text-muted-foreground">
|
|
216
|
+
{{ labelText(field.label) }}{{ field.required ? " *" : "" }}
|
|
217
|
+
</Label>
|
|
218
|
+
<span v-if="field.expressionAllowed" class="text-sm font-mono text-emerald-600/70">
|
|
219
|
+
{{ exprHint }}
|
|
220
|
+
</span>
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<!-- string / expression -->
|
|
224
|
+
<Input
|
|
225
|
+
v-if="['string', 'expression'].includes(field.type)"
|
|
226
|
+
:model-value="readConfig(field.key) ?? ''"
|
|
227
|
+
:placeholder="labelText(field.placeholder)"
|
|
228
|
+
class="h-8 text-sm bg-white"
|
|
229
|
+
:class="field.type === 'expression' ? 'font-mono' : ''"
|
|
230
|
+
@update:model-value="(v) => writeConfig(field.key, v)"
|
|
231
|
+
/>
|
|
232
|
+
|
|
233
|
+
<!-- number -->
|
|
234
|
+
<Input
|
|
235
|
+
v-else-if="field.type === 'number'"
|
|
236
|
+
type="number"
|
|
237
|
+
:model-value="readConfig(field.key) ?? ''"
|
|
238
|
+
class="h-8 text-sm bg-white"
|
|
239
|
+
@update:model-value="(v) => writeConfig(field.key, v === '' ? null : Number(v))"
|
|
240
|
+
/>
|
|
241
|
+
|
|
242
|
+
<!-- boolean -->
|
|
243
|
+
<div v-else-if="field.type === 'boolean'" class="flex items-center gap-2 pt-1">
|
|
244
|
+
<Switch
|
|
245
|
+
:model-value="Boolean(readConfig(field.key))"
|
|
246
|
+
@update:model-value="(v) => writeConfig(field.key, v)"
|
|
247
|
+
/>
|
|
248
|
+
<span class="text-sm text-muted-foreground">
|
|
249
|
+
{{ labelText(field.description) }}
|
|
250
|
+
</span>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<!-- select -->
|
|
254
|
+
<Select
|
|
255
|
+
v-else-if="field.type === 'select'"
|
|
256
|
+
:model-value="readConfig(field.key) ?? ''"
|
|
257
|
+
@update:model-value="(v) => writeConfig(field.key, v)"
|
|
258
|
+
>
|
|
259
|
+
<SelectTrigger class="h-8 text-sm bg-white">
|
|
260
|
+
<SelectValue :placeholder="labelText(field.placeholder) || 'Pick one'" />
|
|
261
|
+
</SelectTrigger>
|
|
262
|
+
<SelectContent>
|
|
263
|
+
<SelectItem v-for="opt in field.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
|
|
264
|
+
{{ labelText(opt.label) }}
|
|
265
|
+
</SelectItem>
|
|
266
|
+
</SelectContent>
|
|
267
|
+
</Select>
|
|
268
|
+
|
|
269
|
+
<!-- multiselect — pill toggles for now -->
|
|
270
|
+
<div v-else-if="field.type === 'multiselect'" class="flex flex-wrap gap-1">
|
|
271
|
+
<button
|
|
272
|
+
v-for="opt in field.options ?? []"
|
|
273
|
+
:key="String(opt.value)"
|
|
274
|
+
type="button"
|
|
275
|
+
class="rounded-md border border-black/[0.08] px-1.5 py-0.5 text-sm transition"
|
|
276
|
+
:class="(readConfig(field.key) ?? []).includes(opt.value) ? 'bg-emerald-500/15 text-emerald-700 border-emerald-500/30' : 'bg-white text-muted-foreground hover:text-foreground'"
|
|
277
|
+
@click="() => {
|
|
278
|
+
const current = readConfig(field.key) ?? [];
|
|
279
|
+
const has = current.includes(opt.value);
|
|
280
|
+
writeConfig(field.key, has ? current.filter((v) => v !== opt.value) : [...current, opt.value]);
|
|
281
|
+
}"
|
|
282
|
+
>
|
|
283
|
+
{{ labelText(opt.label) }}
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<!-- secret-ref -->
|
|
288
|
+
<Select
|
|
289
|
+
v-else-if="field.type === 'secret-ref'"
|
|
290
|
+
:model-value="readConfig(field.key) ?? ''"
|
|
291
|
+
@update:model-value="(v) => writeConfig(field.key, v)"
|
|
292
|
+
>
|
|
293
|
+
<SelectTrigger class="h-8 text-sm bg-white">
|
|
294
|
+
<SelectValue placeholder="Pick a secret" />
|
|
295
|
+
</SelectTrigger>
|
|
296
|
+
<SelectContent>
|
|
297
|
+
<SelectItem v-for="k in secretKeys" :key="k" :value="k">{{ k }}</SelectItem>
|
|
298
|
+
</SelectContent>
|
|
299
|
+
</Select>
|
|
300
|
+
|
|
301
|
+
<!-- credentials-ref — pick a credential of the required type -->
|
|
302
|
+
<template v-else-if="field.type === 'credentials-ref'">
|
|
303
|
+
<Select
|
|
304
|
+
:model-value="readConfig(field.key) ?? ''"
|
|
305
|
+
@update:model-value="(v) => writeConfig(field.key, v)"
|
|
306
|
+
>
|
|
307
|
+
<SelectTrigger class="h-8 text-sm bg-white">
|
|
308
|
+
<SelectValue placeholder="Pick a credential" />
|
|
309
|
+
</SelectTrigger>
|
|
310
|
+
<SelectContent>
|
|
311
|
+
<SelectItem v-for="c in credsOfType(field.credentialType)" :key="c.id" :value="c.id">
|
|
312
|
+
{{ c.name }}
|
|
313
|
+
</SelectItem>
|
|
314
|
+
</SelectContent>
|
|
315
|
+
</Select>
|
|
316
|
+
<p v-if="credNeedsReauth(readConfig(field.key))" class="text-sm text-amber-600">
|
|
317
|
+
This credential needs reconnect.
|
|
318
|
+
</p>
|
|
319
|
+
<NuxtLink to="/integrations/credentials" class="text-sm text-violet-600 hover:underline">
|
|
320
|
+
Manage credentials →
|
|
321
|
+
</NuxtLink>
|
|
322
|
+
</template>
|
|
323
|
+
|
|
324
|
+
<!-- array of objects (e.g. headers / mappings) -->
|
|
325
|
+
<div v-else-if="field.type === 'array' && field.items?.type === 'object'" class="space-y-2">
|
|
326
|
+
<div
|
|
327
|
+
v-for="(item, idx) in readArray(field.key)"
|
|
328
|
+
:key="idx"
|
|
329
|
+
class="rounded-md border border-black/[0.06] bg-muted/20 p-2 space-y-1.5"
|
|
330
|
+
>
|
|
331
|
+
<div class="flex items-center justify-between">
|
|
332
|
+
<span class="text-mono text-muted-foreground">
|
|
333
|
+
#{{ idx + 1 }}
|
|
334
|
+
</span>
|
|
335
|
+
<button
|
|
336
|
+
type="button"
|
|
337
|
+
class="flex size-5 items-center justify-center rounded text-muted-foreground hover:text-rose-600"
|
|
338
|
+
@click="removeArrayItem(field.key, idx)"
|
|
339
|
+
>
|
|
340
|
+
<Trash2 :size="11" />
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
<div
|
|
344
|
+
v-for="sub in field.items.properties ?? []"
|
|
345
|
+
:key="sub.key"
|
|
346
|
+
class="space-y-1"
|
|
347
|
+
>
|
|
348
|
+
<Label class="text-mono text-muted-foreground">
|
|
349
|
+
{{ labelText(sub.label) }}
|
|
350
|
+
</Label>
|
|
351
|
+
<Input
|
|
352
|
+
:model-value="item[sub.key] ?? ''"
|
|
353
|
+
:placeholder="labelText(sub.placeholder)"
|
|
354
|
+
class="h-7 text-sm bg-white"
|
|
355
|
+
:class="sub.type === 'expression' ? 'font-mono' : ''"
|
|
356
|
+
@update:model-value="(v) => setArrayItemField(field.key, idx, sub.key, v)"
|
|
357
|
+
/>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
<Button
|
|
361
|
+
type="button"
|
|
362
|
+
variant="outline"
|
|
363
|
+
size="sm"
|
|
364
|
+
class="h-7 text-sm w-full"
|
|
365
|
+
@click="pushArrayItem(field.key, Object.fromEntries((field.items?.properties ?? []).map((p) => [p.key, ''])))"
|
|
366
|
+
>
|
|
367
|
+
<Plus :size="11" />
|
|
368
|
+
Add row
|
|
369
|
+
</Button>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<!-- fallback: JSON textarea for complex types -->
|
|
373
|
+
<Textarea
|
|
374
|
+
v-else
|
|
375
|
+
:model-value="readConfig(field.key) === void 0 ? '' : JSON.stringify(readConfig(field.key), null, 2)"
|
|
376
|
+
rows="3"
|
|
377
|
+
class="text-sm font-mono bg-white"
|
|
378
|
+
@update:model-value="(v) => {
|
|
379
|
+
try {
|
|
380
|
+
writeConfig(field.key, v ? JSON.parse(String(v)) : void 0);
|
|
381
|
+
} catch {
|
|
382
|
+
}
|
|
383
|
+
}"
|
|
384
|
+
/>
|
|
385
|
+
|
|
386
|
+
<p v-if="field.description" class="text-sm text-muted-foreground">
|
|
387
|
+
{{ labelText(field.description) }}
|
|
388
|
+
</p>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
<div v-else class="text-sm text-muted-foreground italic">
|
|
392
|
+
This node has no configuration.
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
<!-- Retry section (always available on nodes) -->
|
|
396
|
+
<div class="space-y-1.5 border-t border-black/[0.06] pt-3 mt-3">
|
|
397
|
+
<Label class="text-mono text-muted-foreground">Retry policy</Label>
|
|
398
|
+
<div class="flex items-center gap-2">
|
|
399
|
+
<Switch
|
|
400
|
+
:model-value="!!selection.retry"
|
|
401
|
+
@update:model-value="(v) => emit('update', { ...selection, retry: v ? { maxAttempts: 3, backoffSeconds: 1, strategy: 'exponential' } : void 0 })"
|
|
402
|
+
/>
|
|
403
|
+
<span class="text-sm text-muted-foreground">Retry on failure</span>
|
|
404
|
+
</div>
|
|
405
|
+
<template v-if="selection.retry">
|
|
406
|
+
<div class="grid grid-cols-2 gap-2 pt-1">
|
|
407
|
+
<Input
|
|
408
|
+
type="number"
|
|
409
|
+
:model-value="selection.retry?.maxAttempts ?? 3"
|
|
410
|
+
class="h-7 text-sm bg-white"
|
|
411
|
+
@update:model-value="(v) => emit('update', { ...selection, retry: { ...selection.retry, maxAttempts: Number(v) || 1 } })"
|
|
412
|
+
/>
|
|
413
|
+
<Input
|
|
414
|
+
type="number"
|
|
415
|
+
:model-value="selection.retry?.backoffSeconds ?? 0"
|
|
416
|
+
class="h-7 text-sm bg-white"
|
|
417
|
+
@update:model-value="(v) => emit('update', { ...selection, retry: { ...selection.retry, backoffSeconds: Number(v) || 0 } })"
|
|
418
|
+
/>
|
|
419
|
+
</div>
|
|
420
|
+
<Select
|
|
421
|
+
:model-value="selection.retry?.strategy ?? 'exponential'"
|
|
422
|
+
@update:model-value="(v) => emit('update', { ...selection, retry: { ...selection.retry, strategy: v } })"
|
|
423
|
+
>
|
|
424
|
+
<SelectTrigger class="h-7 text-sm bg-white">
|
|
425
|
+
<SelectValue />
|
|
426
|
+
</SelectTrigger>
|
|
427
|
+
<SelectContent>
|
|
428
|
+
<SelectItem value="linear">linear backoff</SelectItem>
|
|
429
|
+
<SelectItem value="exponential">exponential backoff</SelectItem>
|
|
430
|
+
</SelectContent>
|
|
431
|
+
</Select>
|
|
432
|
+
</template>
|
|
433
|
+
</div>
|
|
434
|
+
</template>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
<div class="border-t border-black/[0.06] p-3">
|
|
438
|
+
<Button
|
|
439
|
+
variant="outline"
|
|
440
|
+
size="sm"
|
|
441
|
+
class="w-full h-7 text-sm text-rose-600 hover:text-rose-700"
|
|
442
|
+
@click="emit('remove')"
|
|
443
|
+
>
|
|
444
|
+
<Trash2 :size="12" />
|
|
445
|
+
Remove from workflow
|
|
446
|
+
</Button>
|
|
447
|
+
</div>
|
|
448
|
+
</template>
|
|
449
|
+
</aside>
|
|
450
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WorkflowNode, WorkflowTrigger } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
/** Either a workflow node OR a trigger. Distinguished by presence of `nodeSlug`. */
|
|
4
|
+
selection: WorkflowNode | WorkflowTrigger | null;
|
|
5
|
+
secretKeys: string[];
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
9
|
+
remove: () => any;
|
|
10
|
+
close: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
13
|
+
onRemove?: (() => any) | undefined;
|
|
14
|
+
onClose?: (() => any) | undefined;
|
|
15
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
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;
|