@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
|
@@ -25,6 +25,7 @@ defineProps({
|
|
|
25
25
|
workflows: { type: Array, required: true },
|
|
26
26
|
loading: { type: Boolean, required: false, default: false }
|
|
27
27
|
});
|
|
28
|
+
const t = useIntegrationsT();
|
|
28
29
|
const router = useRouter();
|
|
29
30
|
const { remove, toggleActive } = useIntegrationsWorkflows();
|
|
30
31
|
const duplicateTarget = ref(null);
|
|
@@ -34,7 +35,7 @@ function askDuplicate(wf) {
|
|
|
34
35
|
duplicateOpen.value = true;
|
|
35
36
|
}
|
|
36
37
|
async function onDuplicated(copy) {
|
|
37
|
-
toast.success(
|
|
38
|
+
toast.success(t("integration.workflows.table.toast.duplicated", { name: copy.name }));
|
|
38
39
|
await router.push(`/integrations/workflows/${copy.id}`);
|
|
39
40
|
}
|
|
40
41
|
const GROUP_BY_TAGS_KEY = "integrations.workflows.groupByTags";
|
|
@@ -60,15 +61,15 @@ function lastRunFor(id) {
|
|
|
60
61
|
function runBadge(status) {
|
|
61
62
|
switch (status) {
|
|
62
63
|
case "succeeded":
|
|
63
|
-
return { label: "
|
|
64
|
+
return { label: t("integration.workflows.table.status.run.succeeded"), class: "bg-emerald-500/15 text-emerald-700 border-emerald-500/20" };
|
|
64
65
|
case "failed":
|
|
65
|
-
return { label: "
|
|
66
|
+
return { label: t("integration.workflows.table.status.run.failed"), class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
|
|
66
67
|
case "running":
|
|
67
|
-
return { label: "
|
|
68
|
+
return { label: t("integration.workflows.table.status.run.running"), class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
|
|
68
69
|
case "queued":
|
|
69
|
-
return { label: "
|
|
70
|
+
return { label: t("integration.workflows.table.status.run.queued"), class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
|
|
70
71
|
case "cancelled":
|
|
71
|
-
return { label: "
|
|
72
|
+
return { label: t("integration.workflows.table.status.run.cancelled"), class: "bg-slate-100 text-slate-700 border-slate-200" };
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
const pendingDelete = ref(null);
|
|
@@ -93,27 +94,38 @@ async function onToggleActive(wf) {
|
|
|
93
94
|
} catch (e) {
|
|
94
95
|
const issues = apiValidationIssues(e);
|
|
95
96
|
if (issues.length) {
|
|
96
|
-
toast.error(
|
|
97
|
+
toast.error(t("integration.workflows.table.toast.activateBlocked", issues.length, { name: wf.name }));
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
toast.error(e?.message ?? "
|
|
100
|
+
toast.error(e?.message ?? t("integration.workflows.table.toast.statusChangeFailed"));
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
function lifecycleOf(wf) {
|
|
104
|
+
if (!wf.active) return "paused";
|
|
105
|
+
return wf.revision === 0 ? "draft" : "active";
|
|
106
|
+
}
|
|
107
|
+
function lifecycleLabel(state) {
|
|
108
|
+
return t(`integration.workflows.table.status.lifecycle.${state}`);
|
|
109
|
+
}
|
|
110
|
+
const LIFECYCLE_TONE = {
|
|
111
|
+
active: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20",
|
|
112
|
+
draft: "bg-amber-500/10 text-amber-700 border-amber-500/20",
|
|
113
|
+
paused: "bg-slate-100 text-slate-700 border-slate-200"
|
|
114
|
+
};
|
|
102
115
|
function statusBadge(wf) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return { label: "Active", class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
|
|
116
|
+
const state = lifecycleOf(wf);
|
|
117
|
+
return { label: lifecycleLabel(state), class: LIFECYCLE_TONE[state] };
|
|
106
118
|
}
|
|
107
119
|
function buildState(wf) {
|
|
108
120
|
switch (wf.buildStatus) {
|
|
109
121
|
case "building":
|
|
110
|
-
return { label: "
|
|
122
|
+
return { label: t("integration.workflows.table.status.build.building"), class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
|
|
111
123
|
case "pending":
|
|
112
|
-
return { label: "
|
|
124
|
+
return { label: t("integration.workflows.table.status.build.pending"), class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
|
|
113
125
|
case "failed":
|
|
114
|
-
return { label: "
|
|
126
|
+
return { label: t("integration.workflows.table.status.build.failed"), class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
|
|
115
127
|
case "ready":
|
|
116
|
-
return { label: "
|
|
128
|
+
return { label: t("integration.workflows.table.status.build.ready"), class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
|
|
117
129
|
default:
|
|
118
130
|
return { label: "\u2014", class: "bg-slate-100 text-slate-700 border-slate-200" };
|
|
119
131
|
}
|
|
@@ -121,11 +133,11 @@ function buildState(wf) {
|
|
|
121
133
|
function modeLabel(m) {
|
|
122
134
|
switch (m) {
|
|
123
135
|
case "sync_only":
|
|
124
|
-
return "sync";
|
|
136
|
+
return t("integration.workflows.table.mode.sync");
|
|
125
137
|
case "caller_decides":
|
|
126
|
-
return "caller";
|
|
138
|
+
return t("integration.workflows.table.mode.caller");
|
|
127
139
|
case "async_only":
|
|
128
|
-
return "async";
|
|
140
|
+
return t("integration.workflows.table.mode.async");
|
|
129
141
|
default:
|
|
130
142
|
return "\u2014";
|
|
131
143
|
}
|
|
@@ -143,45 +155,71 @@ function formatDateTime(iso) {
|
|
|
143
155
|
return iso;
|
|
144
156
|
}
|
|
145
157
|
}
|
|
146
|
-
const
|
|
158
|
+
const LIFECYCLE = ["active", "draft", "paused"];
|
|
159
|
+
const BUILD_STATES = ["pending", "building", "ready", "failed"];
|
|
160
|
+
const MODES = ["async_only", "sync_only", "caller_decides"];
|
|
161
|
+
const filterTabs = computed(() => [
|
|
162
|
+
{
|
|
163
|
+
key: "status",
|
|
164
|
+
label: t("integration.workflows.table.column.status"),
|
|
165
|
+
getter: (wf) => lifecycleOf(wf),
|
|
166
|
+
options: LIFECYCLE.map((v) => ({ value: v, label: lifecycleLabel(v) }))
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: "build",
|
|
170
|
+
label: t("integration.workflows.table.column.build"),
|
|
171
|
+
getter: (wf) => wf.buildStatus ?? "",
|
|
172
|
+
options: BUILD_STATES.map((v) => ({
|
|
173
|
+
value: v,
|
|
174
|
+
label: t(`integration.workflows.table.status.build.${v}`)
|
|
175
|
+
}))
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
key: "mode",
|
|
179
|
+
label: t("integration.workflows.table.column.mode"),
|
|
180
|
+
getter: (wf) => wf.execution_mode ?? "",
|
|
181
|
+
options: MODES.map((v) => ({ value: v, label: modeLabel(v) }))
|
|
182
|
+
}
|
|
183
|
+
]);
|
|
184
|
+
const columns = computed(() => [
|
|
147
185
|
{
|
|
148
186
|
key: "name",
|
|
149
|
-
label: "
|
|
187
|
+
label: t("integration.workflows.table.column.name"),
|
|
150
188
|
sortable: true,
|
|
151
189
|
sortGetter: (w) => w.name.toLowerCase(),
|
|
152
190
|
searchGetter: (w) => `${w.name} ${w.description ?? ""} ${(w.blob.tags ?? []).join(" ")}`
|
|
153
191
|
},
|
|
154
192
|
{
|
|
155
193
|
key: "status",
|
|
156
|
-
label: "
|
|
194
|
+
label: t("integration.workflows.table.column.status"),
|
|
157
195
|
width: "100px",
|
|
158
196
|
sortable: true,
|
|
159
|
-
sortGetter: (w) => w
|
|
197
|
+
sortGetter: (w) => lifecycleOf(w)
|
|
160
198
|
},
|
|
161
199
|
{
|
|
162
200
|
key: "build",
|
|
163
|
-
label: "
|
|
201
|
+
label: t("integration.workflows.table.column.build"),
|
|
164
202
|
width: "120px",
|
|
165
203
|
sortable: true,
|
|
166
204
|
sortGetter: (w) => w.buildStatus ?? ""
|
|
167
205
|
},
|
|
168
206
|
{
|
|
169
207
|
key: "mode",
|
|
170
|
-
label: "
|
|
208
|
+
label: t("integration.workflows.table.column.mode"),
|
|
171
209
|
width: "120px",
|
|
172
210
|
sortable: true,
|
|
173
211
|
sortGetter: (w) => w.execution_mode ?? ""
|
|
174
212
|
},
|
|
175
213
|
{
|
|
176
214
|
key: "triggers",
|
|
177
|
-
label: "
|
|
215
|
+
label: t("integration.workflows.table.column.triggers"),
|
|
178
216
|
width: "140px",
|
|
179
217
|
sortable: true,
|
|
180
|
-
sortGetter: (w) => w.blob.triggers.map((
|
|
218
|
+
sortGetter: (w) => w.blob.triggers.map((tr) => tr.type).join(",")
|
|
181
219
|
},
|
|
182
220
|
{
|
|
183
221
|
key: "lastRun",
|
|
184
|
-
label: "
|
|
222
|
+
label: t("integration.workflows.table.column.lastRun"),
|
|
185
223
|
// Wide enough for badge + full timestamp — the flexible Name column has
|
|
186
224
|
// the slack to give; a truncated "18. Juli, 1…" helps nobody.
|
|
187
225
|
width: "210px",
|
|
@@ -190,7 +228,7 @@ const columns = [
|
|
|
190
228
|
},
|
|
191
229
|
{
|
|
192
230
|
key: "updated",
|
|
193
|
-
label: "
|
|
231
|
+
label: t("integration.workflows.table.column.updated"),
|
|
194
232
|
width: "120px",
|
|
195
233
|
sortable: true,
|
|
196
234
|
sortGetter: (w) => w.updated_at ?? ""
|
|
@@ -201,21 +239,21 @@ const columns = [
|
|
|
201
239
|
width: "40px",
|
|
202
240
|
toggleable: false
|
|
203
241
|
}
|
|
204
|
-
];
|
|
242
|
+
]);
|
|
205
243
|
</script>
|
|
206
244
|
|
|
207
245
|
<template>
|
|
208
246
|
<DataTable
|
|
209
247
|
:data="workflows"
|
|
210
248
|
:group-by="groupByTags ? (w) => w.blob.tags ?? [] : null"
|
|
211
|
-
group-empty-label="
|
|
249
|
+
:group-empty-label="t('integration.workflows.table.group.noTags')"
|
|
212
250
|
:columns="columns"
|
|
213
251
|
:row-key="(wf2) => wf2.id"
|
|
214
252
|
:default-sort="{ column: 'updated', direction: 'desc' }"
|
|
215
|
-
:search="{ placeholder: '
|
|
216
|
-
:filter="
|
|
253
|
+
:search="{ placeholder: t('integration.workflows.table.search.placeholder') }"
|
|
254
|
+
:filter-tabs="filterTabs"
|
|
217
255
|
:loading="loading"
|
|
218
|
-
empty-text="
|
|
256
|
+
:empty-text="t('integration.workflows.table.state.empty')"
|
|
219
257
|
@row-click="(wf2) => router.push(`/integrations/workflows/${wf2.id}`)"
|
|
220
258
|
>
|
|
221
259
|
<template #toolbar-left>
|
|
@@ -227,7 +265,7 @@ const columns = [
|
|
|
227
265
|
@click="groupByTags = !groupByTags"
|
|
228
266
|
>
|
|
229
267
|
<Layers :size="13" />
|
|
230
|
-
|
|
268
|
+
{{ t("integration.workflows.table.action.groupByTags") }}
|
|
231
269
|
</Button>
|
|
232
270
|
</template>
|
|
233
271
|
|
|
@@ -317,7 +355,7 @@ const columns = [
|
|
|
317
355
|
<span
|
|
318
356
|
v-else
|
|
319
357
|
class="text-sm text-muted-foreground"
|
|
320
|
-
:aria-label="runsLoaded ?
|
|
358
|
+
:aria-label="runsLoaded ? t('integration.workflows.table.lastRun.aria.never', { name: wf.name }) : void 0"
|
|
321
359
|
>—</span>
|
|
322
360
|
</template>
|
|
323
361
|
|
|
@@ -333,28 +371,28 @@ const columns = [
|
|
|
333
371
|
<DropdownMenuTrigger as-child>
|
|
334
372
|
<!-- Icon-only, so it needs a name of its own — the rows are plain
|
|
335
373
|
divs, which leaves nothing else to address this button by. -->
|
|
336
|
-
<Button variant="ghost" size="icon" class="size-7" :aria-label="
|
|
374
|
+
<Button variant="ghost" size="icon" class="size-7" :aria-label="t('integration.workflows.table.menu.actions.aria', { name: wf.name })">
|
|
337
375
|
<MoreHorizontal :size="14" />
|
|
338
376
|
</Button>
|
|
339
377
|
</DropdownMenuTrigger>
|
|
340
378
|
<DropdownMenuContent align="end">
|
|
341
|
-
<DropdownMenuLabel>
|
|
379
|
+
<DropdownMenuLabel>{{ t("integration.workflows.table.menu.actions.label") }}</DropdownMenuLabel>
|
|
342
380
|
<DropdownMenuSeparator />
|
|
343
381
|
<DropdownMenuItem @select="router.push(`/integrations/workflows/${wf.id}`)">
|
|
344
|
-
|
|
382
|
+
{{ t("integration.workflows.table.menu.action.openEditor") }}
|
|
345
383
|
</DropdownMenuItem>
|
|
346
384
|
<DropdownMenuItem @select="askDuplicate(wf)">
|
|
347
|
-
|
|
385
|
+
{{ t("integration.workflows.action.duplicate") }}
|
|
348
386
|
</DropdownMenuItem>
|
|
349
387
|
<DropdownMenuItem @select="onToggleActive(wf)">
|
|
350
|
-
{{ wf.active ? "
|
|
388
|
+
{{ wf.active ? t("integration.workflows.table.menu.action.pause") : t("integration.workflows.table.menu.action.activate") }}
|
|
351
389
|
</DropdownMenuItem>
|
|
352
390
|
<DropdownMenuSeparator />
|
|
353
391
|
<DropdownMenuItem
|
|
354
392
|
class="text-destructive focus:text-destructive"
|
|
355
393
|
@select="askDelete(wf)"
|
|
356
394
|
>
|
|
357
|
-
|
|
395
|
+
{{ t("integration.common.action.delete") }}
|
|
358
396
|
</DropdownMenuItem>
|
|
359
397
|
</DropdownMenuContent>
|
|
360
398
|
</DropdownMenu>
|
|
@@ -367,21 +405,21 @@ const columns = [
|
|
|
367
405
|
}">
|
|
368
406
|
<AlertDialogContent>
|
|
369
407
|
<AlertDialogHeader>
|
|
370
|
-
<AlertDialogTitle>
|
|
408
|
+
<AlertDialogTitle>{{ t("integration.workflows.table.deleteDialog.title") }}</AlertDialogTitle>
|
|
371
409
|
<AlertDialogDescription>
|
|
372
410
|
<span v-if="pendingDelete">
|
|
373
411
|
<span class="font-medium text-foreground">{{ pendingDelete.name }}</span>
|
|
374
|
-
|
|
412
|
+
{{ t("integration.workflows.table.deleteDialog.description") }}
|
|
375
413
|
</span>
|
|
376
414
|
</AlertDialogDescription>
|
|
377
415
|
</AlertDialogHeader>
|
|
378
416
|
<AlertDialogFooter>
|
|
379
417
|
<Button variant="outline" :disabled="deleting" @click="pendingDelete = null">
|
|
380
|
-
|
|
418
|
+
{{ t("integration.common.action.cancel") }}
|
|
381
419
|
</Button>
|
|
382
420
|
<Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="confirmDelete">
|
|
383
421
|
<Loader2 v-if="deleting" :size="14" class="animate-spin" />
|
|
384
|
-
{{ deleting ? "
|
|
422
|
+
{{ deleting ? t("integration.workflows.table.deleteDialog.action.deleting") : t("integration.common.action.delete") }}
|
|
385
423
|
</Button>
|
|
386
424
|
</AlertDialogFooter>
|
|
387
425
|
</AlertDialogContent>
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
3
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
4
|
export function useIntegrationsApi() {
|
|
4
5
|
const config = useRuntimeConfig();
|
|
5
6
|
const { getToken } = usePlatformAuth();
|
|
6
7
|
const { slug: onlyTenantSlug, load: loadTenants } = usePlatformTenant();
|
|
8
|
+
const t = useIntegrationsT();
|
|
7
9
|
const baseUrl = computed(
|
|
8
10
|
() => config.public.integrationsApi ?? "https://integrations.revenexx.com/api/v1"
|
|
9
11
|
);
|
|
10
12
|
async function buildHeaders(extra = {}) {
|
|
11
13
|
const token = await getToken();
|
|
12
|
-
if (!token) throw new IntegrationsApiError(401, null, "
|
|
14
|
+
if (!token) throw new IntegrationsApiError(401, null, t("integration.common.error.notSignedIn"));
|
|
13
15
|
if (!onlyTenantSlug.value) await loadTenants();
|
|
14
16
|
const headers = {
|
|
15
17
|
Accept: "application/json",
|
|
@@ -34,7 +36,7 @@ export function useIntegrationsApi() {
|
|
|
34
36
|
} catch (err) {
|
|
35
37
|
const fe = err;
|
|
36
38
|
const data = fe?.response?._data;
|
|
37
|
-
const detail = data?.message ?? fe?.message ?? "
|
|
39
|
+
const detail = data?.message ?? fe?.message ?? t("integration.common.error.requestFailed");
|
|
38
40
|
throw new IntegrationsApiError(fe?.response?.status ?? 0, data, detail);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -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 useCredentialTypes() {
|
|
@@ -8,6 +9,7 @@ export function useCredentialTypes() {
|
|
|
8
9
|
const loaded = useState("integrations.credentialTypes.loaded", () => false);
|
|
9
10
|
const gate = useLoadGate("integrations.credentialTypes", { loading, loaded });
|
|
10
11
|
const api = useIntegrationsApi();
|
|
12
|
+
const t = useIntegrationsT();
|
|
11
13
|
function normalize(res) {
|
|
12
14
|
const obj = res;
|
|
13
15
|
if (Array.isArray(obj)) return obj;
|
|
@@ -25,14 +27,14 @@ export function useCredentialTypes() {
|
|
|
25
27
|
types.value = normalize(await api.get("/credential-types"));
|
|
26
28
|
loaded.value = true;
|
|
27
29
|
} catch (e) {
|
|
28
|
-
error.value = captureError(e, "
|
|
30
|
+
error.value = captureError(e, t("integration.credentials.error.loadTypes"));
|
|
29
31
|
} finally {
|
|
30
32
|
gate.release();
|
|
31
33
|
}
|
|
32
34
|
return types.value;
|
|
33
35
|
}
|
|
34
36
|
function bySlug(slug) {
|
|
35
|
-
return types.value.find((
|
|
37
|
+
return types.value.find((t2) => t2.slug === slug) ?? null;
|
|
36
38
|
}
|
|
37
39
|
async function get(slug) {
|
|
38
40
|
const cached = bySlug(slug);
|
|
@@ -13,8 +13,19 @@ import type { Credential } from '../types/integrations.js';
|
|
|
13
13
|
* POST /credentials/{id}/test → test saved instance
|
|
14
14
|
* POST /credentials/{id}/oauth/authorize-url → { authorize_url, state }
|
|
15
15
|
*/
|
|
16
|
+
/**
|
|
17
|
+
* `message` is what the credential type said about the test, whichever way it went —
|
|
18
|
+
* which host answered, which environment, which plan, or why it was refused. The
|
|
19
|
+
* service sends it on every path, and it may be absent.
|
|
20
|
+
*
|
|
21
|
+
* `error` is not the service's: it is what THIS composable puts there when the call
|
|
22
|
+
* never arrived — a network failure, a refusal that is not an answer. Nothing on the
|
|
23
|
+
* wire ever carries it, so a surface that reads `error` alone and falls back to a
|
|
24
|
+
* fixed sentence throws away every reason the platform did send.
|
|
25
|
+
*/
|
|
16
26
|
export interface CredentialTestResult {
|
|
17
27
|
ok: boolean;
|
|
28
|
+
message?: string;
|
|
18
29
|
error?: string;
|
|
19
30
|
}
|
|
20
31
|
export declare function useIntegrationsCredentials(): {
|
|
@@ -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
|
export function useIntegrationsCredentials() {
|
|
@@ -9,6 +10,7 @@ export function useIntegrationsCredentials() {
|
|
|
9
10
|
const loaded = useState("integrations.credentials.loaded", () => false);
|
|
10
11
|
const gate = useLoadGate("integrations.credentials", { loading, loaded });
|
|
11
12
|
const api = useIntegrationsApi();
|
|
13
|
+
const t = useIntegrationsT();
|
|
12
14
|
function normalize(res) {
|
|
13
15
|
const obj = res;
|
|
14
16
|
if (Array.isArray(obj)) return obj;
|
|
@@ -29,7 +31,7 @@ export function useIntegrationsCredentials() {
|
|
|
29
31
|
credentials.value = normalize(await api.get("/credentials"));
|
|
30
32
|
loaded.value = true;
|
|
31
33
|
} catch (e) {
|
|
32
|
-
error.value = captureError(e, "
|
|
34
|
+
error.value = captureError(e, t("integration.credentials.error.loadFailed"));
|
|
33
35
|
} finally {
|
|
34
36
|
gate.release();
|
|
35
37
|
}
|
|
@@ -58,23 +60,23 @@ export function useIntegrationsCredentials() {
|
|
|
58
60
|
async function testInline(credentialTypeSlug, config) {
|
|
59
61
|
try {
|
|
60
62
|
const res = await api.post("/credentials/test", { credential_type_slug: credentialTypeSlug, config });
|
|
61
|
-
return res?.ok === false ? res : { ok: true };
|
|
63
|
+
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
62
64
|
} catch (e) {
|
|
63
|
-
return { ok: false, error: captureError(e, "
|
|
65
|
+
return { ok: false, error: captureError(e, t("integration.credentials.error.testFailed")) };
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
async function test(id) {
|
|
67
69
|
try {
|
|
68
70
|
const res = await api.post(`/credentials/${encodeURIComponent(id)}/test`);
|
|
69
|
-
return res?.ok === false ? res : { ok: true };
|
|
71
|
+
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
70
72
|
} catch (e) {
|
|
71
|
-
return { ok: false, error: captureError(e, "
|
|
73
|
+
return { ok: false, error: captureError(e, t("integration.credentials.error.testFailed")) };
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
async function oauthAuthorizeUrl(id) {
|
|
75
77
|
const res = await api.post(`/credentials/${encodeURIComponent(id)}/oauth/authorize-url`);
|
|
76
78
|
const authorizeUrl = res?.authorize_url ?? res?.data?.authorize_url;
|
|
77
|
-
if (!authorizeUrl) throw new IntegrationsApiError(0, res, "
|
|
79
|
+
if (!authorizeUrl) throw new IntegrationsApiError(0, res, t("integration.credentials.error.noAuthorizeUrl"));
|
|
78
80
|
return authorizeUrl;
|
|
79
81
|
}
|
|
80
82
|
const total = computed(() => credentials.value.length);
|
|
@@ -6,6 +6,18 @@
|
|
|
6
6
|
* Pages opt in by calling `useIntegrationsFocus().enable()` in onMounted
|
|
7
7
|
* and `disable()` in onUnmounted (we provide an `autoEnable()` helper
|
|
8
8
|
* that wires both).
|
|
9
|
+
*
|
|
10
|
+
* **A page that hides the sidebar renders the control that brings it back** — see
|
|
11
|
+
* specs/studio-navigation.md. The control belongs to the page and not to the
|
|
12
|
+
* shell, because the cockpit ships its own shell and reads this same state: a
|
|
13
|
+
* control in the module's shell would be missing in the cockpit, which is the
|
|
14
|
+
* one-way switch PO-273 was filed about.
|
|
15
|
+
*
|
|
16
|
+
* `autoEnable()` *is* that one-way switch, and `toggle()` has no caller: the
|
|
17
|
+
* workflow editor derives the state from a remembered preference and applies it
|
|
18
|
+
* with `enable()`/`disable()`, which a derived state can do idempotently and a
|
|
19
|
+
* flip cannot. Both are kept because they are part of what this module publishes,
|
|
20
|
+
* and a host driving the fold itself may still want them.
|
|
9
21
|
*/
|
|
10
22
|
export declare function useIntegrationsFocus(): {
|
|
11
23
|
focused: any;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* The studio's interface language, for a host to read and to set.
|
|
4
|
+
*
|
|
5
|
+
* This is the seam between the application the studio is embedded in and the
|
|
6
|
+
* text the Integrations API returns. The module is published and installed into
|
|
7
|
+
* hosts it knows nothing about, so it never reads a browser store or a cockpit
|
|
8
|
+
* i18n instance: the host owns the choice and hands it over here.
|
|
9
|
+
*
|
|
10
|
+
* A host with a language switcher wires it once, at boot:
|
|
11
|
+
*
|
|
12
|
+
* const { setLocale } = useIntegrationsLocale()
|
|
13
|
+
* watch(uiLocale, l => setLocale(l), { immediate: true })
|
|
14
|
+
*
|
|
15
|
+
* A host with a fixed language sets `studioIntegrations.locale` in its Nuxt
|
|
16
|
+
* config and never calls this. Neither is required — with nothing set the
|
|
17
|
+
* studio renders English.
|
|
18
|
+
*
|
|
19
|
+
* `locale` is reactive, and so is every name, label and description on screen:
|
|
20
|
+
* they are resolved through the same value, so a switch reaches surfaces that
|
|
21
|
+
* are already mounted without reloading anything.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useIntegrationsLocale(): {
|
|
24
|
+
locale: ComputedRef<string>;
|
|
25
|
+
setLocale: (locale: string | null | undefined) => void;
|
|
26
|
+
};
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { applyNodeCatalog, findManifest } from "../data/integrationNodeCatalog.js";
|
|
2
2
|
import { resolveLocalized } from "../utils/localize.js";
|
|
3
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
4
|
+
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
5
|
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
5
6
|
import { useLoadGate } from "./useLoadGate.js";
|
|
7
|
+
const RETRY_COOLDOWN_MS = 3e4;
|
|
6
8
|
export function useIntegrationsNodes() {
|
|
7
9
|
const loading = useState("integrations.nodes.loading", () => false);
|
|
8
10
|
const error = useState("integrations.nodes.error", () => null);
|
|
9
11
|
const loaded = useState("integrations.nodes.loaded", () => false);
|
|
12
|
+
const failedAt = useState("integrations.nodes.failedAt", () => 0);
|
|
10
13
|
const gate = useLoadGate("integrations.nodes", { loading, loaded });
|
|
11
14
|
const api = useIntegrationsApi();
|
|
15
|
+
const t = useIntegrationsT();
|
|
12
16
|
async function load(force = false) {
|
|
17
|
+
if (!force && failedAt.value && Date.now() - failedAt.value < RETRY_COOLDOWN_MS) return;
|
|
13
18
|
if (!await gate.claim(force)) return;
|
|
14
19
|
error.value = null;
|
|
15
20
|
try {
|
|
@@ -21,13 +26,20 @@ export function useIntegrationsNodes() {
|
|
|
21
26
|
version: n.manifest.version ?? n.version,
|
|
22
27
|
name: n.manifest.name ?? n.name,
|
|
23
28
|
// Keep the source package — the palette groups app nodes by it.
|
|
24
|
-
__package: n.package ? { name: n.package.name, label: n.package.label ?? null } : void 0
|
|
29
|
+
__package: n.package ? { name: n.package.name, label: n.package.label ?? null } : void 0,
|
|
30
|
+
// Registered and built are two different things: a version whose code
|
|
31
|
+
// was never built can be drawn but not executed, so the palette must
|
|
32
|
+
// be able to leave it out (PO-338). Missing field = assume built.
|
|
33
|
+
__hasBundle: n.has_bundle !== false
|
|
25
34
|
}));
|
|
26
35
|
applyNodeCatalog(manifests);
|
|
27
36
|
loaded.value = true;
|
|
37
|
+
failedAt.value = 0;
|
|
28
38
|
} catch (e) {
|
|
29
39
|
applyNodeCatalog([]);
|
|
30
|
-
|
|
40
|
+
loaded.value = false;
|
|
41
|
+
failedAt.value = Date.now();
|
|
42
|
+
error.value = e instanceof IntegrationsApiError ? e.message : e?.message ?? t("integration.nodes.error.loadFailed");
|
|
31
43
|
} finally {
|
|
32
44
|
gate.release();
|
|
33
45
|
}
|