@revenexx/studio-integrations 0.6.0 → 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 +227 -0
- package/README.md +31 -0
- 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 +29 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
- 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 +16 -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.vue +15 -14
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
- package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
- package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
- 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 +37 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
- 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.js +6 -4
- 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 +3 -1
- 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/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.js +4 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- 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 +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +70 -52
- package/dist/runtime/pages/integrations/index.vue +50 -56
- 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 +77 -68
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
- 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/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +18 -5
- package/dist/runtime/utils/localize.js +32 -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/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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { computed, effectScope, ref, shallowRef } from "vue";
|
|
2
|
+
export const RUN_TOPIC = "revenexx.integrations.run";
|
|
3
|
+
export const WORKFLOW_TOPIC = "revenexx.integrations.workflow";
|
|
4
|
+
export const BUILD_TOPIC = "revenexx.integrations.build";
|
|
5
|
+
export const RUN_ACTIONS = ["created", "started", "finished", "failed", "cancelled"];
|
|
6
|
+
export const WORKFLOW_ACTIONS = ["created", "updated", "deleted"];
|
|
7
|
+
export const BUILD_ACTIONS = ["started", "succeeded", "failed"];
|
|
8
|
+
export function toRealtimeEvent(envelope) {
|
|
9
|
+
const topic = typeof envelope.topic === "string" ? envelope.topic : "";
|
|
10
|
+
const topicId = envelope.topic_id;
|
|
11
|
+
const data = envelope.data;
|
|
12
|
+
return {
|
|
13
|
+
id: typeof envelope.id === "string" ? envelope.id : "",
|
|
14
|
+
action: topic.split(".")[3] ?? "",
|
|
15
|
+
topicId: topicId === null || topicId === void 0 ? null : String(topicId),
|
|
16
|
+
data: data !== null && typeof data === "object" ? data : {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function workflowIdOf(event) {
|
|
20
|
+
const id = event?.data.workflow_id;
|
|
21
|
+
return typeof id === "string" && id ? id : null;
|
|
22
|
+
}
|
|
23
|
+
const runTick = ref(0);
|
|
24
|
+
const workflowTick = ref(0);
|
|
25
|
+
const buildTick = ref(0);
|
|
26
|
+
const lastRunEvent = shallowRef(null);
|
|
27
|
+
const lastWorkflowEvent = shallowRef(null);
|
|
28
|
+
const lastBuildEvent = shallowRef(null);
|
|
29
|
+
const deliveries = computed(() => runTick.value + workflowTick.value + buildTick.value);
|
|
30
|
+
let scope = null;
|
|
31
|
+
let openChannels = null;
|
|
32
|
+
function onRun(envelope) {
|
|
33
|
+
lastRunEvent.value = toRealtimeEvent(envelope);
|
|
34
|
+
runTick.value++;
|
|
35
|
+
}
|
|
36
|
+
function onWorkflow(envelope) {
|
|
37
|
+
lastWorkflowEvent.value = toRealtimeEvent(envelope);
|
|
38
|
+
workflowTick.value++;
|
|
39
|
+
}
|
|
40
|
+
function onBuild(envelope) {
|
|
41
|
+
lastBuildEvent.value = toRealtimeEvent(envelope);
|
|
42
|
+
buildTick.value++;
|
|
43
|
+
}
|
|
44
|
+
function onRunGap() {
|
|
45
|
+
lastRunEvent.value = null;
|
|
46
|
+
runTick.value++;
|
|
47
|
+
}
|
|
48
|
+
function onWorkflowGap() {
|
|
49
|
+
lastWorkflowEvent.value = null;
|
|
50
|
+
workflowTick.value++;
|
|
51
|
+
}
|
|
52
|
+
function onBuildGap() {
|
|
53
|
+
lastBuildEvent.value = null;
|
|
54
|
+
buildTick.value++;
|
|
55
|
+
}
|
|
56
|
+
export function useIntegrationsRealtime() {
|
|
57
|
+
const { talkback, enabled } = usePlatformTalkback();
|
|
58
|
+
if (enabled && import.meta.client && !scope) {
|
|
59
|
+
scope = effectScope(true);
|
|
60
|
+
scope.run(() => {
|
|
61
|
+
const subs = [
|
|
62
|
+
...RUN_ACTIONS.map((action) => useTalkbackTopic(`${RUN_TOPIC}.${action}`, { handler: onRun, onResync: onRunGap })),
|
|
63
|
+
...WORKFLOW_ACTIONS.map((action) => useTalkbackTopic(`${WORKFLOW_TOPIC}.${action}`, { handler: onWorkflow, onResync: onWorkflowGap })),
|
|
64
|
+
...BUILD_ACTIONS.map((action) => useTalkbackTopic(`${BUILD_TOPIC}.${action}`, { handler: onBuild, onResync: onBuildGap }))
|
|
65
|
+
];
|
|
66
|
+
openChannels = computed(() => subs.map((s) => s.channel.value).filter((c) => !!c));
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const channels = computed(() => openChannels?.value ?? []);
|
|
70
|
+
return {
|
|
71
|
+
delivering: computed(() => deliveries.value > 0 && enabled && !!talkback.value && channels.value.length > 0),
|
|
72
|
+
runTick,
|
|
73
|
+
workflowTick,
|
|
74
|
+
buildTick,
|
|
75
|
+
lastRunEvent,
|
|
76
|
+
lastWorkflowEvent,
|
|
77
|
+
lastBuildEvent,
|
|
78
|
+
channels
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ref } 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
|
export function useIntegrationsRevisions() {
|
|
5
6
|
const api = useIntegrationsApi();
|
|
7
|
+
const t = useIntegrationsT();
|
|
6
8
|
const loading = ref(false);
|
|
7
9
|
const error = ref(null);
|
|
8
10
|
function captureError(e, fallback) {
|
|
@@ -16,7 +18,7 @@ export function useIntegrationsRevisions() {
|
|
|
16
18
|
const res = await api.get(`/workflows/${workflowId}/revisions?page=${page}&per_page=${perPage}`);
|
|
17
19
|
return { revisions: res.data ?? [], meta: res.meta };
|
|
18
20
|
} catch (e) {
|
|
19
|
-
error.value = captureError(e, "
|
|
21
|
+
error.value = captureError(e, t("integration.revisions.error.loadFailed"));
|
|
20
22
|
throw e;
|
|
21
23
|
} finally {
|
|
22
24
|
loading.value = false;
|
|
@@ -25,12 +27,12 @@ export function useIntegrationsRevisions() {
|
|
|
25
27
|
async function get(workflowId, revision) {
|
|
26
28
|
const res = await api.get(`/workflows/${workflowId}/revisions/${revision}`);
|
|
27
29
|
const apiBlob = res.blob ?? {};
|
|
28
|
-
const triggers = (res.triggers ?? []).map((
|
|
29
|
-
id:
|
|
30
|
-
type:
|
|
31
|
-
name: typeof
|
|
32
|
-
active:
|
|
33
|
-
config:
|
|
30
|
+
const triggers = (res.triggers ?? []).map((t2) => ({
|
|
31
|
+
id: t2.handle ?? String(t2.id ?? ""),
|
|
32
|
+
type: t2.type,
|
|
33
|
+
name: typeof t2.name === "string" ? t2.name : t2.name ?? void 0,
|
|
34
|
+
active: t2.active,
|
|
35
|
+
config: t2.config ?? {}
|
|
34
36
|
}));
|
|
35
37
|
return {
|
|
36
38
|
...res,
|
|
@@ -24,7 +24,7 @@ export declare function useIntegrationsRuns(): {
|
|
|
24
24
|
}) => WorkflowRun[];
|
|
25
25
|
get: (id: string) => WorkflowRun | null;
|
|
26
26
|
fetchRun: (workflowId: number | string, runId: number | string, workflowName: string) => Promise<WorkflowRun>;
|
|
27
|
-
trigger: (workflowId: number | string, payload?: Record<string, unknown>, mode?: "
|
|
27
|
+
trigger: (workflowId: number | string, payload?: Record<string, unknown>, mode?: "async" | "sync") => Promise<unknown>;
|
|
28
28
|
cancelRun: (workflowId: number | string, runId: number | string, reason?: string) => Promise<unknown>;
|
|
29
29
|
terminateRun: (workflowId: number | string, runId: number | string, reason?: string) => Promise<unknown>;
|
|
30
30
|
retryRun: (workflowId: number | string, runId: number | string) => Promise<unknown>;
|
|
@@ -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
|
function mapStatus(status) {
|
|
@@ -33,6 +34,7 @@ export function useIntegrationsRuns() {
|
|
|
33
34
|
const gate = useLoadGate("integrations.runs", { loading, loaded });
|
|
34
35
|
const SYNC_LIMIT = 5;
|
|
35
36
|
const api = useIntegrationsApi();
|
|
37
|
+
const t = useIntegrationsT();
|
|
36
38
|
const { workflows, load: loadWorkflows, loaded: workflowsLoaded } = useIntegrationsWorkflows();
|
|
37
39
|
function mapRun(r, workflowName) {
|
|
38
40
|
const startedMs = r.started_at ? new Date(r.started_at).getTime() : null;
|
|
@@ -99,7 +101,7 @@ export function useIntegrationsRuns() {
|
|
|
99
101
|
}
|
|
100
102
|
loaded.value = workflowsLoaded.value && perWorkflow.every((p) => p.status === "fulfilled");
|
|
101
103
|
} catch (e) {
|
|
102
|
-
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? "
|
|
104
|
+
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? t("integration.runs.error.loadFailed");
|
|
103
105
|
} finally {
|
|
104
106
|
gate.release();
|
|
105
107
|
}
|
|
@@ -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 KEY_PATTERN = /^[A-Z0-9_]+$/;
|
|
@@ -10,6 +11,7 @@ export function useIntegrationsSecrets() {
|
|
|
10
11
|
const loaded = useState("integrations.secrets.loaded", () => false);
|
|
11
12
|
const gate = useLoadGate("integrations.secrets", { loading, loaded });
|
|
12
13
|
const api = useIntegrationsApi();
|
|
14
|
+
const t = useIntegrationsT();
|
|
13
15
|
function isValidKey(key) {
|
|
14
16
|
return KEY_PATTERN.test(key);
|
|
15
17
|
}
|
|
@@ -35,7 +37,7 @@ export function useIntegrationsSecrets() {
|
|
|
35
37
|
secrets.value = normalize(await api.get("/secrets"));
|
|
36
38
|
loaded.value = true;
|
|
37
39
|
} catch (e) {
|
|
38
|
-
error.value = captureError(e, "
|
|
40
|
+
error.value = captureError(e, t("integration.secrets.error.loadFailed"));
|
|
39
41
|
} finally {
|
|
40
42
|
gate.release();
|
|
41
43
|
}
|
|
@@ -47,7 +49,7 @@ export function useIntegrationsSecrets() {
|
|
|
47
49
|
}
|
|
48
50
|
async function upsert(key, value) {
|
|
49
51
|
if (!isValidKey(key)) {
|
|
50
|
-
throw new Error("
|
|
52
|
+
throw new Error(t("integration.secrets.error.invalidKey"));
|
|
51
53
|
}
|
|
52
54
|
const created = !secrets.value.some((s) => s.key === key);
|
|
53
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
|
-
startingPoints: import("vue").ComputedRef<
|
|
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;
|
|
@@ -84,7 +84,14 @@ function applyDetail(base, api) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
export function useIntegrationsTemplates() {
|
|
87
|
-
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
|
+
}));
|
|
88
95
|
const loaded = useState("integrations.templates.loaded", () => false);
|
|
89
96
|
const loading = useState("integrations.templates.loading", () => false);
|
|
90
97
|
const error = useState("integrations.templates.error", () => null);
|
|
@@ -101,11 +108,13 @@ export function useIntegrationsTemplates() {
|
|
|
101
108
|
error.value = null;
|
|
102
109
|
try {
|
|
103
110
|
const list2 = unwrap(await api.get("/templates")) ?? [];
|
|
104
|
-
|
|
111
|
+
rawList.value = list2;
|
|
112
|
+
rawDetails.value = {};
|
|
105
113
|
loaded.value = true;
|
|
106
114
|
} catch (e) {
|
|
107
|
-
error.value = captureError(e, "
|
|
108
|
-
|
|
115
|
+
error.value = captureError(e, t("integration.templates.state.error.fallback"));
|
|
116
|
+
rawList.value = [];
|
|
117
|
+
rawDetails.value = {};
|
|
109
118
|
loaded.value = true;
|
|
110
119
|
} finally {
|
|
111
120
|
gate.release();
|
|
@@ -116,16 +125,15 @@ export function useIntegrationsTemplates() {
|
|
|
116
125
|
return templates.value;
|
|
117
126
|
}
|
|
118
127
|
function get(slug) {
|
|
119
|
-
return templates.value.find((
|
|
128
|
+
return templates.value.find((t2) => t2.slug === slug) ?? null;
|
|
120
129
|
}
|
|
121
130
|
async function fetchDetail(slug) {
|
|
122
131
|
const base = get(slug);
|
|
123
132
|
if (!base?.fromApi) return base;
|
|
124
133
|
try {
|
|
125
134
|
const detail = unwrap(await api.get(`/templates/${slug}`));
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return merged;
|
|
135
|
+
rawDetails.value = { ...rawDetails.value, [slug]: detail };
|
|
136
|
+
return get(slug);
|
|
129
137
|
} catch {
|
|
130
138
|
return base;
|
|
131
139
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
2
3
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
3
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
4
5
|
export function useIntegrationsTriggers() {
|
|
@@ -7,6 +8,7 @@ export function useIntegrationsTriggers() {
|
|
|
7
8
|
const error = useState("integrations.triggers.error", () => null);
|
|
8
9
|
const loaded = useState("integrations.triggers.loaded", () => false);
|
|
9
10
|
const api = useIntegrationsApi();
|
|
11
|
+
const t = useIntegrationsT();
|
|
10
12
|
const { workflows, load: loadWorkflows, loaded: workflowsLoaded } = useIntegrationsWorkflows();
|
|
11
13
|
const gate = useLoadGate("integrations.triggers", { loading, loaded });
|
|
12
14
|
function unwrapList(res) {
|
|
@@ -22,12 +24,12 @@ export function useIntegrationsTriggers() {
|
|
|
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
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
|
}
|
|
@@ -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";
|
|
@@ -83,6 +84,7 @@ export function useIntegrationsWorkflows() {
|
|
|
83
84
|
const loaded = useState("integrations.workflows.loaded", () => false);
|
|
84
85
|
const gate = useLoadGate("integrations.workflows", { loading, loaded });
|
|
85
86
|
const api = useIntegrationsApi();
|
|
87
|
+
const t = useIntegrationsT();
|
|
86
88
|
function unwrap(res) {
|
|
87
89
|
return res?.data ?? res;
|
|
88
90
|
}
|
|
@@ -102,7 +104,7 @@ export function useIntegrationsWorkflows() {
|
|
|
102
104
|
workflows.value = list2.map(toCockpit);
|
|
103
105
|
loaded.value = true;
|
|
104
106
|
} catch (e) {
|
|
105
|
-
error.value = captureError(e, "
|
|
107
|
+
error.value = captureError(e, t("integration.workflows.error.loadFailed"));
|
|
106
108
|
} finally {
|
|
107
109
|
gate.release();
|
|
108
110
|
}
|
|
@@ -134,7 +136,7 @@ export function useIntegrationsWorkflows() {
|
|
|
134
136
|
}
|
|
135
137
|
async function duplicate(id, name) {
|
|
136
138
|
const source = await get(id);
|
|
137
|
-
if (!source) throw new Error("
|
|
139
|
+
if (!source) throw new Error(t("integration.workflows.error.notFound"));
|
|
138
140
|
const blob = JSON.parse(JSON.stringify(source.blob));
|
|
139
141
|
const handleMap = /* @__PURE__ */ new Map();
|
|
140
142
|
for (const trigger of blob.triggers ?? []) {
|
|
@@ -170,7 +172,7 @@ export function useIntegrationsWorkflows() {
|
|
|
170
172
|
lastError = err;
|
|
171
173
|
}
|
|
172
174
|
}
|
|
173
|
-
throw lastError instanceof Error ? lastError : new Error("
|
|
175
|
+
throw lastError instanceof Error ? lastError : new Error(t("integration.workflows.error.duplicateFailed"));
|
|
174
176
|
}
|
|
175
177
|
async function update(id, patch) {
|
|
176
178
|
const wf = toCockpit(unwrap(await api.put(`/workflows/${id}`, toPayload({
|
|
@@ -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:
|
|
@@ -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;
|
|
@@ -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,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
|
}
|
|
@@ -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
|
}
|