@revenexx/studio-integrations 0.5.1 → 0.7.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/CHANGELOG.md +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
5
|
+
import { useLoadGate } from "./useLoadGate.js";
|
|
4
6
|
const KEY_PATTERN = /^[A-Z0-9_]+$/;
|
|
5
7
|
export function useIntegrationsSecrets() {
|
|
6
8
|
const secrets = useState("integrations.secrets", () => []);
|
|
7
9
|
const loading = useState("integrations.secrets.loading", () => false);
|
|
8
10
|
const error = useState("integrations.secrets.error", () => null);
|
|
9
11
|
const loaded = useState("integrations.secrets.loaded", () => false);
|
|
12
|
+
const gate = useLoadGate("integrations.secrets", { loading, loaded });
|
|
10
13
|
const api = useIntegrationsApi();
|
|
14
|
+
const t = useIntegrationsT();
|
|
11
15
|
function isValidKey(key) {
|
|
12
16
|
return KEY_PATTERN.test(key);
|
|
13
17
|
}
|
|
14
18
|
function normalize(res) {
|
|
15
19
|
const obj = res;
|
|
20
|
+
if (Array.isArray(obj?.data)) return obj.data.map(toRecord);
|
|
21
|
+
if (Array.isArray(obj?.secrets)) return obj.secrets.map(toRecord);
|
|
16
22
|
if (Array.isArray(obj?.keys)) return obj.keys.map((key) => ({ key, created_at: "", updated_at: "" }));
|
|
17
|
-
if (Array.isArray(
|
|
18
|
-
if (Array.isArray(obj?.secrets)) return obj.secrets;
|
|
19
|
-
if (Array.isArray(res)) return res;
|
|
23
|
+
if (Array.isArray(res)) return res.map(toRecord);
|
|
20
24
|
return [];
|
|
21
25
|
}
|
|
26
|
+
function toRecord(r) {
|
|
27
|
+
return { key: r.key ?? "", created_at: r.created_at ?? "", updated_at: r.updated_at ?? "" };
|
|
28
|
+
}
|
|
22
29
|
function captureError(e, fallback) {
|
|
23
30
|
if (e instanceof IntegrationsApiError) return e.message;
|
|
24
31
|
return e?.message ?? fallback;
|
|
25
32
|
}
|
|
26
33
|
async function load(force = false) {
|
|
27
|
-
if (
|
|
28
|
-
if (loaded.value && !force) return secrets.value;
|
|
29
|
-
loading.value = true;
|
|
34
|
+
if (!await gate.claim(force)) return secrets.value;
|
|
30
35
|
error.value = null;
|
|
31
36
|
try {
|
|
32
37
|
secrets.value = normalize(await api.get("/secrets"));
|
|
33
38
|
loaded.value = true;
|
|
34
39
|
} catch (e) {
|
|
35
|
-
error.value = captureError(e, "
|
|
40
|
+
error.value = captureError(e, t("integration.secrets.error.loadFailed"));
|
|
36
41
|
} finally {
|
|
37
|
-
|
|
42
|
+
gate.release();
|
|
38
43
|
}
|
|
39
44
|
return secrets.value;
|
|
40
45
|
}
|
|
@@ -44,7 +49,7 @@ export function useIntegrationsSecrets() {
|
|
|
44
49
|
}
|
|
45
50
|
async function upsert(key, value) {
|
|
46
51
|
if (!isValidKey(key)) {
|
|
47
|
-
throw new Error("
|
|
52
|
+
throw new Error(t("integration.secrets.error.invalidKey"));
|
|
48
53
|
}
|
|
49
54
|
const created = !secrets.value.some((s) => s.key === key);
|
|
50
55
|
await api.post("/secrets", { key, value });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
import type { Translate } from '../i18n/translate.js';
|
|
3
|
+
/**
|
|
4
|
+
* `useIntegrationsT()` — the studio's own interface strings.
|
|
5
|
+
*
|
|
6
|
+
* const t = useIntegrationsT()
|
|
7
|
+
* t('integration.dashboard.runs.title')
|
|
8
|
+
* t('integration.dashboard.attention.missedRuns', missed.length)
|
|
9
|
+
* t('integration.credentials.toast.deleted', { name: credential.name })
|
|
10
|
+
*
|
|
11
|
+
* Returns `t` directly rather than `{ t }`, matching `useT()` in
|
|
12
|
+
* `@revenexx/studio-shared`. It is **not** that function: `useT()` asks the
|
|
13
|
+
* host's catalogue, which has no `integration.*` keys in it, so this module
|
|
14
|
+
* reads its own (`runtime/i18n/messages/*.json`) instead. The names differ on
|
|
15
|
+
* purpose — studio-shared registers a global auto-import called `useT`, and two
|
|
16
|
+
* modules claiming one name is a silent, one-wins collision.
|
|
17
|
+
*
|
|
18
|
+
* Not a composable in the reactive-state sense: no component instance is
|
|
19
|
+
* needed, so it may be called from a plain helper or outside `setup()`.
|
|
20
|
+
*
|
|
21
|
+
* **Reactivity is on the caller.** `t()` reads the language at call time, so
|
|
22
|
+
* anything evaluated once — a `const TABS = [...]` at module or setup scope —
|
|
23
|
+
* keeps whatever language was current when the file loaded. Put it in a
|
|
24
|
+
* `computed()`. That is the one way to ship this looking finished and have it
|
|
25
|
+
* not switch.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useIntegrationsT(): Translate;
|
|
28
|
+
/**
|
|
29
|
+
* The language the studio's chrome is rendering in, as a `ComputedRef`.
|
|
30
|
+
*
|
|
31
|
+
* For the rare case that needs the tag itself — an `Intl` format, a `lang`
|
|
32
|
+
* attribute — rather than a translated string. To *set* it, that is
|
|
33
|
+
* `useIntegrationsLocale()`, which is the host's seam.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useIntegrationsUiLocale(): ComputedRef<string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { computed } from "vue";
|
|
2
|
+
import { studioLocale } from "../utils/localize.js";
|
|
3
|
+
import { translate } from "../i18n/translate.js";
|
|
4
|
+
export function useIntegrationsT() {
|
|
5
|
+
return translate;
|
|
6
|
+
}
|
|
7
|
+
export function useIntegrationsUiLocale() {
|
|
8
|
+
return computed(() => studioLocale());
|
|
9
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TemplateRequirements, WorkflowTemplate } from '../types/integrations.js';
|
|
2
2
|
export declare function useIntegrationsTemplates(): {
|
|
3
|
-
templates:
|
|
3
|
+
templates: import("vue").ComputedRef<WorkflowTemplate[]>;
|
|
4
4
|
loaded: any;
|
|
5
5
|
loading: any;
|
|
6
6
|
error: any;
|
|
7
|
-
total: import("vue").ComputedRef<
|
|
8
|
-
|
|
7
|
+
total: import("vue").ComputedRef<number>;
|
|
8
|
+
startingPoints: import("vue").ComputedRef<WorkflowTemplate[]>;
|
|
9
9
|
load: (force?: boolean) => Promise<WorkflowTemplate[]>;
|
|
10
10
|
list: () => WorkflowTemplate[];
|
|
11
11
|
get: (slug: string) => WorkflowTemplate | null;
|
|
@@ -2,6 +2,7 @@ import { computed } from "vue";
|
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
4
|
import { resolveLocalized } from "../utils/localize.js";
|
|
5
|
+
import { useLoadGate } from "./useLoadGate.js";
|
|
5
6
|
const BLOB_VERSION = "v0-draft";
|
|
6
7
|
const INDUSTRY_VALUES = ["mechanical-engineering", "chemicals", "wholesale", "manufacturing", "retail", "any"];
|
|
7
8
|
function plainExcerpt(md) {
|
|
@@ -34,14 +35,18 @@ function mapListItem(api) {
|
|
|
34
35
|
triggerTypes: api.triggerTypes ?? [],
|
|
35
36
|
images: api.images ?? [],
|
|
36
37
|
package: api.package ? { name: api.package.name, label: api.package.label ?? null } : null,
|
|
37
|
-
|
|
38
|
+
// The registry documents this field as free-form and returns it as nullable,
|
|
39
|
+
// so "no category" is a state and not an error. It used to become `sync` —
|
|
40
|
+
// a category nothing publishes, so the studio was badging a template with a
|
|
41
|
+
// name no publisher had chosen. Empty travels instead, and every surface that
|
|
42
|
+
// draws a chip leaves it out rather than filling it in (PO-243).
|
|
43
|
+
category: api.category || "",
|
|
38
44
|
industries: mapIndustries(api.industries),
|
|
39
45
|
vendors: api.vendors ?? [],
|
|
40
46
|
requiredSecrets: [],
|
|
41
47
|
level: api.level ?? "beginner",
|
|
42
48
|
outline: [],
|
|
43
49
|
nodeCount: 0,
|
|
44
|
-
installs: 0,
|
|
45
50
|
publisher: publisherFor(api.slug, api.vendors ?? []),
|
|
46
51
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47
52
|
blob: emptyBlob(resolveLocalized(api.name)),
|
|
@@ -79,10 +84,18 @@ function applyDetail(base, api) {
|
|
|
79
84
|
};
|
|
80
85
|
}
|
|
81
86
|
export function useIntegrationsTemplates() {
|
|
82
|
-
const
|
|
87
|
+
const t = useIntegrationsT();
|
|
88
|
+
const rawList = useState("integrations.templates.raw", () => []);
|
|
89
|
+
const rawDetails = useState("integrations.templates.rawDetails", () => ({}));
|
|
90
|
+
const templates = computed(() => rawList.value.map((item) => {
|
|
91
|
+
const base = mapListItem(item);
|
|
92
|
+
const detail = rawDetails.value[item.slug];
|
|
93
|
+
return detail ? applyDetail(base, detail) : base;
|
|
94
|
+
}));
|
|
83
95
|
const loaded = useState("integrations.templates.loaded", () => false);
|
|
84
96
|
const loading = useState("integrations.templates.loading", () => false);
|
|
85
97
|
const error = useState("integrations.templates.error", () => null);
|
|
98
|
+
const gate = useLoadGate("integrations.templates", { loading, loaded });
|
|
86
99
|
const api = useIntegrationsApi();
|
|
87
100
|
function unwrap(res) {
|
|
88
101
|
return res?.data ?? res;
|
|
@@ -91,20 +104,20 @@ export function useIntegrationsTemplates() {
|
|
|
91
104
|
return e instanceof IntegrationsApiError ? e.message : e?.message ?? fallback;
|
|
92
105
|
}
|
|
93
106
|
async function load(force = false) {
|
|
94
|
-
if (
|
|
95
|
-
if (loaded.value && !force) return templates.value;
|
|
96
|
-
loading.value = true;
|
|
107
|
+
if (!await gate.claim(force)) return templates.value;
|
|
97
108
|
error.value = null;
|
|
98
109
|
try {
|
|
99
110
|
const list2 = unwrap(await api.get("/templates")) ?? [];
|
|
100
|
-
|
|
111
|
+
rawList.value = list2;
|
|
112
|
+
rawDetails.value = {};
|
|
101
113
|
loaded.value = true;
|
|
102
114
|
} catch (e) {
|
|
103
|
-
error.value = captureError(e, "
|
|
104
|
-
|
|
115
|
+
error.value = captureError(e, t("integration.templates.state.error.fallback"));
|
|
116
|
+
rawList.value = [];
|
|
117
|
+
rawDetails.value = {};
|
|
105
118
|
loaded.value = true;
|
|
106
119
|
} finally {
|
|
107
|
-
|
|
120
|
+
gate.release();
|
|
108
121
|
}
|
|
109
122
|
return templates.value;
|
|
110
123
|
}
|
|
@@ -112,16 +125,15 @@ export function useIntegrationsTemplates() {
|
|
|
112
125
|
return templates.value;
|
|
113
126
|
}
|
|
114
127
|
function get(slug) {
|
|
115
|
-
return templates.value.find((
|
|
128
|
+
return templates.value.find((t2) => t2.slug === slug) ?? null;
|
|
116
129
|
}
|
|
117
130
|
async function fetchDetail(slug) {
|
|
118
131
|
const base = get(slug);
|
|
119
132
|
if (!base?.fromApi) return base;
|
|
120
133
|
try {
|
|
121
134
|
const detail = unwrap(await api.get(`/templates/${slug}`));
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return merged;
|
|
135
|
+
rawDetails.value = { ...rawDetails.value, [slug]: detail };
|
|
136
|
+
return get(slug);
|
|
125
137
|
} catch {
|
|
126
138
|
return base;
|
|
127
139
|
}
|
|
@@ -165,6 +177,6 @@ export function useIntegrationsTemplates() {
|
|
|
165
177
|
return instantiate(slug);
|
|
166
178
|
}
|
|
167
179
|
const total = computed(() => templates.value.length);
|
|
168
|
-
const
|
|
169
|
-
return { templates, loaded, loading, error, total,
|
|
180
|
+
const startingPoints = computed(() => templates.value.slice(0, 4));
|
|
181
|
+
return { templates, loaded, loading, error, total, startingPoints, load, list, get, fetchDetail, ensureDetails, requirements, instantiate, install };
|
|
170
182
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
2
3
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
|
+
import { useLoadGate } from "./useLoadGate.js";
|
|
3
5
|
export function useIntegrationsTriggers() {
|
|
4
6
|
const all = useState("integrations.triggers.all", () => []);
|
|
5
7
|
const loading = useState("integrations.triggers.loading", () => false);
|
|
6
8
|
const error = useState("integrations.triggers.error", () => null);
|
|
7
9
|
const loaded = useState("integrations.triggers.loaded", () => false);
|
|
8
10
|
const api = useIntegrationsApi();
|
|
9
|
-
const
|
|
11
|
+
const t = useIntegrationsT();
|
|
12
|
+
const { workflows, load: loadWorkflows, loaded: workflowsLoaded } = useIntegrationsWorkflows();
|
|
13
|
+
const gate = useLoadGate("integrations.triggers", { loading, loaded });
|
|
10
14
|
function unwrapList(res) {
|
|
11
15
|
if (Array.isArray(res)) return res;
|
|
12
16
|
return res?.data ?? [];
|
|
@@ -15,21 +19,19 @@ export function useIntegrationsTriggers() {
|
|
|
15
19
|
return unwrapList(await api.get(`/workflows/${workflowId}/triggers`));
|
|
16
20
|
}
|
|
17
21
|
async function loadAll(force = false) {
|
|
18
|
-
if (
|
|
19
|
-
if (loaded.value && !force) return all.value;
|
|
20
|
-
loading.value = true;
|
|
22
|
+
if (!await gate.claim(force)) return all.value;
|
|
21
23
|
error.value = null;
|
|
22
24
|
try {
|
|
23
25
|
await loadWorkflows();
|
|
24
26
|
const settled = await Promise.allSettled(
|
|
25
|
-
workflows.value.map(async (wf) => (await list(wf.id)).map((
|
|
27
|
+
workflows.value.map(async (wf) => (await list(wf.id)).map((t2) => ({ ...t2, workflowName: wf.name })))
|
|
26
28
|
);
|
|
27
29
|
all.value = settled.flatMap((p) => p.status === "fulfilled" ? p.value : []);
|
|
28
|
-
loaded.value =
|
|
30
|
+
loaded.value = workflowsLoaded.value && settled.every((p) => p.status === "fulfilled");
|
|
29
31
|
} catch (e) {
|
|
30
|
-
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? "
|
|
32
|
+
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? t("integration.triggers.error.loadFailed");
|
|
31
33
|
} finally {
|
|
32
|
-
|
|
34
|
+
gate.release();
|
|
33
35
|
}
|
|
34
36
|
return all.value;
|
|
35
37
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
5
|
+
import { useLoadGate } from "./useLoadGate.js";
|
|
4
6
|
const BLOB_VERSION = "v0-draft";
|
|
5
7
|
function toCockpit(api) {
|
|
6
8
|
const apiBlob = api.blob ?? {};
|
|
@@ -35,6 +37,10 @@ function toCockpit(api) {
|
|
|
35
37
|
execution_mode: api.execution_mode,
|
|
36
38
|
buildStatus: api.build_status ?? null,
|
|
37
39
|
buildError: api.build_error ?? null,
|
|
40
|
+
// Carried through as-is: `undefined` (e.g. the list response, which omits
|
|
41
|
+
// it) means "unknown", NOT "valid" — callers must fail open and only gate
|
|
42
|
+
// activation on a non-empty array.
|
|
43
|
+
warnings: api.warnings,
|
|
38
44
|
created_at: api.created_at ?? "",
|
|
39
45
|
updated_at: api.updated_at ?? ""
|
|
40
46
|
};
|
|
@@ -76,7 +82,9 @@ export function useIntegrationsWorkflows() {
|
|
|
76
82
|
const loading = useState("integrations.workflows.loading", () => false);
|
|
77
83
|
const error = useState("integrations.workflows.error", () => null);
|
|
78
84
|
const loaded = useState("integrations.workflows.loaded", () => false);
|
|
85
|
+
const gate = useLoadGate("integrations.workflows", { loading, loaded });
|
|
79
86
|
const api = useIntegrationsApi();
|
|
87
|
+
const t = useIntegrationsT();
|
|
80
88
|
function unwrap(res) {
|
|
81
89
|
return res?.data ?? res;
|
|
82
90
|
}
|
|
@@ -89,18 +97,16 @@ export function useIntegrationsWorkflows() {
|
|
|
89
97
|
workflows.value = idx < 0 ? [...workflows.value, wf] : workflows.value.map((w) => w.id === wf.id ? wf : w);
|
|
90
98
|
}
|
|
91
99
|
async function load(force = false) {
|
|
92
|
-
if (
|
|
93
|
-
if (loaded.value && !force) return workflows.value;
|
|
94
|
-
loading.value = true;
|
|
100
|
+
if (!await gate.claim(force)) return workflows.value;
|
|
95
101
|
error.value = null;
|
|
96
102
|
try {
|
|
97
103
|
const list2 = unwrap(await api.get("/workflows")) ?? [];
|
|
98
104
|
workflows.value = list2.map(toCockpit);
|
|
99
105
|
loaded.value = true;
|
|
100
106
|
} catch (e) {
|
|
101
|
-
error.value = captureError(e, "
|
|
107
|
+
error.value = captureError(e, t("integration.workflows.error.loadFailed"));
|
|
102
108
|
} finally {
|
|
103
|
-
|
|
109
|
+
gate.release();
|
|
104
110
|
}
|
|
105
111
|
return workflows.value;
|
|
106
112
|
}
|
|
@@ -130,7 +136,7 @@ export function useIntegrationsWorkflows() {
|
|
|
130
136
|
}
|
|
131
137
|
async function duplicate(id, name) {
|
|
132
138
|
const source = await get(id);
|
|
133
|
-
if (!source) throw new Error("
|
|
139
|
+
if (!source) throw new Error(t("integration.workflows.error.notFound"));
|
|
134
140
|
const blob = JSON.parse(JSON.stringify(source.blob));
|
|
135
141
|
const handleMap = /* @__PURE__ */ new Map();
|
|
136
142
|
for (const trigger of blob.triggers ?? []) {
|
|
@@ -166,7 +172,7 @@ export function useIntegrationsWorkflows() {
|
|
|
166
172
|
lastError = err;
|
|
167
173
|
}
|
|
168
174
|
}
|
|
169
|
-
throw lastError instanceof Error ? lastError : new Error("
|
|
175
|
+
throw lastError instanceof Error ? lastError : new Error(t("integration.workflows.error.duplicateFailed"));
|
|
170
176
|
}
|
|
171
177
|
async function update(id, patch) {
|
|
172
178
|
const wf = toCockpit(unwrap(await api.put(`/workflows/${id}`, toPayload({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { LoadGate } from '../utils/loadGate.js';
|
|
3
|
+
/**
|
|
4
|
+
* A composable's load gate, keyed by the namespace its own state already uses:
|
|
5
|
+
* `useLoadGate('integrations.triggers', { loading, loaded })`.
|
|
6
|
+
*
|
|
7
|
+
* The rule lives in `utils/loadGate.ts`; this is the wiring, and the wiring is the
|
|
8
|
+
* reason it is a composable. The counters have to share the flag's fate — one per
|
|
9
|
+
* Nuxt app on the client, one per request on the server — so they are `useState`
|
|
10
|
+
* like the flag itself. A module-level counter would be shared between two
|
|
11
|
+
* concurrent server-rendered requests, and then one tenant's finished load would
|
|
12
|
+
* answer another tenant's forced caller.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useLoadGate(key: string, flags: {
|
|
15
|
+
loading: Ref<boolean>;
|
|
16
|
+
loaded?: Ref<boolean>;
|
|
17
|
+
}): LoadGate;
|
|
@@ -15,7 +15,8 @@ export function isResolvableValue(v) {
|
|
|
15
15
|
}
|
|
16
16
|
function classifyError(err) {
|
|
17
17
|
const status = err instanceof IntegrationsApiError ? err.status : void 0;
|
|
18
|
-
const
|
|
18
|
+
const t = useIntegrationsT();
|
|
19
|
+
const message = err instanceof IntegrationsApiError ? err.message : err?.message ?? t("integration.nodeConfig.resolve.error.generic");
|
|
19
20
|
let kind;
|
|
20
21
|
switch (status) {
|
|
21
22
|
case 409:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure core of the tidy-up branch reorder (see WorkflowEditor's
|
|
3
|
+
* `reorderBranchesByHandle`): given laid-out node positions and the outgoing
|
|
4
|
+
* edges tagged with their output-port index, compute the x moves that put
|
|
5
|
+
* each source's branch subtrees into handle order.
|
|
6
|
+
*
|
|
7
|
+
* dagre places nodes as points and is blind to which output HANDLE an edge
|
|
8
|
+
* leaves from, so a node's branches can land in the opposite left-to-right
|
|
9
|
+
* order to their handles and cross right under the node. Only self-contained,
|
|
10
|
+
* pairwise-disjoint subtrees are moved — a reconverging branch (diamond,
|
|
11
|
+
* shared descendant, outside parent) leaves the whole node untouched rather
|
|
12
|
+
* than corrupted. Idempotent: an already-ordered node yields no moves.
|
|
13
|
+
*
|
|
14
|
+
* The crossing is owned by the direct CHILD NODE's own x, not the subtree's
|
|
15
|
+
* extent: dagre centres a node over its (possibly wide) subtree, so a spine —
|
|
16
|
+
* a long main chain where each step also hangs off a short error/leaf branch —
|
|
17
|
+
* puts the leaf to the LEFT while the continuation node sits at its subtree's
|
|
18
|
+
* centre to the right, crossing out/error even though the two subtrees never
|
|
19
|
+
* share a node. So we reorder by the child nodes' positions: the children keep
|
|
20
|
+
* exactly the set of x slots they already occupy, re-slotted into handle order.
|
|
21
|
+
* That's a pure permutation — the rank's footprint (and the source's centre
|
|
22
|
+
* over it) is preserved and no branch is flung wide, whatever its subtree size.
|
|
23
|
+
*
|
|
24
|
+
* Kept free of vue-flow/component state so it can be unit-tested against
|
|
25
|
+
* hand-built graphs.
|
|
26
|
+
*/
|
|
27
|
+
export interface ReorderNode {
|
|
28
|
+
x: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ReorderEdge {
|
|
31
|
+
source: string;
|
|
32
|
+
target: string;
|
|
33
|
+
/** Left-to-right index of the source's output handle this edge leaves from. */
|
|
34
|
+
portIndex: number;
|
|
35
|
+
}
|
|
36
|
+
/** Node id → new x, for exactly the nodes that need to move. */
|
|
37
|
+
export declare function reorderBranchSubtrees(nodes: Map<string, ReorderNode>, edges: ReorderEdge[]): Map<string, number>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function reorderBranchSubtrees(nodes, edges) {
|
|
2
|
+
const inGraph = (e) => nodes.has(e.source) && nodes.has(e.target);
|
|
3
|
+
const children = /* @__PURE__ */ new Map();
|
|
4
|
+
const parents = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const e of edges) {
|
|
6
|
+
if (!inGraph(e)) continue;
|
|
7
|
+
(children.get(e.source) ?? (children.set(e.source, []), children.get(e.source))).push(e.target);
|
|
8
|
+
(parents.get(e.target) ?? (parents.set(e.target, []), parents.get(e.target))).push(e.source);
|
|
9
|
+
}
|
|
10
|
+
const subtreeOf = (root) => {
|
|
11
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12
|
+
const stack = [root];
|
|
13
|
+
while (stack.length) {
|
|
14
|
+
const v = stack.pop();
|
|
15
|
+
if (seen.has(v)) continue;
|
|
16
|
+
seen.add(v);
|
|
17
|
+
for (const c of children.get(v) ?? []) if (!seen.has(c)) stack.push(c);
|
|
18
|
+
}
|
|
19
|
+
return seen;
|
|
20
|
+
};
|
|
21
|
+
const branchesBySource = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const e of edges) {
|
|
23
|
+
if (!inGraph(e)) continue;
|
|
24
|
+
const byTarget = branchesBySource.get(e.source) ?? (branchesBySource.set(e.source, /* @__PURE__ */ new Map()), branchesBySource.get(e.source));
|
|
25
|
+
byTarget.set(e.target, Math.min(byTarget.get(e.target) ?? Infinity, e.portIndex));
|
|
26
|
+
}
|
|
27
|
+
const moved = /* @__PURE__ */ new Map();
|
|
28
|
+
const xOf = (id) => moved.get(id) ?? nodes.get(id).x;
|
|
29
|
+
for (const [source, byTarget] of branchesBySource) {
|
|
30
|
+
const targets = [...byTarget].map(([target, idx]) => ({ target, idx }));
|
|
31
|
+
if (targets.length < 2) continue;
|
|
32
|
+
const subtrees = new Map(targets.map((t) => [t.target, subtreeOf(t.target)]));
|
|
33
|
+
let safe = true;
|
|
34
|
+
const union = /* @__PURE__ */ new Set();
|
|
35
|
+
outer: for (const t of targets) {
|
|
36
|
+
const st = subtrees.get(t.target);
|
|
37
|
+
if (st.has(source)) {
|
|
38
|
+
safe = false;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
for (const m of st) {
|
|
42
|
+
if (union.has(m)) {
|
|
43
|
+
safe = false;
|
|
44
|
+
break outer;
|
|
45
|
+
}
|
|
46
|
+
union.add(m);
|
|
47
|
+
for (const p of parents.get(m) ?? []) {
|
|
48
|
+
if (p !== source && !st.has(p)) {
|
|
49
|
+
safe = false;
|
|
50
|
+
break outer;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!safe) continue;
|
|
56
|
+
const desired = [...targets].sort((a, b) => a.idx - b.idx || a.target.localeCompare(b.target));
|
|
57
|
+
const current = [...targets].sort((a, b) => xOf(a.target) - xOf(b.target) || a.target.localeCompare(b.target));
|
|
58
|
+
if (current.every((t, i) => t.target === desired[i].target)) continue;
|
|
59
|
+
const slots = current.map((t) => xOf(t.target));
|
|
60
|
+
for (let i = 0; i < desired.length; i++) {
|
|
61
|
+
const target = desired[i].target;
|
|
62
|
+
const dx = slots[i] - xOf(target);
|
|
63
|
+
if (dx !== 0) {
|
|
64
|
+
for (const m of subtrees.get(target)) {
|
|
65
|
+
moved.set(m, xOf(m) + dx);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return moved;
|
|
71
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the logical port name out of a canvas handle id.
|
|
3
|
+
*
|
|
4
|
+
* Each node on the canvas renders four connection points per port, one per
|
|
5
|
+
* side, so that an edge can leave and arrive without crossing a card body. The
|
|
6
|
+
* side rides along in the handle id as a `__<side>` suffix; the workflow blob
|
|
7
|
+
* stores only the port name. Everything that persists an edge, or reasons about
|
|
8
|
+
* which port an edge left from, has to read the name back out — and an id whose
|
|
9
|
+
* name is missing has to be told apart from one whose name is unusual.
|
|
10
|
+
*
|
|
11
|
+
* An output dot is `<port>__<side>`. A node's single input dot is `__in__<side>`
|
|
12
|
+
* and a group frame's is `__in__top`: the separator sits at the very front, so
|
|
13
|
+
* there is no port name in front of it. Those answer `null` — **not** the empty
|
|
14
|
+
* string, which is what a `slice(0, 0)` would hand back. Every caller writes
|
|
15
|
+
* `portFromHandleId(id) ?? 'default'`, and `??` does not fire on `''`, so an
|
|
16
|
+
* empty name would travel on past the fallback that exists to catch it.
|
|
17
|
+
*
|
|
18
|
+
* It lives here rather than in the editor because it is the one place that knows
|
|
19
|
+
* how to read an id, and because that is what makes it testable: the editor is
|
|
20
|
+
* an SFC, out of reach of the unit suite, and the defect above shipped once
|
|
21
|
+
* already without anything going red.
|
|
22
|
+
*/
|
|
23
|
+
export declare function portFromHandleId(handleId: string | null | undefined): string | null;
|
|
@@ -13,11 +13,52 @@ export declare function applyNodeCatalog(manifests: NodeManifest[]): void;
|
|
|
13
13
|
* Categories rendered in the picker. Colors map to the L.I.D.F.
|
|
14
14
|
* domain palette we use across the cockpit.
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* The catalogue's five top-level groups.
|
|
18
|
+
*
|
|
19
|
+
* `label` is the group's **stable English identity**, not display text: the
|
|
20
|
+
* palette matches a stack path back to a category by comparing against it, so
|
|
21
|
+
* translating it in place would break navigation the moment the language
|
|
22
|
+
* changed. Anything that puts a group name on screen calls
|
|
23
|
+
* {@link nodeCategoryLabel} instead.
|
|
24
|
+
*/
|
|
16
25
|
export declare const NODE_CATEGORIES: Array<{
|
|
17
26
|
key: NodeManifest['category'];
|
|
18
27
|
label: string;
|
|
19
28
|
color: string;
|
|
20
29
|
}>;
|
|
30
|
+
/**
|
|
31
|
+
* A category's name for the reader, read at call time so it follows a language
|
|
32
|
+
* switch. Falls back to the English identity for a category the catalogue has
|
|
33
|
+
* no wording for — visible and usable, rather than a raw key.
|
|
34
|
+
*/
|
|
35
|
+
export declare function nodeCategoryLabel(key: NodeManifest['category'] | string | null | undefined): string;
|
|
36
|
+
/** The trigger kinds the catalogue carries a word for, as a runtime value —
|
|
37
|
+
* `TriggerType` is a type and cannot be asked at runtime what it contains. */
|
|
38
|
+
/**
|
|
39
|
+
* The trigger kinds the studio has a word for, in the order a reader is offered
|
|
40
|
+
* them. Exported for the same reason `RUN_STATUS_WORDS` is: the runs list narrows
|
|
41
|
+
* by trigger kind and needs the vocabulary it labels, not a second copy of it.
|
|
42
|
+
*/
|
|
43
|
+
export declare const TRIGGER_TYPE_WORDS: readonly ["manual", "schedule", "webhook", "event"];
|
|
44
|
+
/**
|
|
45
|
+
* What a trigger's kind is called for the reader — `manual` as "Manual", and in
|
|
46
|
+
* German as "Manuell".
|
|
47
|
+
*
|
|
48
|
+
* Here rather than in a component because two surfaces say it: the trigger card
|
|
49
|
+
* on the canvas and the Trigger column of the runs list, which used to print the
|
|
50
|
+
* raw field and so read English under a German heading. One function is the same
|
|
51
|
+
* lesson `resolveLocalized` was built on — a word said in two places gets
|
|
52
|
+
* translated in one of them.
|
|
53
|
+
*
|
|
54
|
+
* The keys are `integration.common.triggerType.*`: they belong to neither
|
|
55
|
+
* surface, and the catalogue puts a word shared across surfaces under `common`.
|
|
56
|
+
*
|
|
57
|
+
* A kind the catalogue has no word for is returned as it came, the way
|
|
58
|
+
* `runStatusLabel` returns an unknown status: asking for the key would put the
|
|
59
|
+
* key itself on screen, because a missing message resolves to its own name.
|
|
60
|
+
*/
|
|
61
|
+
export declare function triggerTypeLabel(type: TriggerType | string | null | undefined): string;
|
|
21
62
|
export declare function findManifest(slug: string, version?: string): NodeManifest | null;
|
|
22
63
|
export declare const TRIGGER_OUTPUT_PORT = "default";
|
|
23
64
|
export interface TriggerTemplate {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reactive } from "vue";
|
|
2
|
+
import { useIntegrationsT } from "../composables/useIntegrationsT.js";
|
|
2
3
|
export const INTEGRATION_NODE_CATALOG = reactive([]);
|
|
3
4
|
export function applyNodeCatalog(manifests) {
|
|
4
5
|
INTEGRATION_NODE_CATALOG.splice(0, INTEGRATION_NODE_CATALOG.length, ...manifests);
|
|
@@ -12,6 +13,19 @@ export const NODE_CATEGORIES = [
|
|
|
12
13
|
{ key: "transform", label: "Transforms", color: "bg-slate-100 text-amber-700/80" },
|
|
13
14
|
{ key: "control", label: "Control flow", color: "bg-slate-100 text-rose-600/80" }
|
|
14
15
|
];
|
|
16
|
+
export function nodeCategoryLabel(key) {
|
|
17
|
+
const category = NODE_CATEGORIES.find((c) => c.key === key);
|
|
18
|
+
if (!category) return "";
|
|
19
|
+
return useIntegrationsT()(`integration.nodes.category.${category.key}`) || category.label;
|
|
20
|
+
}
|
|
21
|
+
export const TRIGGER_TYPE_WORDS = ["manual", "schedule", "webhook", "event"];
|
|
22
|
+
const TRIGGER_TYPE_WORD_SET = new Set(TRIGGER_TYPE_WORDS);
|
|
23
|
+
export function triggerTypeLabel(type) {
|
|
24
|
+
if (!type) return "";
|
|
25
|
+
const kind = String(type);
|
|
26
|
+
if (!TRIGGER_TYPE_WORD_SET.has(kind)) return kind;
|
|
27
|
+
return useIntegrationsT()(`integration.common.triggerType.${kind}`);
|
|
28
|
+
}
|
|
15
29
|
export function findManifest(slug, version) {
|
|
16
30
|
const matches = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug);
|
|
17
31
|
if (!matches.length) return null;
|