@revenexx/studio-integrations 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +428 -0
- package/README.md +48 -4
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +43 -41
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +123 -50
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +108 -71
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +12 -15
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +7 -4
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +67 -37
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +30 -20
- package/dist/runtime/components/Integrations/Layout.vue +48 -4
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +23 -23
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +11 -13
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +187 -93
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1157 -376
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +43 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +122 -55
- package/dist/runtime/components/Integrations/QueryBuilder.vue +46 -38
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +38 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +27 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +38 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +119 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +16 -18
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +52 -67
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +29 -11
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +240 -116
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +28 -43
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +17 -7
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +198 -56
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +56 -59
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +28 -21
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +15 -10
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +50 -36
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +126 -48
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +8 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +10 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +7 -5
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +14 -7
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +14 -9
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +28 -16
- package/dist/runtime/composables/useIntegrationsTriggers.js +10 -8
- package/dist/runtime/composables/useIntegrationsWorkflows.js +13 -7
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +48 -0
- package/dist/runtime/data/integrationTemplates.js +21 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +29 -4
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +4 -6
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +140 -37
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +9 -6
- package/dist/runtime/pages/integrations/credentials.vue +180 -64
- package/dist/runtime/pages/integrations/index.vue +91 -92
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +88 -23
- package/dist/runtime/pages/integrations/secrets.vue +57 -35
- package/dist/runtime/pages/integrations/templates/[slug].vue +119 -88
- package/dist/runtime/pages/integrations/templates/index.vue +82 -103
- package/dist/runtime/pages/integrations/workflows/[id].vue +344 -91
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +46 -4
- package/dist/runtime/utils/localize.js +34 -7
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +116 -0
- package/dist/runtime/utils/schedule.js +219 -0
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +54 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/dist/types.d.mts +2 -6
- package/package.json +10 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, reactive, ref, watch } from "vue";
|
|
3
|
-
import { AddCircle as Plus, Copy, TrashBinMinimalistic as
|
|
3
|
+
import { AddCircle as Plus, Copy, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
Dialog,
|
|
@@ -18,11 +18,13 @@ import {
|
|
|
18
18
|
SelectValue
|
|
19
19
|
} from "@revenexx/studio";
|
|
20
20
|
import { toast } from "vue-sonner";
|
|
21
|
+
import { resolveDisplayName } from "../../utils/localize";
|
|
21
22
|
const props = defineProps({
|
|
22
23
|
open: { type: Boolean, required: true },
|
|
23
24
|
variables: { type: Object, required: true }
|
|
24
25
|
});
|
|
25
26
|
const emit = defineEmits(["update:open", "update:variables"]);
|
|
27
|
+
const t = useIntegrationsT();
|
|
26
28
|
const KEY_PATTERN = /^[a-z_][a-z0-9_]*$/i;
|
|
27
29
|
const TYPES = ["string", "number", "boolean", "object", "array"];
|
|
28
30
|
const jsonDrafts = reactive({});
|
|
@@ -79,19 +81,17 @@ function applyJsonDraft(key, type) {
|
|
|
79
81
|
const parsed = JSON.parse(raw);
|
|
80
82
|
const ok = type === "array" ? Array.isArray(parsed) : parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
|
|
81
83
|
if (!ok) {
|
|
82
|
-
jsonErrors[key] = type === "array" ? "
|
|
84
|
+
jsonErrors[key] = type === "array" ? t("integration.workflows.variables.error.mustBeArray") : t("integration.workflows.variables.error.mustBeObject");
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
85
87
|
jsonErrors[key] = null;
|
|
86
88
|
patchVar(key, { default: parsed });
|
|
87
89
|
} catch {
|
|
88
|
-
jsonErrors[key] = "
|
|
90
|
+
jsonErrors[key] = t("integration.workflows.variables.error.invalidJson");
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
function descriptionText(v) {
|
|
92
|
-
|
|
93
|
-
if (!d) return "";
|
|
94
|
-
return typeof d === "string" ? d : d.en ?? Object.values(d)[0] ?? "";
|
|
94
|
+
return resolveDisplayName(v.description);
|
|
95
95
|
}
|
|
96
96
|
function writeDescription(key, text) {
|
|
97
97
|
const cur = props.variables[key];
|
|
@@ -108,8 +108,8 @@ const newKey = ref("");
|
|
|
108
108
|
const newKeyError = computed(() => {
|
|
109
109
|
const k = newKey.value.trim();
|
|
110
110
|
if (!k) return null;
|
|
111
|
-
if (!KEY_PATTERN.test(k)) return "
|
|
112
|
-
if (props.variables[k]) return "
|
|
111
|
+
if (!KEY_PATTERN.test(k)) return t("integration.workflows.variables.error.invalidKey");
|
|
112
|
+
if (props.variables[k]) return t("integration.workflows.variables.error.keyExists");
|
|
113
113
|
return null;
|
|
114
114
|
});
|
|
115
115
|
function addVariable() {
|
|
@@ -121,9 +121,9 @@ function addVariable() {
|
|
|
121
121
|
async function copyUsage(key) {
|
|
122
122
|
try {
|
|
123
123
|
await navigator.clipboard.writeText(`\${{ vars.${key} }}`);
|
|
124
|
-
toast.success("
|
|
124
|
+
toast.success(t("integration.workflows.variables.toast.copied"));
|
|
125
125
|
} catch {
|
|
126
|
-
toast.error("
|
|
126
|
+
toast.error(t("integration.workflows.variables.toast.copyFailed"));
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
</script>
|
|
@@ -132,22 +132,24 @@ async function copyUsage(key) {
|
|
|
132
132
|
<Dialog :open="open" @update:open="(v2) => emit('update:open', v2)">
|
|
133
133
|
<DialogContent class="max-w-[94vw] sm:max-w-3xl">
|
|
134
134
|
<DialogHeader>
|
|
135
|
-
<DialogTitle>
|
|
135
|
+
<DialogTitle>{{ t("integration.workflows.variables.dialog.title") }}</DialogTitle>
|
|
136
136
|
<DialogDescription class="text-sm">
|
|
137
|
-
|
|
137
|
+
{{ t("integration.workflows.variables.dialog.description.before") }}
|
|
138
138
|
<code v-pre class="rounded bg-black/[0.05] px-1 py-0.5 font-mono text-xs">${{ vars.key }}</code>.
|
|
139
|
-
|
|
139
|
+
{{ t("integration.workflows.variables.dialog.description.after") }}
|
|
140
140
|
</DialogDescription>
|
|
141
141
|
</DialogHeader>
|
|
142
142
|
|
|
143
143
|
<div class="max-h-[62vh] space-y-3 overflow-y-auto pr-1">
|
|
144
144
|
<p v-if="!entries.length" class="rounded-lg border border-dashed border-black/[0.12] px-4 py-6 text-center text-sm text-muted-foreground">
|
|
145
|
-
|
|
145
|
+
{{ t("integration.workflows.variables.state.empty") }}
|
|
146
146
|
</p>
|
|
147
147
|
|
|
148
148
|
<div
|
|
149
149
|
v-for="[key, v] in entries"
|
|
150
150
|
:key="key"
|
|
151
|
+
role="group"
|
|
152
|
+
:aria-label="t('integration.workflows.variables.row.aria', { key })"
|
|
151
153
|
class="rounded-lg border border-black/[0.06] bg-white p-3"
|
|
152
154
|
>
|
|
153
155
|
<div class="flex items-center gap-2">
|
|
@@ -155,34 +157,32 @@ async function copyUsage(key) {
|
|
|
155
157
|
<button
|
|
156
158
|
type="button"
|
|
157
159
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
158
|
-
:title="
|
|
160
|
+
:title="t('integration.workflows.variables.action.copyUsage.title', { key })"
|
|
159
161
|
@click="copyUsage(key)"
|
|
160
162
|
>
|
|
161
163
|
<Copy :size="12" />
|
|
162
164
|
</button>
|
|
163
165
|
<div class="flex-1" />
|
|
164
|
-
<label class="flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
165
|
-
Required
|
|
166
|
-
<Switch
|
|
167
|
-
:model-value="v.required === true"
|
|
168
|
-
@update:model-value="(val) => patchVar(key, { required: val || void 0 })"
|
|
169
|
-
/>
|
|
170
|
-
</label>
|
|
171
166
|
<button
|
|
172
167
|
type="button"
|
|
173
168
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
174
|
-
title="
|
|
169
|
+
:title="t('integration.workflows.variables.action.delete')"
|
|
175
170
|
@click="removeVar(key)"
|
|
176
171
|
>
|
|
177
|
-
<
|
|
172
|
+
<TrashBin :size="13" />
|
|
178
173
|
</button>
|
|
179
174
|
</div>
|
|
180
175
|
|
|
181
176
|
<div class="mt-2 grid gap-2 sm:grid-cols-[8rem_1fr]">
|
|
177
|
+
<!-- One row per variable, so every id carries its key — otherwise
|
|
178
|
+
the labels of the second row point at the first row's fields. -->
|
|
182
179
|
<div class="space-y-1">
|
|
183
|
-
<Label class="text-mono text-muted-foreground">
|
|
184
|
-
<Select :model-value="v.type" @update:model-value="(
|
|
185
|
-
|
|
180
|
+
<Label :for="`var-${key}-type`" class="text-mono text-muted-foreground">{{ t("integration.workflows.variables.field.type.label") }}</Label>
|
|
181
|
+
<Select :model-value="v.type" @update:model-value="(vt) => changeType(key, vt)">
|
|
182
|
+
<!-- Both: the `id` lets the visible Label's `for` resolve, and the
|
|
183
|
+
`aria-label` names the trigger without relying on an engine
|
|
184
|
+
naming a button from a <label> — see the sweep's docblock. -->
|
|
185
|
+
<SelectTrigger :id="`var-${key}-type`" :aria-label="t('integration.workflows.variables.field.type.label')" class="h-8 bg-white text-sm">
|
|
186
186
|
<SelectValue />
|
|
187
187
|
</SelectTrigger>
|
|
188
188
|
<SelectContent>
|
|
@@ -192,37 +192,49 @@ async function copyUsage(key) {
|
|
|
192
192
|
</div>
|
|
193
193
|
|
|
194
194
|
<div class="space-y-1">
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
</Label>
|
|
195
|
+
<!-- "Value", not "Default": PO-206. The `for`/`id` still name the
|
|
196
|
+
blob field, which is called `default` and stays that way. -->
|
|
197
|
+
<Label :for="`var-${key}-default`" class="text-mono text-muted-foreground">{{ t("integration.workflows.variables.field.value.label") }}</Label>
|
|
198
198
|
|
|
199
|
+
<!-- Only one of these renders, so they share the one `id` the Label
|
|
200
|
+
points at. The labelable ones take their name from that Label —
|
|
201
|
+
one string, and renaming it turns the tests red instead of
|
|
202
|
+
leaving them green against changed copy. -->
|
|
199
203
|
<Input
|
|
200
204
|
v-if="v.type === 'string'"
|
|
205
|
+
:id="`var-${key}-default`"
|
|
201
206
|
:model-value="typeof v.default === 'string' ? v.default : ''"
|
|
202
207
|
class="h-8 bg-white text-sm"
|
|
203
208
|
@update:model-value="(val) => patchVar(key, { default: String(val) })"
|
|
204
209
|
/>
|
|
205
210
|
<Input
|
|
206
211
|
v-else-if="v.type === 'number'"
|
|
212
|
+
:id="`var-${key}-default`"
|
|
207
213
|
type="number"
|
|
208
214
|
:model-value="typeof v.default === 'number' ? v.default : ''"
|
|
209
215
|
class="h-8 bg-white text-sm"
|
|
210
216
|
@update:model-value="(val) => patchVar(key, { default: val === '' ? void 0 : Number(val) })"
|
|
211
217
|
/>
|
|
212
218
|
<div v-else-if="v.type === 'boolean'" class="flex h-8 items-center">
|
|
219
|
+
<!-- Named out loud: a Switch is a `button role="switch"`, and
|
|
220
|
+
HTML-AAM does not name a button from a <label> — Chromium
|
|
221
|
+
does, other engines need not. -->
|
|
213
222
|
<Switch
|
|
223
|
+
:id="`var-${key}-default`"
|
|
214
224
|
:model-value="v.default === true"
|
|
225
|
+
:aria-label="t('integration.workflows.variables.field.value.label')"
|
|
215
226
|
@update:model-value="(val) => patchVar(key, { default: val })"
|
|
216
227
|
/>
|
|
217
228
|
</div>
|
|
218
229
|
<div v-else>
|
|
219
230
|
<textarea
|
|
231
|
+
:id="`var-${key}-default`"
|
|
220
232
|
:value="jsonDrafts[key] ?? ''"
|
|
221
233
|
rows="3"
|
|
222
234
|
spellcheck="false"
|
|
223
235
|
class="w-full rounded-md border bg-white p-2 font-mono text-xs focus:outline-none focus:ring-2 focus:ring-primary/40"
|
|
224
236
|
:class="jsonErrors[key] ? 'border-rose-400' : 'border-black/[0.08]'"
|
|
225
|
-
:placeholder="v.type === 'array' ? '
|
|
237
|
+
:placeholder="v.type === 'array' ? t('integration.workflows.variables.field.value.placeholder.array') : t('integration.workflows.variables.field.value.placeholder.object')"
|
|
226
238
|
@input="(e) => jsonDrafts[key] = e.target.value"
|
|
227
239
|
@blur="applyJsonDraft(key, v.type)"
|
|
228
240
|
/>
|
|
@@ -232,11 +244,12 @@ async function copyUsage(key) {
|
|
|
232
244
|
</div>
|
|
233
245
|
|
|
234
246
|
<div class="mt-2 space-y-1">
|
|
235
|
-
<Label class="text-mono text-muted-foreground">
|
|
247
|
+
<Label :for="`var-${key}-desc`" class="text-mono text-muted-foreground">{{ t("integration.workflows.variables.field.description.label") }}</Label>
|
|
236
248
|
<Input
|
|
249
|
+
:id="`var-${key}-desc`"
|
|
237
250
|
:model-value="descriptionText(v)"
|
|
238
251
|
class="h-8 bg-white text-sm"
|
|
239
|
-
placeholder="
|
|
252
|
+
:placeholder="t('integration.workflows.variables.field.description.placeholder')"
|
|
240
253
|
@update:model-value="(val) => writeDescription(key, String(val))"
|
|
241
254
|
/>
|
|
242
255
|
</div>
|
|
@@ -244,17 +257,18 @@ async function copyUsage(key) {
|
|
|
244
257
|
|
|
245
258
|
<!-- Add row -->
|
|
246
259
|
<div class="rounded-lg border border-dashed border-black/[0.12] p-3">
|
|
247
|
-
<Label class="text-mono text-muted-foreground">
|
|
260
|
+
<Label for="var-new-key" class="text-mono text-muted-foreground">{{ t("integration.workflows.variables.field.newKey.label") }}</Label>
|
|
248
261
|
<div class="mt-1 flex items-center gap-2">
|
|
249
262
|
<Input
|
|
263
|
+
id="var-new-key"
|
|
250
264
|
v-model="newKey"
|
|
251
265
|
class="h-8 flex-1 bg-white font-mono text-sm"
|
|
252
|
-
placeholder="
|
|
266
|
+
:placeholder="t('integration.workflows.variables.field.newKey.placeholder')"
|
|
253
267
|
@keydown.enter.prevent="addVariable"
|
|
254
268
|
/>
|
|
255
269
|
<Button size="sm" class="h-8" :disabled="!newKey.trim() || !!newKeyError" @click="addVariable">
|
|
256
270
|
<Plus :size="14" class="mr-1" />
|
|
257
|
-
|
|
271
|
+
{{ t("integration.workflows.variables.action.add") }}
|
|
258
272
|
</Button>
|
|
259
273
|
</div>
|
|
260
274
|
<p v-if="newKeyError" class="mt-1 text-xs text-rose-600">{{ newKeyError }}</p>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { RefreshCircle as Loader2, MenuDots as MoreHorizontal } from "#solar-icons";
|
|
3
3
|
import { toast } from "vue-sonner";
|
|
4
|
+
import { apiValidationIssues } from "../../utils/apiError";
|
|
4
5
|
import WorkflowDuplicateDialog from "./WorkflowDuplicateDialog.vue";
|
|
5
6
|
import WorkflowIconView from "./WorkflowIconView.vue";
|
|
6
7
|
import { Layers } from "#solar-icons";
|
|
@@ -24,6 +25,7 @@ defineProps({
|
|
|
24
25
|
workflows: { type: Array, required: true },
|
|
25
26
|
loading: { type: Boolean, required: false, default: false }
|
|
26
27
|
});
|
|
28
|
+
const t = useIntegrationsT();
|
|
27
29
|
const router = useRouter();
|
|
28
30
|
const { remove, toggleActive } = useIntegrationsWorkflows();
|
|
29
31
|
const duplicateTarget = ref(null);
|
|
@@ -33,7 +35,7 @@ function askDuplicate(wf) {
|
|
|
33
35
|
duplicateOpen.value = true;
|
|
34
36
|
}
|
|
35
37
|
async function onDuplicated(copy) {
|
|
36
|
-
toast.success(
|
|
38
|
+
toast.success(t("integration.workflows.table.toast.duplicated", { name: copy.name }));
|
|
37
39
|
await router.push(`/integrations/workflows/${copy.id}`);
|
|
38
40
|
}
|
|
39
41
|
const GROUP_BY_TAGS_KEY = "integrations.workflows.groupByTags";
|
|
@@ -48,7 +50,7 @@ watch(groupByTags, (on) => {
|
|
|
48
50
|
} catch {
|
|
49
51
|
}
|
|
50
52
|
});
|
|
51
|
-
const { runs, load: loadRuns, list: listRuns } = useIntegrationsRuns();
|
|
53
|
+
const { runs, loaded: runsLoaded, load: loadRuns, list: listRuns } = useIntegrationsRuns();
|
|
52
54
|
onMounted(() => {
|
|
53
55
|
void loadRuns();
|
|
54
56
|
});
|
|
@@ -59,15 +61,15 @@ function lastRunFor(id) {
|
|
|
59
61
|
function runBadge(status) {
|
|
60
62
|
switch (status) {
|
|
61
63
|
case "succeeded":
|
|
62
|
-
return { label: "
|
|
64
|
+
return { label: t("integration.workflows.table.status.run.succeeded"), class: "bg-emerald-500/15 text-emerald-700 border-emerald-500/20" };
|
|
63
65
|
case "failed":
|
|
64
|
-
return { label: "
|
|
66
|
+
return { label: t("integration.workflows.table.status.run.failed"), class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
|
|
65
67
|
case "running":
|
|
66
|
-
return { label: "
|
|
68
|
+
return { label: t("integration.workflows.table.status.run.running"), class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
|
|
67
69
|
case "queued":
|
|
68
|
-
return { label: "
|
|
70
|
+
return { label: t("integration.workflows.table.status.run.queued"), class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
|
|
69
71
|
case "cancelled":
|
|
70
|
-
return { label: "
|
|
72
|
+
return { label: t("integration.workflows.table.status.run.cancelled"), class: "bg-slate-100 text-slate-700 border-slate-200" };
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
const pendingDelete = ref(null);
|
|
@@ -86,21 +88,44 @@ async function confirmDelete() {
|
|
|
86
88
|
deleting.value = false;
|
|
87
89
|
}
|
|
88
90
|
}
|
|
91
|
+
async function onToggleActive(wf) {
|
|
92
|
+
try {
|
|
93
|
+
await toggleActive(wf.id);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
const issues = apiValidationIssues(e);
|
|
96
|
+
if (issues.length) {
|
|
97
|
+
toast.error(t("integration.workflows.table.toast.activateBlocked", issues.length, { name: wf.name }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
toast.error(e?.message ?? t("integration.workflows.table.toast.statusChangeFailed"));
|
|
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
|
+
};
|
|
89
115
|
function statusBadge(wf) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
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] };
|
|
93
118
|
}
|
|
94
119
|
function buildState(wf) {
|
|
95
120
|
switch (wf.buildStatus) {
|
|
96
121
|
case "building":
|
|
97
|
-
return { label: "
|
|
122
|
+
return { label: t("integration.workflows.table.status.build.building"), class: "bg-sky-500/15 text-sky-700 border-sky-500/20" };
|
|
98
123
|
case "pending":
|
|
99
|
-
return { label: "
|
|
124
|
+
return { label: t("integration.workflows.table.status.build.pending"), class: "bg-amber-500/10 text-amber-700 border-amber-500/20" };
|
|
100
125
|
case "failed":
|
|
101
|
-
return { label: "
|
|
126
|
+
return { label: t("integration.workflows.table.status.build.failed"), class: "bg-rose-500/15 text-rose-700 border-rose-500/20" };
|
|
102
127
|
case "ready":
|
|
103
|
-
return { label: "
|
|
128
|
+
return { label: t("integration.workflows.table.status.build.ready"), class: "bg-emerald-500/10 text-emerald-700 border-emerald-500/20" };
|
|
104
129
|
default:
|
|
105
130
|
return { label: "\u2014", class: "bg-slate-100 text-slate-700 border-slate-200" };
|
|
106
131
|
}
|
|
@@ -108,11 +133,11 @@ function buildState(wf) {
|
|
|
108
133
|
function modeLabel(m) {
|
|
109
134
|
switch (m) {
|
|
110
135
|
case "sync_only":
|
|
111
|
-
return "sync";
|
|
136
|
+
return t("integration.workflows.table.mode.sync");
|
|
112
137
|
case "caller_decides":
|
|
113
|
-
return "caller";
|
|
138
|
+
return t("integration.workflows.table.mode.caller");
|
|
114
139
|
case "async_only":
|
|
115
|
-
return "async";
|
|
140
|
+
return t("integration.workflows.table.mode.async");
|
|
116
141
|
default:
|
|
117
142
|
return "\u2014";
|
|
118
143
|
}
|
|
@@ -130,45 +155,71 @@ function formatDateTime(iso) {
|
|
|
130
155
|
return iso;
|
|
131
156
|
}
|
|
132
157
|
}
|
|
133
|
-
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(() => [
|
|
134
185
|
{
|
|
135
186
|
key: "name",
|
|
136
|
-
label: "
|
|
187
|
+
label: t("integration.workflows.table.column.name"),
|
|
137
188
|
sortable: true,
|
|
138
189
|
sortGetter: (w) => w.name.toLowerCase(),
|
|
139
190
|
searchGetter: (w) => `${w.name} ${w.description ?? ""} ${(w.blob.tags ?? []).join(" ")}`
|
|
140
191
|
},
|
|
141
192
|
{
|
|
142
193
|
key: "status",
|
|
143
|
-
label: "
|
|
194
|
+
label: t("integration.workflows.table.column.status"),
|
|
144
195
|
width: "100px",
|
|
145
196
|
sortable: true,
|
|
146
|
-
sortGetter: (w) => w
|
|
197
|
+
sortGetter: (w) => lifecycleOf(w)
|
|
147
198
|
},
|
|
148
199
|
{
|
|
149
200
|
key: "build",
|
|
150
|
-
label: "
|
|
201
|
+
label: t("integration.workflows.table.column.build"),
|
|
151
202
|
width: "120px",
|
|
152
203
|
sortable: true,
|
|
153
204
|
sortGetter: (w) => w.buildStatus ?? ""
|
|
154
205
|
},
|
|
155
206
|
{
|
|
156
207
|
key: "mode",
|
|
157
|
-
label: "
|
|
208
|
+
label: t("integration.workflows.table.column.mode"),
|
|
158
209
|
width: "120px",
|
|
159
210
|
sortable: true,
|
|
160
211
|
sortGetter: (w) => w.execution_mode ?? ""
|
|
161
212
|
},
|
|
162
213
|
{
|
|
163
214
|
key: "triggers",
|
|
164
|
-
label: "
|
|
215
|
+
label: t("integration.workflows.table.column.triggers"),
|
|
165
216
|
width: "140px",
|
|
166
217
|
sortable: true,
|
|
167
|
-
sortGetter: (w) => w.blob.triggers.map((
|
|
218
|
+
sortGetter: (w) => w.blob.triggers.map((tr) => tr.type).join(",")
|
|
168
219
|
},
|
|
169
220
|
{
|
|
170
221
|
key: "lastRun",
|
|
171
|
-
label: "
|
|
222
|
+
label: t("integration.workflows.table.column.lastRun"),
|
|
172
223
|
// Wide enough for badge + full timestamp — the flexible Name column has
|
|
173
224
|
// the slack to give; a truncated "18. Juli, 1…" helps nobody.
|
|
174
225
|
width: "210px",
|
|
@@ -177,7 +228,7 @@ const columns = [
|
|
|
177
228
|
},
|
|
178
229
|
{
|
|
179
230
|
key: "updated",
|
|
180
|
-
label: "
|
|
231
|
+
label: t("integration.workflows.table.column.updated"),
|
|
181
232
|
width: "120px",
|
|
182
233
|
sortable: true,
|
|
183
234
|
sortGetter: (w) => w.updated_at ?? ""
|
|
@@ -188,21 +239,21 @@ const columns = [
|
|
|
188
239
|
width: "40px",
|
|
189
240
|
toggleable: false
|
|
190
241
|
}
|
|
191
|
-
];
|
|
242
|
+
]);
|
|
192
243
|
</script>
|
|
193
244
|
|
|
194
245
|
<template>
|
|
195
246
|
<DataTable
|
|
196
247
|
:data="workflows"
|
|
197
248
|
:group-by="groupByTags ? (w) => w.blob.tags ?? [] : null"
|
|
198
|
-
group-empty-label="
|
|
249
|
+
:group-empty-label="t('integration.workflows.table.group.noTags')"
|
|
199
250
|
:columns="columns"
|
|
200
251
|
:row-key="(wf2) => wf2.id"
|
|
201
252
|
:default-sort="{ column: 'updated', direction: 'desc' }"
|
|
202
|
-
:search="{ placeholder: '
|
|
203
|
-
:filter="
|
|
253
|
+
:search="{ placeholder: t('integration.workflows.table.search.placeholder') }"
|
|
254
|
+
:filter-tabs="filterTabs"
|
|
204
255
|
:loading="loading"
|
|
205
|
-
empty-text="
|
|
256
|
+
:empty-text="t('integration.workflows.table.state.empty')"
|
|
206
257
|
@row-click="(wf2) => router.push(`/integrations/workflows/${wf2.id}`)"
|
|
207
258
|
>
|
|
208
259
|
<template #toolbar-left>
|
|
@@ -214,7 +265,7 @@ const columns = [
|
|
|
214
265
|
@click="groupByTags = !groupByTags"
|
|
215
266
|
>
|
|
216
267
|
<Layers :size="13" />
|
|
217
|
-
|
|
268
|
+
{{ t("integration.workflows.table.action.groupByTags") }}
|
|
218
269
|
</Button>
|
|
219
270
|
</template>
|
|
220
271
|
|
|
@@ -238,14 +289,28 @@ const columns = [
|
|
|
238
289
|
</div>
|
|
239
290
|
</template>
|
|
240
291
|
|
|
292
|
+
<!-- The badge word alone ("Paused", "Failed") also matches a column header
|
|
293
|
+
and the neighbouring column's own words, so each badge carries its
|
|
294
|
+
workflow's name — the rows are plain divs with nothing else to tie a
|
|
295
|
+
value to the workflow it belongs to. -->
|
|
241
296
|
<template #cell-status="{ row: wf }">
|
|
242
|
-
<Badge
|
|
297
|
+
<Badge
|
|
298
|
+
variant="outline"
|
|
299
|
+
:class="['border text-xs font-medium uppercase', statusBadge(wf).class]"
|
|
300
|
+
:aria-label="`${wf.name}: ${statusBadge(wf).label}`"
|
|
301
|
+
>
|
|
243
302
|
{{ statusBadge(wf).label }}
|
|
244
303
|
</Badge>
|
|
245
304
|
</template>
|
|
246
305
|
|
|
306
|
+
<!-- A response without a build state renders `—`; naming that would announce
|
|
307
|
+
"<name> build: —", which claims nothing in more words. No state, no name. -->
|
|
247
308
|
<template #cell-build="{ row: wf }">
|
|
248
|
-
<Badge
|
|
309
|
+
<Badge
|
|
310
|
+
variant="outline"
|
|
311
|
+
:class="['border text-xs font-medium uppercase', buildState(wf).class]"
|
|
312
|
+
:aria-label="wf.buildStatus ? `${wf.name} build: ${buildState(wf).label}` : void 0"
|
|
313
|
+
>
|
|
249
314
|
{{ buildState(wf).label }}
|
|
250
315
|
</Badge>
|
|
251
316
|
</template>
|
|
@@ -273,14 +338,25 @@ const columns = [
|
|
|
273
338
|
|
|
274
339
|
<template #cell-lastRun="{ row: wf }">
|
|
275
340
|
<div v-if="lastRunFor(wf.id)" class="flex items-center gap-2 min-w-0">
|
|
276
|
-
<Badge
|
|
341
|
+
<Badge
|
|
342
|
+
variant="outline"
|
|
343
|
+
:class="['border text-xs font-medium uppercase shrink-0', runBadge(lastRunFor(wf.id).status).class]"
|
|
344
|
+
:aria-label="`${wf.name} last run: ${runBadge(lastRunFor(wf.id).status).label}`"
|
|
345
|
+
>
|
|
277
346
|
{{ runBadge(lastRunFor(wf.id).status).label }}
|
|
278
347
|
</Badge>
|
|
279
348
|
<span class="text-sm tabular-nums text-muted-foreground truncate">
|
|
280
349
|
{{ formatDateTime(lastRunFor(wf.id).startedAt) }}
|
|
281
350
|
</span>
|
|
282
351
|
</div>
|
|
283
|
-
|
|
352
|
+
<!-- Runs arrive after the rows do, so until they are in, "never run" is
|
|
353
|
+
not known — the bare `—` says nothing, and that is the honest answer.
|
|
354
|
+
The name is only claimed once there is something to claim it from. -->
|
|
355
|
+
<span
|
|
356
|
+
v-else
|
|
357
|
+
class="text-sm text-muted-foreground"
|
|
358
|
+
:aria-label="runsLoaded ? t('integration.workflows.table.lastRun.aria.never', { name: wf.name }) : void 0"
|
|
359
|
+
>—</span>
|
|
284
360
|
</template>
|
|
285
361
|
|
|
286
362
|
<template #cell-updated="{ row: wf }">
|
|
@@ -293,28 +369,30 @@ const columns = [
|
|
|
293
369
|
<div @click.stop>
|
|
294
370
|
<DropdownMenu>
|
|
295
371
|
<DropdownMenuTrigger as-child>
|
|
296
|
-
|
|
372
|
+
<!-- Icon-only, so it needs a name of its own — the rows are plain
|
|
373
|
+
divs, which leaves nothing else to address this button by. -->
|
|
374
|
+
<Button variant="ghost" size="icon" class="size-7" :aria-label="t('integration.workflows.table.menu.actions.aria', { name: wf.name })">
|
|
297
375
|
<MoreHorizontal :size="14" />
|
|
298
376
|
</Button>
|
|
299
377
|
</DropdownMenuTrigger>
|
|
300
378
|
<DropdownMenuContent align="end">
|
|
301
|
-
<DropdownMenuLabel>
|
|
379
|
+
<DropdownMenuLabel>{{ t("integration.workflows.table.menu.actions.label") }}</DropdownMenuLabel>
|
|
302
380
|
<DropdownMenuSeparator />
|
|
303
381
|
<DropdownMenuItem @select="router.push(`/integrations/workflows/${wf.id}`)">
|
|
304
|
-
|
|
382
|
+
{{ t("integration.workflows.table.menu.action.openEditor") }}
|
|
305
383
|
</DropdownMenuItem>
|
|
306
384
|
<DropdownMenuItem @select="askDuplicate(wf)">
|
|
307
|
-
|
|
385
|
+
{{ t("integration.workflows.action.duplicate") }}
|
|
308
386
|
</DropdownMenuItem>
|
|
309
|
-
<DropdownMenuItem @select="
|
|
310
|
-
{{ wf.active ? "
|
|
387
|
+
<DropdownMenuItem @select="onToggleActive(wf)">
|
|
388
|
+
{{ wf.active ? t("integration.workflows.table.menu.action.pause") : t("integration.workflows.table.menu.action.activate") }}
|
|
311
389
|
</DropdownMenuItem>
|
|
312
390
|
<DropdownMenuSeparator />
|
|
313
391
|
<DropdownMenuItem
|
|
314
392
|
class="text-destructive focus:text-destructive"
|
|
315
393
|
@select="askDelete(wf)"
|
|
316
394
|
>
|
|
317
|
-
|
|
395
|
+
{{ t("integration.common.action.delete") }}
|
|
318
396
|
</DropdownMenuItem>
|
|
319
397
|
</DropdownMenuContent>
|
|
320
398
|
</DropdownMenu>
|
|
@@ -327,21 +405,21 @@ const columns = [
|
|
|
327
405
|
}">
|
|
328
406
|
<AlertDialogContent>
|
|
329
407
|
<AlertDialogHeader>
|
|
330
|
-
<AlertDialogTitle>
|
|
408
|
+
<AlertDialogTitle>{{ t("integration.workflows.table.deleteDialog.title") }}</AlertDialogTitle>
|
|
331
409
|
<AlertDialogDescription>
|
|
332
410
|
<span v-if="pendingDelete">
|
|
333
411
|
<span class="font-medium text-foreground">{{ pendingDelete.name }}</span>
|
|
334
|
-
|
|
412
|
+
{{ t("integration.workflows.table.deleteDialog.description") }}
|
|
335
413
|
</span>
|
|
336
414
|
</AlertDialogDescription>
|
|
337
415
|
</AlertDialogHeader>
|
|
338
416
|
<AlertDialogFooter>
|
|
339
417
|
<Button variant="outline" :disabled="deleting" @click="pendingDelete = null">
|
|
340
|
-
|
|
418
|
+
{{ t("integration.common.action.cancel") }}
|
|
341
419
|
</Button>
|
|
342
420
|
<Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="confirmDelete">
|
|
343
421
|
<Loader2 v-if="deleting" :size="14" class="animate-spin" />
|
|
344
|
-
{{ deleting ? "
|
|
422
|
+
{{ deleting ? t("integration.workflows.table.deleteDialog.action.deleting") : t("integration.common.action.delete") }}
|
|
345
423
|
</Button>
|
|
346
424
|
</AlertDialogFooter>
|
|
347
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
|
}
|