@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +31 -0
  3. package/dist/module.d.mts +17 -1
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +10 -2
  6. package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +29 -28
  7. package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
  8. package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
  9. package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
  10. package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
  11. package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
  12. package/dist/runtime/components/Integrations/ExpressionInput.vue +16 -15
  13. package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
  14. package/dist/runtime/components/Integrations/Layout.vue +28 -3
  15. package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +15 -14
  16. package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
  17. package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
  18. package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
  19. package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
  20. package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
  21. package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
  22. package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
  23. package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
  24. package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
  25. package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
  26. package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
  27. package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
  28. package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
  29. package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
  30. package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
  31. package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
  32. package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
  33. package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
  34. package/dist/runtime/components/Integrations/StickyNoteView.vue +37 -23
  35. package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
  36. package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
  37. package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
  38. package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
  39. package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
  40. package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
  41. package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
  42. package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
  43. package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
  44. package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
  45. package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
  46. package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
  47. package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
  48. package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
  49. package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
  50. package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
  51. package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
  52. package/dist/runtime/composables/integrationsApi.js +4 -2
  53. package/dist/runtime/composables/useCredentialTypes.js +4 -2
  54. package/dist/runtime/composables/useIntegrationsCredentials.js +6 -4
  55. package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
  56. package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
  57. package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
  58. package/dist/runtime/composables/useIntegrationsNodes.js +3 -1
  59. package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
  60. package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
  61. package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
  62. package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
  63. package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
  64. package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
  65. package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
  66. package/dist/runtime/composables/useIntegrationsT.js +9 -0
  67. package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
  68. package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
  69. package/dist/runtime/composables/useIntegrationsTriggers.js +4 -2
  70. package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
  71. package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
  72. package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
  73. package/dist/runtime/data/integrationNodeCatalog.js +14 -0
  74. package/dist/runtime/data/integrationTemplates.d.ts +9 -0
  75. package/dist/runtime/data/integrationTemplates.js +7 -1
  76. package/dist/runtime/data/payloadForm.js +2 -1
  77. package/dist/runtime/data/workflowIconCatalog.js +2 -1
  78. package/dist/runtime/i18n/messages/de.json +1207 -0
  79. package/dist/runtime/i18n/messages/en.json +1207 -0
  80. package/dist/runtime/i18n/translate.d.ts +83 -0
  81. package/dist/runtime/i18n/translate.js +41 -0
  82. package/dist/runtime/pages/integrations/audit.vue +52 -34
  83. package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
  84. package/dist/runtime/pages/integrations/credentials.vue +70 -52
  85. package/dist/runtime/pages/integrations/index.vue +50 -56
  86. package/dist/runtime/pages/integrations/runs.vue +33 -21
  87. package/dist/runtime/pages/integrations/schedules.vue +49 -28
  88. package/dist/runtime/pages/integrations/secrets.vue +34 -34
  89. package/dist/runtime/pages/integrations/templates/[slug].vue +77 -68
  90. package/dist/runtime/pages/integrations/templates/index.vue +35 -27
  91. package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
  92. package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
  93. package/dist/runtime/plugins/locale.d.ts +10 -0
  94. package/dist/runtime/plugins/locale.js +6 -0
  95. package/dist/runtime/plugins/realtime.client.d.ts +32 -0
  96. package/dist/runtime/plugins/realtime.client.js +4 -0
  97. package/dist/runtime/utils/credentialState.d.ts +24 -1
  98. package/dist/runtime/utils/credentialState.js +26 -11
  99. package/dist/runtime/utils/localize.d.ts +18 -5
  100. package/dist/runtime/utils/localize.js +32 -9
  101. package/dist/runtime/utils/pollBudget.d.ts +72 -0
  102. package/dist/runtime/utils/pollBudget.js +17 -0
  103. package/dist/runtime/utils/schedule.d.ts +0 -25
  104. package/dist/runtime/utils/schedule.js +37 -25
  105. package/dist/runtime/utils/statusStyle.d.ts +26 -2
  106. package/dist/runtime/utils/statusStyle.js +9 -1
  107. package/dist/runtime/utils/validationFindings.d.ts +2 -2
  108. package/dist/runtime/utils/validationFindings.js +4 -3
  109. package/dist/types.d.mts +2 -6
  110. 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, "Could not load the revision history.");
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((t) => ({
29
- id: t.handle ?? String(t.id ?? ""),
30
- type: t.type,
31
- name: typeof t.name === "string" ? t.name : t.name ?? void 0,
32
- active: t.active,
33
- config: t.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?: "sync" | "async") => Promise<unknown>;
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 ?? "Could not load runs.";
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, "Could not load secrets.");
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("Key must match ^[A-Z0-9_]+$");
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: any;
3
+ templates: import("vue").ComputedRef<WorkflowTemplate[]>;
4
4
  loaded: any;
5
5
  loading: any;
6
6
  error: any;
7
- total: import("vue").ComputedRef<any>;
8
- startingPoints: import("vue").ComputedRef<any>;
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 templates = useState("integrations.templates", () => []);
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
- templates.value = list2.map(mapListItem);
111
+ rawList.value = list2;
112
+ rawDetails.value = {};
105
113
  loaded.value = true;
106
114
  } catch (e) {
107
- error.value = captureError(e, "Could not load templates.");
108
- templates.value = [];
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((t) => t.slug === slug) ?? null;
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
- const merged = applyDetail(base, detail);
127
- templates.value = templates.value.map((t) => t.slug === slug ? merged : t);
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((t) => ({ ...t, workflowName: wf.name })))
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 ?? "Could not load triggers.";
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, "Could not load workflows.");
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("Workflow not found.");
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("Could not duplicate the workflow.");
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 message = err instanceof IntegrationsApiError ? err.message : err?.message ?? "Resolve failed";
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) return known.label;
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 === "" ? "File" : labelFor(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 || "Integration", src: logo.src });
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
  }