@revenexx/studio-integrations 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +29 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -58
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +10 -9
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +18 -17
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -3
- package/dist/runtime/components/Integrations/ExpressionInput.vue +16 -15
- package/dist/runtime/components/Integrations/GroupFrameView.vue +29 -19
- package/dist/runtime/components/Integrations/Layout.vue +28 -3
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +15 -14
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
- package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
- package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
- package/dist/runtime/components/Integrations/QueryBuilder.vue +44 -43
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +36 -12
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +114 -62
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +14 -13
- package/dist/runtime/components/Integrations/RunResponseModal.vue +11 -10
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +0 -1
- package/dist/runtime/components/Integrations/RunsTable.vue +38 -16
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +0 -1
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +8 -7
- package/dist/runtime/components/Integrations/Sidebar.vue +14 -10
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +14 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +37 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +83 -45
- package/dist/runtime/composables/integrationsApi.js +4 -2
- package/dist/runtime/composables/useCredentialTypes.js +4 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +6 -4
- package/dist/runtime/composables/useIntegrationsFocus.d.ts +12 -0
- package/dist/runtime/composables/useIntegrationsLocale.d.ts +26 -0
- package/dist/runtime/composables/useIntegrationsLocale.js +8 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +3 -1
- package/dist/runtime/composables/useIntegrationsRealtime.d.ts +149 -0
- package/dist/runtime/composables/useIntegrationsRealtime.js +80 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +9 -7
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +3 -1
- package/dist/runtime/composables/useIntegrationsSecrets.js +4 -2
- package/dist/runtime/composables/useIntegrationsT.d.ts +35 -0
- package/dist/runtime/composables/useIntegrationsT.js +9 -0
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +3 -3
- package/dist/runtime/composables/useIntegrationsTemplates.js +16 -8
- package/dist/runtime/composables/useIntegrationsTriggers.js +4 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -0
- package/dist/runtime/i18n/translate.d.ts +83 -0
- package/dist/runtime/i18n/translate.js +41 -0
- package/dist/runtime/pages/integrations/audit.vue +52 -34
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +3 -2
- package/dist/runtime/pages/integrations/credentials.vue +70 -52
- package/dist/runtime/pages/integrations/index.vue +50 -56
- package/dist/runtime/pages/integrations/runs.vue +33 -21
- package/dist/runtime/pages/integrations/schedules.vue +49 -28
- package/dist/runtime/pages/integrations/secrets.vue +34 -34
- package/dist/runtime/pages/integrations/templates/[slug].vue +77 -68
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -5
- package/dist/runtime/plugins/locale.d.ts +10 -0
- package/dist/runtime/plugins/locale.js +6 -0
- package/dist/runtime/plugins/realtime.client.d.ts +32 -0
- package/dist/runtime/plugins/realtime.client.js +4 -0
- package/dist/runtime/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +18 -5
- package/dist/runtime/utils/localize.js +32 -9
- package/dist/runtime/utils/pollBudget.d.ts +72 -0
- package/dist/runtime/utils/pollBudget.js +17 -0
- package/dist/runtime/utils/schedule.d.ts +0 -25
- package/dist/runtime/utils/schedule.js +37 -25
- package/dist/runtime/utils/statusStyle.d.ts +26 -2
- package/dist/runtime/utils/statusStyle.js +9 -1
- package/dist/runtime/utils/validationFindings.d.ts +2 -2
- package/dist/runtime/utils/validationFindings.js +4 -3
- package/dist/types.d.mts +2 -6
- package/package.json +3 -2
|
@@ -29,6 +29,7 @@ const props = defineProps({
|
|
|
29
29
|
tagSuggestions: { type: Array, required: false }
|
|
30
30
|
});
|
|
31
31
|
const emit = defineEmits(["update:open", "change-mode", "save-meta"]);
|
|
32
|
+
const t = useIntegrationsT();
|
|
32
33
|
const nameDraft = ref("");
|
|
33
34
|
const descriptionDraft = ref("");
|
|
34
35
|
const tagsDraft = ref([]);
|
|
@@ -54,15 +55,15 @@ function formatDateTime(iso) {
|
|
|
54
55
|
<Dialog :open="open" @update:open="(v) => emit('update:open', v)">
|
|
55
56
|
<DialogContent class="sm:max-w-lg">
|
|
56
57
|
<DialogHeader>
|
|
57
|
-
<DialogTitle>
|
|
58
|
+
<DialogTitle>{{ t("integration.workflows.settings.dialog.title") }}</DialogTitle>
|
|
58
59
|
<DialogDescription class="text-sm">
|
|
59
|
-
|
|
60
|
+
{{ t("integration.workflows.settings.dialog.description", { name: workflow.name }) }}
|
|
60
61
|
</DialogDescription>
|
|
61
62
|
</DialogHeader>
|
|
62
63
|
|
|
63
64
|
<div class="space-y-4 text-sm">
|
|
64
65
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-3">
|
|
65
|
-
<div class="text-mono text-muted-foreground">
|
|
66
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.execution") }}</div>
|
|
66
67
|
<div class="flex items-center gap-2">
|
|
67
68
|
<Select
|
|
68
69
|
:model-value="modeDraft"
|
|
@@ -71,8 +72,8 @@ function formatDateTime(iso) {
|
|
|
71
72
|
>
|
|
72
73
|
<!-- Named explicitly: the section caption above says "Execution",
|
|
73
74
|
which is not a name the control itself carries. -->
|
|
74
|
-
<SelectTrigger aria-label="
|
|
75
|
-
<SelectValue placeholder="
|
|
75
|
+
<SelectTrigger :aria-label="t('integration.workflows.settings.field.executionMode.aria')" class="h-8 w-full bg-white text-sm">
|
|
76
|
+
<SelectValue :placeholder="t('integration.workflows.settings.field.executionMode.placeholder')" />
|
|
76
77
|
</SelectTrigger>
|
|
77
78
|
<SelectContent>
|
|
78
79
|
<SelectItem v-for="m in modes" :key="m.value" :value="m.value">
|
|
@@ -88,24 +89,24 @@ function formatDateTime(iso) {
|
|
|
88
89
|
</div>
|
|
89
90
|
|
|
90
91
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-3">
|
|
91
|
-
<div class="text-mono text-muted-foreground">
|
|
92
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.appearance") }}</div>
|
|
92
93
|
<!-- Real labels, not styled captions: each one names its own control
|
|
93
94
|
(`for`/`id`), otherwise a screen reader reads four unlabelled
|
|
94
95
|
fields in a row. -->
|
|
95
96
|
<div class="space-y-1.5">
|
|
96
|
-
<Label for="wf-set-name" class="text-sm font-normal text-muted-foreground">
|
|
97
|
-
<Input id="wf-set-name" v-model="nameDraft" class="bg-white" placeholder="
|
|
97
|
+
<Label for="wf-set-name" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.name.label") }}</Label>
|
|
98
|
+
<Input id="wf-set-name" v-model="nameDraft" class="bg-white" :placeholder="t('integration.workflows.settings.field.name.placeholder')" />
|
|
98
99
|
</div>
|
|
99
100
|
<div class="space-y-1.5">
|
|
100
|
-
<Label for="wf-set-desc" class="text-sm font-normal text-muted-foreground">
|
|
101
|
-
<Textarea id="wf-set-desc" v-model="descriptionDraft" rows="2" class="bg-white" placeholder="
|
|
101
|
+
<Label for="wf-set-desc" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.description.label") }}</Label>
|
|
102
|
+
<Textarea id="wf-set-desc" v-model="descriptionDraft" rows="2" class="bg-white" :placeholder="t('integration.workflows.settings.field.description.placeholder')" />
|
|
102
103
|
</div>
|
|
103
104
|
<div class="space-y-1.5">
|
|
104
|
-
<Label for="wf-set-tags" class="text-sm font-normal text-muted-foreground">
|
|
105
|
+
<Label for="wf-set-tags" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.tags.label") }}</Label>
|
|
105
106
|
<WorkflowTagsInput v-model="tagsDraft" input-id="wf-set-tags" :suggestions="tagSuggestions ?? []" />
|
|
106
107
|
</div>
|
|
107
108
|
<div class="space-y-1.5">
|
|
108
|
-
<Label id="wf-set-icon-label" class="text-sm font-normal text-muted-foreground">
|
|
109
|
+
<Label id="wf-set-icon-label" class="text-sm font-normal text-muted-foreground">{{ t("integration.workflows.settings.field.icon.label") }}</Label>
|
|
109
110
|
<WorkflowIconPicker v-model="iconDraft" trigger-labelledby="wf-set-icon-label" />
|
|
110
111
|
</div>
|
|
111
112
|
<div class="flex justify-end">
|
|
@@ -116,23 +117,23 @@ function formatDateTime(iso) {
|
|
|
116
117
|
@click="emit('save-meta', { name: nameDraft.trim(), description: descriptionDraft.trim(), tags: tagsDraft, icon: iconDraft })"
|
|
117
118
|
>
|
|
118
119
|
<Loader2 v-if="savingMeta" :size="12" class="animate-spin" />
|
|
119
|
-
|
|
120
|
+
{{ t("integration.workflows.settings.action.save") }}
|
|
120
121
|
</Button>
|
|
121
122
|
</div>
|
|
122
123
|
</div>
|
|
123
124
|
|
|
124
125
|
<div class="rounded-lg border border-black/[0.06] bg-white p-4 space-y-2">
|
|
125
|
-
<div class="text-mono text-muted-foreground">
|
|
126
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.settings.section.metadata") }}</div>
|
|
126
127
|
<div class="grid grid-cols-[110px_1fr] gap-x-3 gap-y-2">
|
|
127
|
-
<span class="text-muted-foreground">
|
|
128
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.id") }}</span>
|
|
128
129
|
<span class="font-mono">{{ workflow.id }}</span>
|
|
129
|
-
<span class="text-muted-foreground">
|
|
130
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.definition") }}</span>
|
|
130
131
|
<span class="font-mono">{{ workflow.blob_definition_version }}</span>
|
|
131
|
-
<span class="text-muted-foreground">
|
|
132
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.revision") }}</span>
|
|
132
133
|
<span class="font-mono">r{{ workflow.revision }}</span>
|
|
133
|
-
<span class="text-muted-foreground">
|
|
134
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.created") }}</span>
|
|
134
135
|
<span>{{ formatDateTime(workflow.created_at) }}</span>
|
|
135
|
-
<span class="text-muted-foreground">
|
|
136
|
+
<span class="text-muted-foreground">{{ t("integration.workflows.settings.meta.updated") }}</span>
|
|
136
137
|
<span>{{ formatDateTime(workflow.updated_at) }}</span>
|
|
137
138
|
</div>
|
|
138
139
|
</div>
|
|
@@ -5,6 +5,7 @@ const props = defineProps({
|
|
|
5
5
|
suggestions: { type: Array, required: false, default: () => [] },
|
|
6
6
|
inputId: { type: String, required: false }
|
|
7
7
|
});
|
|
8
|
+
const t = useIntegrationsT();
|
|
8
9
|
const draft = ref("");
|
|
9
10
|
const inputRef = ref(null);
|
|
10
11
|
const listId = useId();
|
|
@@ -15,7 +16,7 @@ function commit() {
|
|
|
15
16
|
if (!model.value.includes(tag)) model.value = [...model.value, tag];
|
|
16
17
|
}
|
|
17
18
|
function remove(tag) {
|
|
18
|
-
model.value = model.value.filter((
|
|
19
|
+
model.value = model.value.filter((t2) => t2 !== tag);
|
|
19
20
|
}
|
|
20
21
|
function onKeydown(e) {
|
|
21
22
|
if (e.key === "Enter" || e.key === ",") {
|
|
@@ -33,17 +34,17 @@ function onKeydown(e) {
|
|
|
33
34
|
@click="inputRef?.focus()"
|
|
34
35
|
>
|
|
35
36
|
<span
|
|
36
|
-
v-for="
|
|
37
|
-
:key="
|
|
37
|
+
v-for="tag in model"
|
|
38
|
+
:key="tag"
|
|
38
39
|
class="inline-flex items-center gap-1 rounded-md border border-black/[0.06] bg-muted/50 px-1.5 py-0.5 text-xs text-foreground"
|
|
39
40
|
>
|
|
40
|
-
{{
|
|
41
|
+
{{ tag }}
|
|
41
42
|
<button
|
|
42
43
|
type="button"
|
|
43
44
|
class="text-muted-foreground transition hover:text-foreground"
|
|
44
|
-
:title="
|
|
45
|
-
:aria-label="
|
|
46
|
-
@click.stop="remove(
|
|
45
|
+
:title="t('integration.workflows.tagsInput.action.remove', { tag })"
|
|
46
|
+
:aria-label="t('integration.workflows.tagsInput.action.remove', { tag })"
|
|
47
|
+
@click.stop="remove(tag)"
|
|
47
48
|
>
|
|
48
49
|
×
|
|
49
50
|
</button>
|
|
@@ -57,7 +58,7 @@ function onKeydown(e) {
|
|
|
57
58
|
type="text"
|
|
58
59
|
:list="listId"
|
|
59
60
|
class="h-6 min-w-24 flex-1 bg-transparent text-sm focus:outline-none"
|
|
60
|
-
:placeholder="model.length ? '
|
|
61
|
+
:placeholder="model.length ? t('integration.workflows.tagsInput.placeholder.add') : t('integration.workflows.tagsInput.placeholder.addFirst')"
|
|
61
62
|
@keydown="onKeydown"
|
|
62
63
|
@blur="commit()"
|
|
63
64
|
>
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
|
-
import { ClockCircle as Clock, Play, TrashBinMinimalistic as
|
|
5
|
+
import { ClockCircle as Clock, Play, TrashBinMinimalistic as TrashBin, LinkRound as Webhook, Bolt } from "#solar-icons";
|
|
6
6
|
import { resolveDisplayName } from "../../utils/localize";
|
|
7
|
+
import { triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
7
8
|
import { describeCron } from "../../utils/schedule";
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
id: { type: String, required: true },
|
|
@@ -12,19 +13,20 @@ const props = defineProps({
|
|
|
12
13
|
});
|
|
13
14
|
defineOptions({ inheritAttrs: false });
|
|
14
15
|
defineEmits(["updateNodeInternals"]);
|
|
16
|
+
const t = useIntegrationsT();
|
|
15
17
|
const icon = computed(() => {
|
|
16
18
|
return props.data.type === "schedule" ? Clock : props.data.type === "webhook" ? Webhook : props.data.type === "event" ? Bolt : Play;
|
|
17
19
|
});
|
|
18
20
|
const subtitle = computed(() => {
|
|
19
21
|
const c = props.data.config;
|
|
20
|
-
if (props.data.type === "schedule") return describeCron(c.cron) ?? c.cron ?? "
|
|
22
|
+
if (props.data.type === "schedule") return describeCron(c.cron) ?? c.cron ?? t("integration.canvas.trigger.subtitle.noSchedule");
|
|
21
23
|
if (props.data.type === "webhook") return `${c.method ?? "POST"} ${c.path ?? "/"}`;
|
|
22
|
-
if (props.data.type === "event") return c.event_type ?
|
|
23
|
-
return "
|
|
24
|
+
if (props.data.type === "event") return c.event_type ? t("integration.canvas.trigger.subtitle.onEvent", { event: c.event_type }) : t("integration.canvas.trigger.subtitle.noEvent");
|
|
25
|
+
return t("integration.canvas.trigger.subtitle.onDemand");
|
|
24
26
|
});
|
|
25
27
|
const title = computed(() => {
|
|
26
28
|
const n = props.data.name;
|
|
27
|
-
if (!n) return
|
|
29
|
+
if (!n) return triggerTypeLabel(props.data.type);
|
|
28
30
|
return resolveDisplayName(n);
|
|
29
31
|
});
|
|
30
32
|
const hovered = ref(false);
|
|
@@ -54,10 +56,11 @@ function sideToPosition(s) {
|
|
|
54
56
|
<button
|
|
55
57
|
type="button"
|
|
56
58
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-rose-600 hover:bg-rose-50"
|
|
57
|
-
title="
|
|
59
|
+
:title="t('integration.common.action.delete')"
|
|
60
|
+
:aria-label="t('integration.canvas.trigger.action.deleteAria')"
|
|
58
61
|
@click="remove"
|
|
59
62
|
>
|
|
60
|
-
<
|
|
63
|
+
<TrashBin :size="11" />
|
|
61
64
|
</button>
|
|
62
65
|
</NodeToolbar>
|
|
63
66
|
|
|
@@ -86,8 +89,8 @@ function sideToPosition(s) {
|
|
|
86
89
|
</div>
|
|
87
90
|
</div>
|
|
88
91
|
<div class="flex items-center justify-between border-t border-emerald-500/15 bg-emerald-500/[0.04] rounded-b-md px-3 py-1 text-mono text-emerald-700/80 font-semibold">
|
|
89
|
-
<span>
|
|
90
|
-
<span v-if="data.active === false" class="text-amber-600">
|
|
92
|
+
<span>{{ t("integration.canvas.badge.trigger") }} · {{ data.type }}</span>
|
|
93
|
+
<span v-if="data.active === false" class="text-amber-600">{{ t("integration.canvas.trigger.badge.inactive") }}</span>
|
|
91
94
|
</div>
|
|
92
95
|
</div>
|
|
93
96
|
|
|
@@ -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,
|
|
@@ -24,6 +24,7 @@ const props = defineProps({
|
|
|
24
24
|
variables: { type: Object, required: true }
|
|
25
25
|
});
|
|
26
26
|
const emit = defineEmits(["update:open", "update:variables"]);
|
|
27
|
+
const t = useIntegrationsT();
|
|
27
28
|
const KEY_PATTERN = /^[a-z_][a-z0-9_]*$/i;
|
|
28
29
|
const TYPES = ["string", "number", "boolean", "object", "array"];
|
|
29
30
|
const jsonDrafts = reactive({});
|
|
@@ -80,13 +81,13 @@ function applyJsonDraft(key, type) {
|
|
|
80
81
|
const parsed = JSON.parse(raw);
|
|
81
82
|
const ok = type === "array" ? Array.isArray(parsed) : parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
|
|
82
83
|
if (!ok) {
|
|
83
|
-
jsonErrors[key] = type === "array" ? "
|
|
84
|
+
jsonErrors[key] = type === "array" ? t("integration.workflows.variables.error.mustBeArray") : t("integration.workflows.variables.error.mustBeObject");
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
jsonErrors[key] = null;
|
|
87
88
|
patchVar(key, { default: parsed });
|
|
88
89
|
} catch {
|
|
89
|
-
jsonErrors[key] = "
|
|
90
|
+
jsonErrors[key] = t("integration.workflows.variables.error.invalidJson");
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
function descriptionText(v) {
|
|
@@ -107,8 +108,8 @@ const newKey = ref("");
|
|
|
107
108
|
const newKeyError = computed(() => {
|
|
108
109
|
const k = newKey.value.trim();
|
|
109
110
|
if (!k) return null;
|
|
110
|
-
if (!KEY_PATTERN.test(k)) return "
|
|
111
|
-
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");
|
|
112
113
|
return null;
|
|
113
114
|
});
|
|
114
115
|
function addVariable() {
|
|
@@ -120,9 +121,9 @@ function addVariable() {
|
|
|
120
121
|
async function copyUsage(key) {
|
|
121
122
|
try {
|
|
122
123
|
await navigator.clipboard.writeText(`\${{ vars.${key} }}`);
|
|
123
|
-
toast.success("
|
|
124
|
+
toast.success(t("integration.workflows.variables.toast.copied"));
|
|
124
125
|
} catch {
|
|
125
|
-
toast.error("
|
|
126
|
+
toast.error(t("integration.workflows.variables.toast.copyFailed"));
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
</script>
|
|
@@ -131,24 +132,24 @@ async function copyUsage(key) {
|
|
|
131
132
|
<Dialog :open="open" @update:open="(v2) => emit('update:open', v2)">
|
|
132
133
|
<DialogContent class="max-w-[94vw] sm:max-w-3xl">
|
|
133
134
|
<DialogHeader>
|
|
134
|
-
<DialogTitle>
|
|
135
|
+
<DialogTitle>{{ t("integration.workflows.variables.dialog.title") }}</DialogTitle>
|
|
135
136
|
<DialogDescription class="text-sm">
|
|
136
|
-
|
|
137
|
+
{{ t("integration.workflows.variables.dialog.description.before") }}
|
|
137
138
|
<code v-pre class="rounded bg-black/[0.05] px-1 py-0.5 font-mono text-xs">${{ vars.key }}</code>.
|
|
138
|
-
|
|
139
|
+
{{ t("integration.workflows.variables.dialog.description.after") }}
|
|
139
140
|
</DialogDescription>
|
|
140
141
|
</DialogHeader>
|
|
141
142
|
|
|
142
143
|
<div class="max-h-[62vh] space-y-3 overflow-y-auto pr-1">
|
|
143
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">
|
|
144
|
-
|
|
145
|
+
{{ t("integration.workflows.variables.state.empty") }}
|
|
145
146
|
</p>
|
|
146
147
|
|
|
147
148
|
<div
|
|
148
149
|
v-for="[key, v] in entries"
|
|
149
150
|
:key="key"
|
|
150
151
|
role="group"
|
|
151
|
-
:aria-label="
|
|
152
|
+
:aria-label="t('integration.workflows.variables.row.aria', { key })"
|
|
152
153
|
class="rounded-lg border border-black/[0.06] bg-white p-3"
|
|
153
154
|
>
|
|
154
155
|
<div class="flex items-center gap-2">
|
|
@@ -156,7 +157,7 @@ async function copyUsage(key) {
|
|
|
156
157
|
<button
|
|
157
158
|
type="button"
|
|
158
159
|
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
159
|
-
:title="
|
|
160
|
+
:title="t('integration.workflows.variables.action.copyUsage.title', { key })"
|
|
160
161
|
@click="copyUsage(key)"
|
|
161
162
|
>
|
|
162
163
|
<Copy :size="12" />
|
|
@@ -165,10 +166,10 @@ async function copyUsage(key) {
|
|
|
165
166
|
<button
|
|
166
167
|
type="button"
|
|
167
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"
|
|
168
|
-
title="
|
|
169
|
+
:title="t('integration.workflows.variables.action.delete')"
|
|
169
170
|
@click="removeVar(key)"
|
|
170
171
|
>
|
|
171
|
-
<
|
|
172
|
+
<TrashBin :size="13" />
|
|
172
173
|
</button>
|
|
173
174
|
</div>
|
|
174
175
|
|
|
@@ -176,12 +177,12 @@ async function copyUsage(key) {
|
|
|
176
177
|
<!-- One row per variable, so every id carries its key — otherwise
|
|
177
178
|
the labels of the second row point at the first row's fields. -->
|
|
178
179
|
<div class="space-y-1">
|
|
179
|
-
<Label :for="`var-${key}-type`" class="text-mono text-muted-foreground">
|
|
180
|
-
<Select :model-value="v.type" @update:model-value="(
|
|
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)">
|
|
181
182
|
<!-- Both: the `id` lets the visible Label's `for` resolve, and the
|
|
182
183
|
`aria-label` names the trigger without relying on an engine
|
|
183
184
|
naming a button from a <label> — see the sweep's docblock. -->
|
|
184
|
-
<SelectTrigger :id="`var-${key}-type`" aria-label="
|
|
185
|
+
<SelectTrigger :id="`var-${key}-type`" :aria-label="t('integration.workflows.variables.field.type.label')" class="h-8 bg-white text-sm">
|
|
185
186
|
<SelectValue />
|
|
186
187
|
</SelectTrigger>
|
|
187
188
|
<SelectContent>
|
|
@@ -193,7 +194,7 @@ async function copyUsage(key) {
|
|
|
193
194
|
<div class="space-y-1">
|
|
194
195
|
<!-- "Value", not "Default": PO-206. The `for`/`id` still name the
|
|
195
196
|
blob field, which is called `default` and stays that way. -->
|
|
196
|
-
<Label :for="`var-${key}-default`" class="text-mono text-muted-foreground">
|
|
197
|
+
<Label :for="`var-${key}-default`" class="text-mono text-muted-foreground">{{ t("integration.workflows.variables.field.value.label") }}</Label>
|
|
197
198
|
|
|
198
199
|
<!-- Only one of these renders, so they share the one `id` the Label
|
|
199
200
|
points at. The labelable ones take their name from that Label —
|
|
@@ -221,7 +222,7 @@ async function copyUsage(key) {
|
|
|
221
222
|
<Switch
|
|
222
223
|
:id="`var-${key}-default`"
|
|
223
224
|
:model-value="v.default === true"
|
|
224
|
-
aria-label="
|
|
225
|
+
:aria-label="t('integration.workflows.variables.field.value.label')"
|
|
225
226
|
@update:model-value="(val) => patchVar(key, { default: val })"
|
|
226
227
|
/>
|
|
227
228
|
</div>
|
|
@@ -233,7 +234,7 @@ async function copyUsage(key) {
|
|
|
233
234
|
spellcheck="false"
|
|
234
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"
|
|
235
236
|
:class="jsonErrors[key] ? 'border-rose-400' : 'border-black/[0.08]'"
|
|
236
|
-
: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')"
|
|
237
238
|
@input="(e) => jsonDrafts[key] = e.target.value"
|
|
238
239
|
@blur="applyJsonDraft(key, v.type)"
|
|
239
240
|
/>
|
|
@@ -243,12 +244,12 @@ async function copyUsage(key) {
|
|
|
243
244
|
</div>
|
|
244
245
|
|
|
245
246
|
<div class="mt-2 space-y-1">
|
|
246
|
-
<Label :for="`var-${key}-desc`" 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>
|
|
247
248
|
<Input
|
|
248
249
|
:id="`var-${key}-desc`"
|
|
249
250
|
:model-value="descriptionText(v)"
|
|
250
251
|
class="h-8 bg-white text-sm"
|
|
251
|
-
placeholder="
|
|
252
|
+
:placeholder="t('integration.workflows.variables.field.description.placeholder')"
|
|
252
253
|
@update:model-value="(val) => writeDescription(key, String(val))"
|
|
253
254
|
/>
|
|
254
255
|
</div>
|
|
@@ -256,18 +257,18 @@ async function copyUsage(key) {
|
|
|
256
257
|
|
|
257
258
|
<!-- Add row -->
|
|
258
259
|
<div class="rounded-lg border border-dashed border-black/[0.12] p-3">
|
|
259
|
-
<Label for="var-new-key" 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>
|
|
260
261
|
<div class="mt-1 flex items-center gap-2">
|
|
261
262
|
<Input
|
|
262
263
|
id="var-new-key"
|
|
263
264
|
v-model="newKey"
|
|
264
265
|
class="h-8 flex-1 bg-white font-mono text-sm"
|
|
265
|
-
placeholder="
|
|
266
|
+
:placeholder="t('integration.workflows.variables.field.newKey.placeholder')"
|
|
266
267
|
@keydown.enter.prevent="addVariable"
|
|
267
268
|
/>
|
|
268
269
|
<Button size="sm" class="h-8" :disabled="!newKey.trim() || !!newKeyError" @click="addVariable">
|
|
269
270
|
<Plus :size="14" class="mr-1" />
|
|
270
|
-
|
|
271
|
+
{{ t("integration.workflows.variables.action.add") }}
|
|
271
272
|
</Button>
|
|
272
273
|
</div>
|
|
273
274
|
<p v-if="newKeyError" class="mt-1 text-xs text-rose-600">{{ newKeyError }}</p>
|