@revenexx/studio-integrations 0.6.0 → 1.0.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 +643 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -3
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +32 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +45 -38
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +21 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +30 -16
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +10 -8
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +3 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +45 -42
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +3 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +315 -221
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +2 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +37 -10
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +2 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +40 -35
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -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 +82 -34
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/SurfaceCard.d.vue.ts +50 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue +55 -0
- package/dist/runtime/components/Integrations/SurfaceCard.vue.d.ts +50 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +18 -8
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +22 -12
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +41 -39
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +131 -87
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +27 -24
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +76 -46
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +55 -43
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +16 -17
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -13
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +79 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.d.ts +11 -0
- package/dist/runtime/composables/useIntegrationsCredentials.js +8 -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 +14 -2
- 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 +3 -1
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +3 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- 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 +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.d.ts +23 -3
- package/dist/runtime/composables/useIntegrationsTriggers.js +21 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +10 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.js +30 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +4 -2
- package/dist/runtime/data/integrationNodeCatalog.d.ts +63 -1
- package/dist/runtime/data/integrationNodeCatalog.js +22 -4
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/nodeInputData.d.ts +58 -1
- package/dist/runtime/data/nodeInputData.js +30 -5
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/triggerGlyphs.d.ts +47 -0
- package/dist/runtime/data/triggerGlyphs.js +11 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1224 -0
- package/dist/runtime/i18n/messages/en.json +1224 -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 +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +73 -52
- package/dist/runtime/pages/integrations/index.vue +66 -73
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +89 -72
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +351 -197
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -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 +25 -1
- package/dist/runtime/utils/cockpitSources.d.ts +22 -3
- package/dist/runtime/utils/credentialState.d.ts +30 -2
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +52 -5
- package/dist/runtime/utils/localize.js +47 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/servedPaths.d.ts +22 -0
- package/dist/runtime/utils/servedPaths.js +3 -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 +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
|
@@ -1,5 +1,6 @@
|
|
|
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";
|
|
4
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
6
|
const BLOB_VERSION = "v0-draft";
|
|
@@ -81,8 +82,10 @@ export function useIntegrationsWorkflows() {
|
|
|
81
82
|
const loading = useState("integrations.workflows.loading", () => false);
|
|
82
83
|
const error = useState("integrations.workflows.error", () => null);
|
|
83
84
|
const loaded = useState("integrations.workflows.loaded", () => false);
|
|
85
|
+
const apiTriggers = useState("integrations.workflows.apiTriggers", () => ({}));
|
|
84
86
|
const gate = useLoadGate("integrations.workflows", { loading, loaded });
|
|
85
87
|
const api = useIntegrationsApi();
|
|
88
|
+
const t = useIntegrationsT();
|
|
86
89
|
function unwrap(res) {
|
|
87
90
|
return res?.data ?? res;
|
|
88
91
|
}
|
|
@@ -90,6 +93,24 @@ export function useIntegrationsWorkflows() {
|
|
|
90
93
|
if (e instanceof IntegrationsApiError) return e.message;
|
|
91
94
|
return e?.message ?? fallback;
|
|
92
95
|
}
|
|
96
|
+
function remember(api2, into) {
|
|
97
|
+
if (api2.triggers !== void 0) into[Number(api2.id)] = api2.triggers;
|
|
98
|
+
}
|
|
99
|
+
function ingest(api2) {
|
|
100
|
+
const next = { ...apiTriggers.value };
|
|
101
|
+
remember(api2, next);
|
|
102
|
+
apiTriggers.value = next;
|
|
103
|
+
return toCockpit(api2);
|
|
104
|
+
}
|
|
105
|
+
function ingestList(list2) {
|
|
106
|
+
const next = { ...apiTriggers.value };
|
|
107
|
+
const out = list2.map((api2) => {
|
|
108
|
+
remember(api2, next);
|
|
109
|
+
return toCockpit(api2);
|
|
110
|
+
});
|
|
111
|
+
apiTriggers.value = next;
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
93
114
|
function upsertCache(wf) {
|
|
94
115
|
const idx = workflows.value.findIndex((w) => w.id === wf.id);
|
|
95
116
|
workflows.value = idx < 0 ? [...workflows.value, wf] : workflows.value.map((w) => w.id === wf.id ? wf : w);
|
|
@@ -99,10 +120,11 @@ export function useIntegrationsWorkflows() {
|
|
|
99
120
|
error.value = null;
|
|
100
121
|
try {
|
|
101
122
|
const list2 = unwrap(await api.get("/workflows")) ?? [];
|
|
102
|
-
workflows.value = list2
|
|
123
|
+
workflows.value = ingestList(list2);
|
|
103
124
|
loaded.value = true;
|
|
104
125
|
} catch (e) {
|
|
105
|
-
error.value = captureError(e, "
|
|
126
|
+
error.value = captureError(e, t("integration.workflows.error.loadFailed"));
|
|
127
|
+
loaded.value = false;
|
|
106
128
|
} finally {
|
|
107
129
|
gate.release();
|
|
108
130
|
}
|
|
@@ -114,7 +136,7 @@ export function useIntegrationsWorkflows() {
|
|
|
114
136
|
}
|
|
115
137
|
async function get(id) {
|
|
116
138
|
try {
|
|
117
|
-
const wf =
|
|
139
|
+
const wf = ingest(unwrap(await api.get(`/workflows/${id}`)));
|
|
118
140
|
upsertCache(wf);
|
|
119
141
|
return wf;
|
|
120
142
|
} catch {
|
|
@@ -122,7 +144,7 @@ export function useIntegrationsWorkflows() {
|
|
|
122
144
|
}
|
|
123
145
|
}
|
|
124
146
|
async function create(input) {
|
|
125
|
-
const wf =
|
|
147
|
+
const wf = ingest(unwrap(await api.post("/workflows", toPayload({
|
|
126
148
|
name: input.name,
|
|
127
149
|
description: input.description,
|
|
128
150
|
blob: input.blob,
|
|
@@ -134,7 +156,7 @@ export function useIntegrationsWorkflows() {
|
|
|
134
156
|
}
|
|
135
157
|
async function duplicate(id, name) {
|
|
136
158
|
const source = await get(id);
|
|
137
|
-
if (!source) throw new Error("
|
|
159
|
+
if (!source) throw new Error(t("integration.workflows.error.notFound"));
|
|
138
160
|
const blob = JSON.parse(JSON.stringify(source.blob));
|
|
139
161
|
const handleMap = /* @__PURE__ */ new Map();
|
|
140
162
|
for (const trigger of blob.triggers ?? []) {
|
|
@@ -170,10 +192,10 @@ export function useIntegrationsWorkflows() {
|
|
|
170
192
|
lastError = err;
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
|
-
throw lastError instanceof Error ? lastError : new Error("
|
|
195
|
+
throw lastError instanceof Error ? lastError : new Error(t("integration.workflows.error.duplicateFailed"));
|
|
174
196
|
}
|
|
175
197
|
async function update(id, patch) {
|
|
176
|
-
const wf =
|
|
198
|
+
const wf = ingest(unwrap(await api.put(`/workflows/${id}`, toPayload({
|
|
177
199
|
name: patch.name,
|
|
178
200
|
description: patch.description,
|
|
179
201
|
blob: patch.blob,
|
|
@@ -199,6 +221,7 @@ export function useIntegrationsWorkflows() {
|
|
|
199
221
|
loading,
|
|
200
222
|
error,
|
|
201
223
|
loaded,
|
|
224
|
+
apiTriggers,
|
|
202
225
|
total,
|
|
203
226
|
activeCount,
|
|
204
227
|
load,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { computed, onScopeDispose, ref, toValue, watch } from "vue";
|
|
2
|
+
import { isVersionExecutable } from "../data/integrationNodeCatalog.js";
|
|
2
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
4
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
5
|
export function isResolvableValue(v) {
|
|
@@ -15,7 +16,8 @@ export function isResolvableValue(v) {
|
|
|
15
16
|
}
|
|
16
17
|
function classifyError(err) {
|
|
17
18
|
const status = err instanceof IntegrationsApiError ? err.status : void 0;
|
|
18
|
-
const
|
|
19
|
+
const t = useIntegrationsT();
|
|
20
|
+
const message = err instanceof IntegrationsApiError ? err.message : err?.message ?? t("integration.nodeConfig.resolve.error.generic");
|
|
19
21
|
let kind;
|
|
20
22
|
switch (status) {
|
|
21
23
|
case 409:
|
|
@@ -53,7 +55,7 @@ export function useNodeConfigResolve(opts) {
|
|
|
53
55
|
});
|
|
54
56
|
const gated = computed(() => unsatisfied.value.length > 0);
|
|
55
57
|
const isEnabled = computed(
|
|
56
|
-
() => (toValue(opts.enabled) ?? true) && !gated.value && !!toValue(opts.slug) && !!toValue(opts.version)
|
|
58
|
+
() => (toValue(opts.enabled) ?? true) && isVersionExecutable(toValue(opts.slug), toValue(opts.version)) && !gated.value && !!toValue(opts.slug) && !!toValue(opts.version)
|
|
57
59
|
);
|
|
58
60
|
const subsetKey = computed(() => {
|
|
59
61
|
const cfg = toValue(opts.config);
|
|
@@ -13,11 +13,74 @@ 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;
|
|
62
|
+
/**
|
|
63
|
+
* Whether this exact node version can be executed — its code was built, so
|
|
64
|
+
* `execute:test` and `config:resolve` will not be refused (PO-338).
|
|
65
|
+
*
|
|
66
|
+
* Deliberately NOT `findManifest(...)?.__hasBundle !== false`: that helper falls
|
|
67
|
+
* back to the newest registered version when the pinned one is absent, so a node
|
|
68
|
+
* pinned to a withdrawn version would inherit a stranger's build state and every
|
|
69
|
+
* surface would then say something about a version the catalogue does not have.
|
|
70
|
+
* Only an exact match may answer, and anything else — unknown version, catalogue
|
|
71
|
+
* not loaded yet, a host whose API does not report the field — reads as
|
|
72
|
+
* executable, because the alternative is a studio that hides working nodes.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isVersionExecutable(slug: string, version: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The newest version of this node that can be executed, or `null` when none can.
|
|
77
|
+
*
|
|
78
|
+
* What a surface tells the operator to move to. The catalogue is semver-descending
|
|
79
|
+
* within a slug, so the first executable entry is the newest one. `null` where the
|
|
80
|
+
* whole node is unusable — then there is nothing to point at and the surface says
|
|
81
|
+
* only what is wrong.
|
|
82
|
+
*/
|
|
83
|
+
export declare function newestExecutableVersion(slug: string): string | null;
|
|
21
84
|
export declare function findManifest(slug: string, version?: string): NodeManifest | null;
|
|
22
85
|
export declare const TRIGGER_OUTPUT_PORT = "default";
|
|
23
86
|
export interface TriggerTemplate {
|
|
@@ -34,7 +97,6 @@ export interface TriggerTemplate {
|
|
|
34
97
|
en: string;
|
|
35
98
|
de: string;
|
|
36
99
|
};
|
|
37
|
-
icon: string;
|
|
38
100
|
/** Default config the canvas seeds when the user drops the tile. */
|
|
39
101
|
defaultConfig: Record<string, unknown>;
|
|
40
102
|
}
|
|
@@ -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,27 @@ 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
|
+
}
|
|
29
|
+
export function isVersionExecutable(slug, version) {
|
|
30
|
+
const exact = INTEGRATION_NODE_CATALOG.find((m) => m.slug === slug && m.version === version);
|
|
31
|
+
return exact ? exact.__hasBundle !== false : true;
|
|
32
|
+
}
|
|
33
|
+
export function newestExecutableVersion(slug) {
|
|
34
|
+
const hit = INTEGRATION_NODE_CATALOG.find((m) => m.slug === slug && m.__hasBundle !== false);
|
|
35
|
+
return hit?.version ?? null;
|
|
36
|
+
}
|
|
15
37
|
export function findManifest(slug, version) {
|
|
16
38
|
const matches = INTEGRATION_NODE_CATALOG.filter((m) => m.slug === slug);
|
|
17
39
|
if (!matches.length) return null;
|
|
@@ -28,7 +50,6 @@ export const TRIGGER_TEMPLATES = [
|
|
|
28
50
|
en: "Start the workflow yourself with the Run button or an API call.",
|
|
29
51
|
de: "Starte den Workflow selbst \xFCber den Run-Button oder einen API-Aufruf."
|
|
30
52
|
},
|
|
31
|
-
icon: "Play",
|
|
32
53
|
defaultConfig: {}
|
|
33
54
|
},
|
|
34
55
|
{
|
|
@@ -39,7 +60,6 @@ export const TRIGGER_TEMPLATES = [
|
|
|
39
60
|
en: "Runs automatically on a schedule you define, for example every night.",
|
|
40
61
|
de: "L\xE4uft automatisch nach deinem Zeitplan, zum Beispiel jede Nacht."
|
|
41
62
|
},
|
|
42
|
-
icon: "Clock",
|
|
43
63
|
defaultConfig: { cron: "0 * * * *", timezone: "Europe/Berlin" }
|
|
44
64
|
},
|
|
45
65
|
{
|
|
@@ -50,7 +70,6 @@ export const TRIGGER_TEMPLATES = [
|
|
|
50
70
|
en: "Starts when an external system calls this workflow's URL.",
|
|
51
71
|
de: "Startet, wenn ein externes System die URL dieses Workflows aufruft."
|
|
52
72
|
},
|
|
53
|
-
icon: "Webhook",
|
|
54
73
|
defaultConfig: { path: "/incoming", method: "POST" }
|
|
55
74
|
},
|
|
56
75
|
{
|
|
@@ -61,7 +80,6 @@ export const TRIGGER_TEMPLATES = [
|
|
|
61
80
|
en: "Starts when something happens on the platform, for example a new customer.",
|
|
62
81
|
de: "Startet, wenn auf der Plattform etwas passiert, zum Beispiel ein neuer Kunde."
|
|
63
82
|
},
|
|
64
|
-
icon: "Zap",
|
|
65
83
|
defaultConfig: { event_type: "" }
|
|
66
84
|
}
|
|
67
85
|
];
|
|
@@ -35,6 +35,15 @@ export declare const TEMPLATE_CATEGORIES: Array<{
|
|
|
35
35
|
* category the studio has never been taught still gets a chip, named as well as it
|
|
36
36
|
* can be rather than dropped. That guarantee held on the gallery alone before; it
|
|
37
37
|
* now holds on all four.
|
|
38
|
+
*
|
|
39
|
+
* `specs/templates.md` AC-8 splits this in two: the first half — a category the
|
|
40
|
+
* studio *does* have a name for — is the studio's own word, and can follow the
|
|
41
|
+
* reader's language; the second half — the registry's own key, capitalised — never
|
|
42
|
+
* can, because a bare key carries no text to read in any language. Only the first
|
|
43
|
+
* half goes through `useIntegrationsT()`. A message missing from the catalogue
|
|
44
|
+
* (this key not merged into `messages/*.json` yet) falls back to the same English
|
|
45
|
+
* word `TEMPLATE_CATEGORIES` always carried, so the label degrades to "correct but
|
|
46
|
+
* unlocalised" rather than to the raw key.
|
|
38
47
|
*/
|
|
39
48
|
export declare function templateCategoryLabel(key: string): string;
|
|
40
49
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useIntegrationsT } from "../composables/useIntegrationsT.js";
|
|
1
2
|
export const TEMPLATE_CATEGORIES = [
|
|
2
3
|
// Uniform muted pill — the label carries the meaning, no rainbow needed.
|
|
3
4
|
{ key: "commerce", label: "Commerce", color: "bg-slate-100 text-slate-700" },
|
|
@@ -12,7 +13,12 @@ export const TEMPLATE_CATEGORIES = [
|
|
|
12
13
|
export function templateCategoryLabel(key) {
|
|
13
14
|
if (!key) return "";
|
|
14
15
|
const known = TEMPLATE_CATEGORIES.find((c) => c.key === key);
|
|
15
|
-
if (known)
|
|
16
|
+
if (known) {
|
|
17
|
+
const t = useIntegrationsT();
|
|
18
|
+
const messageKey = `integration.templates.category.${known.key}`;
|
|
19
|
+
const translated = t(messageKey);
|
|
20
|
+
return translated === messageKey ? known.label : translated;
|
|
21
|
+
}
|
|
16
22
|
return key.charAt(0).toUpperCase() + key.slice(1);
|
|
17
23
|
}
|
|
18
24
|
export function templatePackageLabel(tpl) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NodeManifest, NodeManifestOutput, OutputKind, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
1
|
+
import type { LocalizedString, NodeManifest, NodeManifestOutput, OutputKind, WorkflowEdge, WorkflowNode } from '../types/integrations.js';
|
|
2
2
|
/** `${{ nodes.<id>.outputs.<port> }}`, optionally narrowed to one field. */
|
|
3
3
|
export declare function nodeOutputExpr(nodeId: string, port: string, field?: string): string;
|
|
4
4
|
/** `${{ trigger.payload }}`, optionally narrowed to a path inside it. */
|
|
@@ -20,13 +20,70 @@ export interface FieldEntry {
|
|
|
20
20
|
name: string;
|
|
21
21
|
/** Declared data type (`string`, `number`, `boolean`, …) when the manifest carries one. */
|
|
22
22
|
dataType: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* What the publisher says this field holds, unresolved. Kept in the shape the
|
|
25
|
+
* manifest sent it so the language is chosen where it is drawn — see the note
|
|
26
|
+
* on {@link PortEntry.description}.
|
|
27
|
+
*/
|
|
28
|
+
description?: LocalizedString;
|
|
23
29
|
}
|
|
24
30
|
export interface PortEntry {
|
|
25
31
|
name: string;
|
|
26
32
|
kind: OutputKind;
|
|
27
33
|
/** Declared payload fields of this port (schema; may be empty). */
|
|
28
34
|
fields: FieldEntry[];
|
|
35
|
+
/**
|
|
36
|
+
* What the publisher says flows through this port, **unresolved** — the
|
|
37
|
+
* localised map as the manifest sent it, not a string.
|
|
38
|
+
*
|
|
39
|
+
* Resolving here would pick the language at the moment the ports are derived,
|
|
40
|
+
* and these helpers are pure by design: their tests would then depend on the
|
|
41
|
+
* studio's locale, and a language switched afterwards would not reach a port
|
|
42
|
+
* list already computed. So the raw value travels and whoever draws it calls
|
|
43
|
+
* the resolver, which is how every other manifest string in the studio is
|
|
44
|
+
* read. Absent rather than null when the publisher wrote none: most do not,
|
|
45
|
+
* and an optional key keeps the derivation's shape honest about that.
|
|
46
|
+
*/
|
|
47
|
+
description?: LocalizedString;
|
|
29
48
|
}
|
|
49
|
+
export interface InputPortEntry {
|
|
50
|
+
name: string;
|
|
51
|
+
/** Declared data type of what the node accepts here. */
|
|
52
|
+
dataType: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Whether an upstream connection is mandatory.
|
|
55
|
+
*
|
|
56
|
+
* Most manifests leave the key out, so the default decides what nearly every
|
|
57
|
+
* node claims. It is **the service's**, not ours: the node-manifest schema
|
|
58
|
+
* documents `required` as *"Whether an upstream connection is mandatory.
|
|
59
|
+
* Defaults to true."*, so an absent key is read as required and the chip says
|
|
60
|
+
* so. Reading it the other way would have the studio call a mandatory input
|
|
61
|
+
* optional on the strength of a key nobody wrote.
|
|
62
|
+
*
|
|
63
|
+
* It is a statement about what the type **declares**, not a rule anything
|
|
64
|
+
* enforces — nothing in the studio blocks a save over it, and the service's own
|
|
65
|
+
* blob validation checks that a named port exists rather than that it is fed.
|
|
66
|
+
*/
|
|
67
|
+
required: boolean;
|
|
68
|
+
/** What the publisher says the node expects, unresolved — see {@link PortEntry.description}. */
|
|
69
|
+
description?: LocalizedString;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* What a node accepts, read from the manifest's `inputs` map.
|
|
73
|
+
*
|
|
74
|
+
* **Two wire shapes, one of them a trap.** A node with named ports arrives as an
|
|
75
|
+
* object; a node that accepts nothing arrives as `[]`, because the service
|
|
76
|
+
* encodes an empty PHP array as a JSON array rather than an object. So the empty
|
|
77
|
+
* case has a different JSON *type* from the populated one, and reading it with
|
|
78
|
+
* `Object.entries` alone would work by luck rather than by intent. Both land here
|
|
79
|
+
* as an empty list.
|
|
80
|
+
*
|
|
81
|
+
* The studio read this under a singular `input` key until PO-216 — a shape the
|
|
82
|
+
* service has never sent and its schema forbids, so every input a node declared
|
|
83
|
+
* was invisible: the sentence, the data type, and whether a connection was
|
|
84
|
+
* mandatory at all.
|
|
85
|
+
*/
|
|
86
|
+
export declare function inputPortsForManifest(manifest: NodeManifest | null | undefined): InputPortEntry[];
|
|
30
87
|
/**
|
|
31
88
|
* A manifest declaring zero output ports describes a TERMINAL node (PO-201):
|
|
32
89
|
* the run ends there, so it gets no output handle and no edge may leave it.
|
|
@@ -37,13 +37,38 @@ export function collectUpstreamNodes(selectedId, nodes, edges) {
|
|
|
37
37
|
function fieldEntries(raw) {
|
|
38
38
|
if (Array.isArray(raw)) return raw.map((v) => ({ name: String(v), dataType: null }));
|
|
39
39
|
if (raw && typeof raw === "object") {
|
|
40
|
-
return Object.entries(raw).map(([name, spec]) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
return Object.entries(raw).map(([name, spec]) => {
|
|
41
|
+
const s = spec && typeof spec === "object" ? spec : null;
|
|
42
|
+
return {
|
|
43
|
+
name,
|
|
44
|
+
dataType: typeof s?.dataType === "string" ? s.dataType : null,
|
|
45
|
+
...describedBy(s?.description)
|
|
46
|
+
};
|
|
47
|
+
});
|
|
44
48
|
}
|
|
45
49
|
return [];
|
|
46
50
|
}
|
|
51
|
+
function describedBy(raw) {
|
|
52
|
+
if (typeof raw === "string") return raw.trim() ? { description: raw.trim() } : {};
|
|
53
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
54
|
+
const entries = Object.entries(raw).filter((e) => typeof e[1] === "string" && e[1].trim() !== "").map(([k, v]) => [k, v.trim()]);
|
|
55
|
+
return entries.length ? { description: Object.fromEntries(entries) } : {};
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
export function inputPortsForManifest(manifest) {
|
|
60
|
+
const raw = manifest?.inputs;
|
|
61
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
|
|
62
|
+
return Object.entries(raw).map(([name, spec]) => {
|
|
63
|
+
const s = spec && typeof spec === "object" ? spec : null;
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
dataType: typeof s?.dataType === "string" ? s.dataType : null,
|
|
67
|
+
required: s?.required !== false,
|
|
68
|
+
...describedBy(s?.description)
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
47
72
|
export function isTerminalManifest(manifest) {
|
|
48
73
|
return !!manifest && Array.isArray(manifest.outputs) && !manifest.outputs.length;
|
|
49
74
|
}
|
|
@@ -53,7 +78,7 @@ export function portsForNode(node, manifest, resolvedOutputs) {
|
|
|
53
78
|
const ports = [];
|
|
54
79
|
for (const o of list) {
|
|
55
80
|
if (o.name) {
|
|
56
|
-
ports.push({ name: o.name, kind: o.kind, fields: fieldEntries(o.fields) });
|
|
81
|
+
ports.push({ name: o.name, kind: o.kind, fields: fieldEntries(o.fields), ...describedBy(o.description) });
|
|
57
82
|
} else if (o.sourceFromConfig) {
|
|
58
83
|
const raw = node.config?.[o.sourceFromConfig];
|
|
59
84
|
const values = Array.isArray(raw) ? raw : [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useIntegrationsT } from "../composables/useIntegrationsT.js";
|
|
1
2
|
const PAYLOAD_REF_RE = /\$\{\{\s*trigger\.payload\.([a-zA-Z0-9_.-]+)/g;
|
|
2
3
|
const FILE_KEYS = /* @__PURE__ */ new Set(["data", "filename", "contentType", "size"]);
|
|
3
4
|
function collectRefs(value, out) {
|
|
@@ -49,7 +50,7 @@ export function payloadFieldsFromBlob(blob) {
|
|
|
49
50
|
}
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
52
|
-
fields.push({ kind: "file", path: prefix, label: prefix === "" ? "
|
|
53
|
+
fields.push({ kind: "file", path: prefix, label: prefix === "" ? useIntegrationsT()("integration.common.label.file") : labelFor(prefix) });
|
|
53
54
|
for (const leaf of leaves) {
|
|
54
55
|
if (FILE_KEYS.has(leaf)) consumed.add(prefix === "" ? leaf : `${prefix}.${leaf}`);
|
|
55
56
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
|
+
import type { TriggerType } from '../types/integrations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Trigger type → glyph, for every surface that draws one.
|
|
5
|
+
*
|
|
6
|
+
* Eight files drew a trigger's glyph, and they did it two ways. Five spelled the
|
|
7
|
+
* mapping out — the canvas card and the run replay panel as ternaries, the node
|
|
8
|
+
* inspector's header, the template card and the template detail page as records —
|
|
9
|
+
* and three did not carry a mapping at all: they resolved a glyph from Lucide by
|
|
10
|
+
* *name*, so no import mentioned one, in the node palette's trigger tiles (through
|
|
11
|
+
* `nodeIcon`), the create dialog's trigger select and the template flow preview
|
|
12
|
+
* (each through a local `iconFor`).
|
|
13
|
+
*
|
|
14
|
+
* They did not agree: a webhook was a globe on the templates page, a link on the
|
|
15
|
+
* canvas and Lucide's pretzel in the palette, so the same trigger changed shape
|
|
16
|
+
* three times on the way from picking it to seeing it. The replay panel's ternary
|
|
17
|
+
* had no `event` branch at all, so an event-triggered run drew the manual play
|
|
18
|
+
* button — wrong information, not a mismatch (PO-329).
|
|
19
|
+
*
|
|
20
|
+
* One map instead, and the reason it can be Solar's: `TRIGGER_TEMPLATES` is
|
|
21
|
+
* this repository's own catalogue, not a node manifest, so its `icon` strings
|
|
22
|
+
* ('Play', 'Clock', 'Webhook', 'Zap') were Lucide names by habit rather than by
|
|
23
|
+
* contract. The node-type icon map (`nodeIcons.ts`) is a different thing and
|
|
24
|
+
* stays Lucide deliberately — see `docs/icons.md`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const TRIGGER_GLYPHS: Record<TriggerType, Component>;
|
|
27
|
+
/**
|
|
28
|
+
* The glyph for a trigger type.
|
|
29
|
+
*
|
|
30
|
+
* The parameter is deliberately wider than `TriggerType`: some callers read the
|
|
31
|
+
* type out of a template blob or a run record, where the service may name a kind
|
|
32
|
+
* the studio has no word for yet — `templates/[slug].vue` is built for exactly
|
|
33
|
+
* that case and says so.
|
|
34
|
+
*
|
|
35
|
+
* Such a kind falls back to the **bolt**, not to the manual play button, which is
|
|
36
|
+
* what the templates surfaces did before this map existed. A play button asserts
|
|
37
|
+
* "you start this yourself", and for an unknown kind that is a claim rather than a
|
|
38
|
+
* gap; the bolt says only that something fires it.
|
|
39
|
+
*
|
|
40
|
+
* `Object.hasOwn`, not `?? Bolt` on its own: a plain object inherits
|
|
41
|
+
* `Object.prototype`, so a service that named a trigger kind `constructor` or
|
|
42
|
+
* `toString` would resolve to a function, the `??` would never fire, and the card
|
|
43
|
+
* would try to render `<component :is="Object">`. The `Lucide[name]` lookups this
|
|
44
|
+
* map replaced could not do that — a module namespace object has a null prototype
|
|
45
|
+
* — so the guard is what keeps the docstring above true rather than nearly true.
|
|
46
|
+
*/
|
|
47
|
+
export declare function triggerGlyph(type?: string | null): Component;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Bolt, ClockCircle, LinkRound, Play } from "#solar-icons";
|
|
2
|
+
export const TRIGGER_GLYPHS = {
|
|
3
|
+
manual: Play,
|
|
4
|
+
schedule: ClockCircle,
|
|
5
|
+
webhook: LinkRound,
|
|
6
|
+
event: Bolt
|
|
7
|
+
};
|
|
8
|
+
export function triggerGlyph(type) {
|
|
9
|
+
const key = String(type);
|
|
10
|
+
return Object.hasOwn(TRIGGER_GLYPHS, key) ? TRIGGER_GLYPHS[key] : Bolt;
|
|
11
|
+
}
|
|
@@ -99,6 +99,7 @@ export function freshImageSrc(stored) {
|
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
101
|
export function nodeLogoOptions() {
|
|
102
|
+
const t = useIntegrationsT();
|
|
102
103
|
const byId = /* @__PURE__ */ new Map();
|
|
103
104
|
for (const m of INTEGRATION_NODE_CATALOG) {
|
|
104
105
|
const imgs = m.images ?? [];
|
|
@@ -107,7 +108,7 @@ export function nodeLogoOptions() {
|
|
|
107
108
|
const id = imageIdFromSrc(logo.src) ?? logo.src;
|
|
108
109
|
if (byId.has(id)) continue;
|
|
109
110
|
const label = m.__package?.label || (m.groups?.length ? resolveLocalized(m.groups[0]) : "") || resolveLocalized(m.name);
|
|
110
|
-
byId.set(id, { label: label || "
|
|
111
|
+
byId.set(id, { label: label || t("integration.workflows.iconCatalog.label.fallback"), src: logo.src });
|
|
111
112
|
}
|
|
112
113
|
return [...byId.entries()].map(([id, v]) => ({ id, src: v.src, label: v.label })).sort((a, b) => a.label.localeCompare(b.label));
|
|
113
114
|
}
|