@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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, nextTick, onMounted, provide, ref, watch } from "vue";
|
|
3
|
-
import { AddCircle as Plus,
|
|
4
|
-
import {
|
|
3
|
+
import { AddCircle as Plus, Copy, Magnifier as Search, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
|
+
import { triggerGlyph as glyphForTrigger } from "../../data/triggerGlyphs";
|
|
5
|
+
import { ChevronDown, ChevronLeft, ChevronRight, Database, MessageSquareText, X } from "lucide-vue-next";
|
|
5
6
|
import { toast } from "vue-sonner";
|
|
6
7
|
import {
|
|
7
8
|
Button,
|
|
@@ -27,9 +28,10 @@ import {
|
|
|
27
28
|
ToggleGroupItem
|
|
28
29
|
} from "@revenexx/studio";
|
|
29
30
|
import EventTypeCombobox from "./EventTypeCombobox.vue";
|
|
30
|
-
import {
|
|
31
|
+
import { findManifest, nodeCategoryLabel, isVersionExecutable, newestExecutableVersion } from "../../data/integrationNodeCatalog";
|
|
31
32
|
import {
|
|
32
33
|
collectUpstreamNodes,
|
|
34
|
+
inputPortsForManifest,
|
|
33
35
|
nodeOutputExpr,
|
|
34
36
|
portsForNode,
|
|
35
37
|
triggerPayloadExpr,
|
|
@@ -45,6 +47,7 @@ import {
|
|
|
45
47
|
} from "../../data/nodeRunData";
|
|
46
48
|
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
47
49
|
import { describeRecurrence, parseCron, scheduleTimezone, toCron } from "../../utils/schedule";
|
|
50
|
+
const t = useIntegrationsT();
|
|
48
51
|
const props = defineProps({
|
|
49
52
|
selection: { type: [Object, null], required: true },
|
|
50
53
|
secretKeys: { type: Array, required: true },
|
|
@@ -86,35 +89,48 @@ const manifest = computed(() => {
|
|
|
86
89
|
const wn = draftNode.value;
|
|
87
90
|
return wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
88
91
|
});
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
+
const versionExecutable = computed(() => {
|
|
93
|
+
const wn = draftNode.value;
|
|
94
|
+
return wn ? isVersionExecutable(wn.nodeSlug, wn.nodeVersion) : true;
|
|
95
|
+
});
|
|
96
|
+
const readyVersion = computed(() => {
|
|
97
|
+
const wn = draftNode.value;
|
|
98
|
+
return wn ? newestExecutableVersion(wn.nodeSlug) : null;
|
|
99
|
+
});
|
|
100
|
+
const TRIGGER_LABELS = computed(() => ({
|
|
101
|
+
manual: t("integration.nodeInspector.trigger.type.manual"),
|
|
102
|
+
schedule: t("integration.nodeInspector.trigger.type.schedule"),
|
|
103
|
+
webhook: t("integration.nodeInspector.trigger.type.webhook"),
|
|
104
|
+
event: t("integration.nodeInspector.trigger.type.event")
|
|
105
|
+
}));
|
|
106
|
+
const triggerGlyph = computed(() => glyphForTrigger(draftTrigger.value?.type));
|
|
92
107
|
const namePlaceholder = computed(() => {
|
|
93
108
|
if (draftNode.value) return resolveLocalized(manifest.value?.name) || draftNode.value.nodeSlug;
|
|
94
|
-
return TRIGGER_LABELS[draftTrigger.value?.type ?? "manual"]
|
|
109
|
+
return t("integration.nodeInspector.trigger.placeholder", { type: TRIGGER_LABELS.value[draftTrigger.value?.type ?? "manual"] });
|
|
95
110
|
});
|
|
96
111
|
const subtitleText = computed(() => {
|
|
97
112
|
const wn = draftNode.value;
|
|
98
113
|
if (wn) return wn.nodeSlug + "@" + wn.nodeVersion;
|
|
99
|
-
return TRIGGER_LABELS[draftTrigger.value?.type ?? "manual"]
|
|
114
|
+
return t("integration.nodeInspector.trigger.placeholder", { type: TRIGGER_LABELS.value[draftTrigger.value?.type ?? "manual"] });
|
|
100
115
|
});
|
|
101
116
|
const manifestDescription = computed(() => resolveLocalized(manifest.value?.description));
|
|
102
117
|
const groupLabel = computed(() => {
|
|
103
|
-
if (!isNode.value) return "
|
|
118
|
+
if (!isNode.value) return t("integration.nodeInspector.header.groupTrigger");
|
|
104
119
|
const groups = manifest.value?.groups;
|
|
105
120
|
if (groups?.length) return resolveLocalized(groups[groups.length - 1]);
|
|
106
|
-
return
|
|
121
|
+
return nodeCategoryLabel(manifest.value?.category) || t("integration.nodeInspector.header.groupNode");
|
|
107
122
|
});
|
|
108
123
|
const nameValue = computed(() => {
|
|
109
124
|
const name = draft.value.name;
|
|
110
125
|
return resolveDisplayName(name);
|
|
111
126
|
});
|
|
127
|
+
const dialogTitle = computed(() => t("integration.nodeInspector.dialogTitle", { name: nameValue.value || namePlaceholder.value }));
|
|
112
128
|
function writeName(name) {
|
|
113
129
|
draft.value = { ...draft.value, name };
|
|
114
130
|
}
|
|
115
131
|
const noteOpen = ref(false);
|
|
116
132
|
const noteText = computed(() => (draftNode.value?.notes ?? "").trim());
|
|
117
|
-
const noteLabel = computed(() => noteText.value ? "
|
|
133
|
+
const noteLabel = computed(() => noteText.value ? t("integration.nodeInspector.note.label") : t("integration.nodeInspector.note.addLabel"));
|
|
118
134
|
function writeNote(v) {
|
|
119
135
|
const text = String(v ?? "");
|
|
120
136
|
patchNode({ notes: text.trim() ? text : void 0 });
|
|
@@ -122,8 +138,11 @@ function writeNote(v) {
|
|
|
122
138
|
const tab = ref("parameters");
|
|
123
139
|
const showSettingsTab = computed(() => isNode.value);
|
|
124
140
|
const inspectorTabs = computed(() => [
|
|
125
|
-
{ key: "parameters", label: "
|
|
126
|
-
...showSettingsTab.value ? [
|
|
141
|
+
{ key: "parameters", label: t("integration.nodeInspector.tab.parameters") },
|
|
142
|
+
...showSettingsTab.value ? [
|
|
143
|
+
{ key: "settings", label: t("integration.nodeInspector.tab.settings") },
|
|
144
|
+
{ key: "output", label: t("integration.nodeInspector.tab.output") }
|
|
145
|
+
] : []
|
|
127
146
|
]);
|
|
128
147
|
watch(showSettingsTab, (hasThem) => {
|
|
129
148
|
if (!hasThem) tab.value = "parameters";
|
|
@@ -184,9 +203,9 @@ async function copyConfig() {
|
|
|
184
203
|
if (!draftNode.value) return;
|
|
185
204
|
try {
|
|
186
205
|
await navigator.clipboard.writeText(JSON.stringify(draftNode.value.config ?? {}, null, 2));
|
|
187
|
-
toast.success("
|
|
206
|
+
toast.success(t("integration.nodeInspector.toast.configCopied"));
|
|
188
207
|
} catch {
|
|
189
|
-
toast.error("
|
|
208
|
+
toast.error(t("integration.nodeInspector.toast.configCopyFailed"));
|
|
190
209
|
}
|
|
191
210
|
}
|
|
192
211
|
async function pasteConfig() {
|
|
@@ -195,22 +214,22 @@ async function pasteConfig() {
|
|
|
195
214
|
try {
|
|
196
215
|
text = await navigator.clipboard.readText();
|
|
197
216
|
} catch {
|
|
198
|
-
toast.error("
|
|
217
|
+
toast.error(t("integration.nodeInspector.toast.clipboardReadFailed"));
|
|
199
218
|
return;
|
|
200
219
|
}
|
|
201
220
|
let parsed;
|
|
202
221
|
try {
|
|
203
222
|
parsed = JSON.parse(text);
|
|
204
223
|
} catch {
|
|
205
|
-
toast.error("
|
|
224
|
+
toast.error(t("integration.nodeInspector.toast.clipboardInvalidJson"));
|
|
206
225
|
return;
|
|
207
226
|
}
|
|
208
227
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
209
|
-
toast.error("
|
|
228
|
+
toast.error(t("integration.nodeInspector.toast.clipboardNotConfigObject"));
|
|
210
229
|
return;
|
|
211
230
|
}
|
|
212
231
|
patchNode({ config: parsed });
|
|
213
|
-
toast.success("
|
|
232
|
+
toast.success(t("integration.nodeInspector.toast.configPasted"));
|
|
214
233
|
}
|
|
215
234
|
function readTriggerConfig(key) {
|
|
216
235
|
return draftTrigger.value?.config[key];
|
|
@@ -223,24 +242,24 @@ function writeTriggerConfig(key, value) {
|
|
|
223
242
|
const MINUTE_INTERVALS = [1, 2, 5, 10, 15, 20, 30];
|
|
224
243
|
const HOUR_INTERVALS = [2, 3, 4, 6, 8, 12];
|
|
225
244
|
const MINUTES_PAST = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
|
|
226
|
-
const DAY_OPTIONS = [
|
|
227
|
-
{ value: 1, short: "
|
|
228
|
-
{ value: 2, short: "
|
|
229
|
-
{ value: 3, short: "
|
|
230
|
-
{ value: 4, short: "
|
|
231
|
-
{ value: 5, short: "
|
|
232
|
-
{ value: 6, short: "
|
|
233
|
-
{ value: 0, short: "
|
|
234
|
-
];
|
|
235
|
-
const FREQUENCY_OPTIONS = [
|
|
236
|
-
{ value: "minutes", label: "
|
|
237
|
-
{ value: "hourly", label: "
|
|
238
|
-
{ value: "hours", label: "
|
|
239
|
-
{ value: "times", label: "
|
|
240
|
-
{ value: "daily", label: "
|
|
241
|
-
{ value: "weekly", label: "
|
|
242
|
-
{ value: "monthly", label: "
|
|
243
|
-
];
|
|
245
|
+
const DAY_OPTIONS = computed(() => [
|
|
246
|
+
{ value: 1, short: t("integration.nodeInspector.schedule.day.monday.short"), name: t("integration.nodeInspector.schedule.day.monday") },
|
|
247
|
+
{ value: 2, short: t("integration.nodeInspector.schedule.day.tuesday.short"), name: t("integration.nodeInspector.schedule.day.tuesday") },
|
|
248
|
+
{ value: 3, short: t("integration.nodeInspector.schedule.day.wednesday.short"), name: t("integration.nodeInspector.schedule.day.wednesday") },
|
|
249
|
+
{ value: 4, short: t("integration.nodeInspector.schedule.day.thursday.short"), name: t("integration.nodeInspector.schedule.day.thursday") },
|
|
250
|
+
{ value: 5, short: t("integration.nodeInspector.schedule.day.friday.short"), name: t("integration.nodeInspector.schedule.day.friday") },
|
|
251
|
+
{ value: 6, short: t("integration.nodeInspector.schedule.day.saturday.short"), name: t("integration.nodeInspector.schedule.day.saturday") },
|
|
252
|
+
{ value: 0, short: t("integration.nodeInspector.schedule.day.sunday.short"), name: t("integration.nodeInspector.schedule.day.sunday") }
|
|
253
|
+
]);
|
|
254
|
+
const FREQUENCY_OPTIONS = computed(() => [
|
|
255
|
+
{ value: "minutes", label: t("integration.nodeInspector.schedule.frequency.minutes") },
|
|
256
|
+
{ value: "hourly", label: t("integration.nodeInspector.schedule.frequency.hourly") },
|
|
257
|
+
{ value: "hours", label: t("integration.nodeInspector.schedule.frequency.hours") },
|
|
258
|
+
{ value: "times", label: t("integration.nodeInspector.schedule.frequency.times") },
|
|
259
|
+
{ value: "daily", label: t("integration.nodeInspector.schedule.frequency.daily") },
|
|
260
|
+
{ value: "weekly", label: t("integration.nodeInspector.schedule.frequency.weekly") },
|
|
261
|
+
{ value: "monthly", label: t("integration.nodeInspector.schedule.frequency.monthly") }
|
|
262
|
+
]);
|
|
244
263
|
const HOURS_OF_DAY = Array.from({ length: 24 }, (_, h) => h);
|
|
245
264
|
const FREQUENCY_ADVANCED = "advanced";
|
|
246
265
|
const cronValue = computed(() => readTriggerConfig("cron") ?? "");
|
|
@@ -416,10 +435,10 @@ async function rotateEndpointSecret() {
|
|
|
416
435
|
rotatedSecret.value = secret;
|
|
417
436
|
rotateOpen.value = true;
|
|
418
437
|
} else {
|
|
419
|
-
toast.error("
|
|
438
|
+
toast.error(t("integration.nodeInspector.toast.rotateNoSecret"));
|
|
420
439
|
}
|
|
421
440
|
} catch (e) {
|
|
422
|
-
toast.error(e.message || "
|
|
441
|
+
toast.error(e.message || t("integration.nodeInspector.toast.rotateFailed"));
|
|
423
442
|
} finally {
|
|
424
443
|
rotateBusy.value = false;
|
|
425
444
|
}
|
|
@@ -428,25 +447,25 @@ async function copySecret() {
|
|
|
428
447
|
if (!rotatedSecret.value) return;
|
|
429
448
|
try {
|
|
430
449
|
await navigator.clipboard.writeText(rotatedSecret.value);
|
|
431
|
-
toast.success("
|
|
450
|
+
toast.success(t("integration.nodeInspector.toast.secretCopied"));
|
|
432
451
|
} catch {
|
|
433
|
-
toast.error("
|
|
452
|
+
toast.error(t("integration.nodeInspector.toast.secretCopyFailed"));
|
|
434
453
|
}
|
|
435
454
|
}
|
|
436
455
|
async function copyWebhookUrl() {
|
|
437
456
|
if (!webhookUrl.value) return;
|
|
438
457
|
try {
|
|
439
458
|
await navigator.clipboard.writeText(webhookUrl.value);
|
|
440
|
-
toast.success("
|
|
459
|
+
toast.success(t("integration.nodeInspector.toast.webhookUrlCopied"));
|
|
441
460
|
} catch {
|
|
442
|
-
toast.error("
|
|
461
|
+
toast.error(t("integration.nodeInspector.toast.webhookUrlCopyFailed"));
|
|
443
462
|
}
|
|
444
463
|
}
|
|
445
464
|
const definedEvents = typeof useDefinedEvents === "function" ? useDefinedEvents() : { emits: ref([]), load: async () => {
|
|
446
465
|
} };
|
|
447
466
|
const eventTopic = computed(() => {
|
|
448
|
-
const
|
|
449
|
-
return typeof
|
|
467
|
+
const raw = readTriggerConfig("event_type");
|
|
468
|
+
return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : null;
|
|
450
469
|
});
|
|
451
470
|
watch(eventTopic, (topic) => {
|
|
452
471
|
if (topic) void definedEvents.load();
|
|
@@ -490,7 +509,7 @@ async function loadRunData() {
|
|
|
490
509
|
const { runs } = await loadForWorkflow(Number(props.workflowId), "", { perPage: 10 });
|
|
491
510
|
const source = runs.find((r) => r.status === "succeeded") ?? runs[0];
|
|
492
511
|
if (!source) {
|
|
493
|
-
runError.value = "
|
|
512
|
+
runError.value = t("integration.nodeInspector.input.runData.empty");
|
|
494
513
|
return;
|
|
495
514
|
}
|
|
496
515
|
const res = await getSteps(props.workflowId, source.id, true);
|
|
@@ -505,7 +524,7 @@ async function loadRunData() {
|
|
|
505
524
|
runData.value = map;
|
|
506
525
|
runInfo.value = { id: source.id, startedAt: source.startedAt };
|
|
507
526
|
} catch (e) {
|
|
508
|
-
runError.value = e?.message ?? "
|
|
527
|
+
runError.value = e?.message ?? t("integration.nodeInspector.input.runData.loadFailed");
|
|
509
528
|
} finally {
|
|
510
529
|
runLoading.value = false;
|
|
511
530
|
}
|
|
@@ -589,19 +608,19 @@ const inputPaneOpen = ref(false);
|
|
|
589
608
|
const exprInsertHandler = ref(null);
|
|
590
609
|
provide("integrationsExpressionInsert", exprInsertHandler);
|
|
591
610
|
function onCenterFocusIn(e) {
|
|
592
|
-
const
|
|
593
|
-
if (
|
|
611
|
+
const target = e.target;
|
|
612
|
+
if (target instanceof HTMLElement && target.closest(".expr-editor")) {
|
|
594
613
|
lastFocusKind.value = "editor";
|
|
595
614
|
inputPaneOpen.value = true;
|
|
596
615
|
return;
|
|
597
616
|
}
|
|
598
|
-
if (
|
|
599
|
-
lastField.value =
|
|
617
|
+
if (target instanceof HTMLTextAreaElement) {
|
|
618
|
+
lastField.value = target;
|
|
600
619
|
lastFocusKind.value = "native";
|
|
601
620
|
return;
|
|
602
621
|
}
|
|
603
|
-
if (
|
|
604
|
-
lastField.value =
|
|
622
|
+
if (target instanceof HTMLInputElement && !["number", "checkbox", "radio", "range", "file", "color"].includes(target.type)) {
|
|
623
|
+
lastField.value = target;
|
|
605
624
|
lastFocusKind.value = "native";
|
|
606
625
|
}
|
|
607
626
|
}
|
|
@@ -623,7 +642,7 @@ function insertExpression(expression) {
|
|
|
623
642
|
void navigator.clipboard?.writeText(expression);
|
|
624
643
|
} catch {
|
|
625
644
|
}
|
|
626
|
-
toast("
|
|
645
|
+
toast(t("integration.nodeInspector.toast.expressionCopied"));
|
|
627
646
|
}
|
|
628
647
|
const chipClass = "max-w-full cursor-grab truncate rounded-md border border-black/[0.06] bg-slate-50 px-1.5 py-0.5 font-mono text-[11px] text-slate-700 transition hover:border-black/[0.12] hover:bg-slate-100 active:cursor-grabbing";
|
|
629
648
|
const rowClass = "flex w-full min-w-0 cursor-grab items-center gap-1.5 rounded-md px-1.5 py-[3px] text-left font-mono text-[11px] text-slate-700 transition hover:bg-slate-100 active:cursor-grabbing";
|
|
@@ -642,10 +661,10 @@ function onChipDragEnd() {
|
|
|
642
661
|
dragActive.value = false;
|
|
643
662
|
clearDropHover();
|
|
644
663
|
}
|
|
645
|
-
function expressionTarget(
|
|
646
|
-
if (
|
|
647
|
-
if (
|
|
648
|
-
if (
|
|
664
|
+
function expressionTarget(t2) {
|
|
665
|
+
if (t2 instanceof HTMLTextAreaElement) return t2;
|
|
666
|
+
if (t2 instanceof HTMLInputElement && !["number", "checkbox", "radio", "range", "file", "color"].includes(t2.type)) return t2;
|
|
667
|
+
if (t2 instanceof HTMLElement) return t2.closest(".expr-editor");
|
|
649
668
|
return null;
|
|
650
669
|
}
|
|
651
670
|
function onCenterDragEnter(e) {
|
|
@@ -737,7 +756,7 @@ function typeGlyph(dataType) {
|
|
|
737
756
|
return dataType ? TYPE_GLYPHS[dataType] ?? null : null;
|
|
738
757
|
}
|
|
739
758
|
const sequence = computed(() => [
|
|
740
|
-
...(props.triggers ?? []).map((
|
|
759
|
+
...(props.triggers ?? []).map((t2) => t2.id),
|
|
741
760
|
...(props.nodes ?? []).map((n) => n.id)
|
|
742
761
|
]);
|
|
743
762
|
const seqIndex = computed(() => sequence.value.indexOf(draft.value.id));
|
|
@@ -750,6 +769,7 @@ function navigate(offset) {
|
|
|
750
769
|
emit("navigate", target);
|
|
751
770
|
}
|
|
752
771
|
const outputView = ref("schema");
|
|
772
|
+
const selfInputs = computed(() => inputPortsForManifest(manifest.value));
|
|
753
773
|
const selfSample = computed(() => draftNode.value ? sampleFor(draftNode.value.id) : void 0);
|
|
754
774
|
const selfPorts = computed(() => draftNode.value ? withSampledFields(portsForNode(draftNode.value, manifest.value, props.dynamicOutputs?.[draftNode.value.id]), selfSample.value) : []);
|
|
755
775
|
const selfSampleJson = computed(() => selfSample.value ? JSON.stringify(selfSample.value, null, 2) : "");
|
|
@@ -771,17 +791,17 @@ function savePin() {
|
|
|
771
791
|
try {
|
|
772
792
|
parsed = JSON.parse(pinDraft.value);
|
|
773
793
|
} catch {
|
|
774
|
-
pinError.value = "
|
|
794
|
+
pinError.value = t("integration.nodeInspector.output.pin.invalidJson");
|
|
775
795
|
return;
|
|
776
796
|
}
|
|
777
797
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
778
|
-
pinError.value =
|
|
798
|
+
pinError.value = t("integration.nodeInspector.output.pin.invalidShape");
|
|
779
799
|
return;
|
|
780
800
|
}
|
|
781
801
|
pinnedSamples.value = { ...pinnedSamples.value, [draftNode.value.id]: parsed };
|
|
782
802
|
persistPins();
|
|
783
803
|
pinEditorOpen.value = false;
|
|
784
|
-
toast.success("
|
|
804
|
+
toast.success(t("integration.nodeInspector.toast.samplePinned"));
|
|
785
805
|
}
|
|
786
806
|
function removePin() {
|
|
787
807
|
if (!draftNode.value) return;
|
|
@@ -820,12 +840,12 @@ async function runTestStep() {
|
|
|
820
840
|
try {
|
|
821
841
|
const parsed = JSON.parse(testInputDraft.value.trim() || defaultTestInput());
|
|
822
842
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
823
|
-
testError.value = "
|
|
843
|
+
testError.value = t("integration.nodeInspector.output.test.inputNotObject");
|
|
824
844
|
return;
|
|
825
845
|
}
|
|
826
846
|
inputs = parsed;
|
|
827
847
|
} catch {
|
|
828
|
-
testError.value = "
|
|
848
|
+
testError.value = t("integration.nodeInspector.output.test.inputInvalidJson");
|
|
829
849
|
testInputOpen.value = true;
|
|
830
850
|
return;
|
|
831
851
|
}
|
|
@@ -843,10 +863,10 @@ async function runTestStep() {
|
|
|
843
863
|
[wn.id]: res.outputs && typeof res.outputs === "object" ? res.outputs : {}
|
|
844
864
|
};
|
|
845
865
|
outputView.value = "json";
|
|
846
|
-
toast.success("
|
|
866
|
+
toast.success(t("integration.nodeInspector.toast.testRunFinished"));
|
|
847
867
|
} catch (e) {
|
|
848
868
|
const body = e.body;
|
|
849
|
-
testError.value = body?.error?.message ?? body?.message ?? e?.message ?? "
|
|
869
|
+
testError.value = body?.error?.message ?? body?.message ?? e?.message ?? t("integration.nodeInspector.output.test.runFailed");
|
|
850
870
|
testLogs.value = body?.logs ?? [];
|
|
851
871
|
} finally {
|
|
852
872
|
testRunning.value = false;
|
|
@@ -858,6 +878,11 @@ async function runTestStep() {
|
|
|
858
878
|
<!-- Fills the editor's near-fullscreen config lightbox; the Dialog owns
|
|
859
879
|
width/height, border and the close control. -->
|
|
860
880
|
<div class="flex min-h-0 flex-1 flex-col bg-white">
|
|
881
|
+
<!-- The dialog's accessible name. Not drawn: the header below says the same
|
|
882
|
+
thing in the name field and the line under it, and says it better. -->
|
|
883
|
+
<DialogTitle class="sr-only">
|
|
884
|
+
{{ dialogTitle }}
|
|
885
|
+
</DialogTitle>
|
|
861
886
|
<!-- Header -->
|
|
862
887
|
<div class="flex shrink-0 items-center gap-3 border-b border-black/[0.06] px-4 py-3">
|
|
863
888
|
<div class="flex size-9 shrink-0 items-center justify-center rounded-md border border-black/[0.06] bg-slate-50 text-slate-600">
|
|
@@ -870,7 +895,7 @@ async function runTestStep() {
|
|
|
870
895
|
type, not for the field. -->
|
|
871
896
|
<input
|
|
872
897
|
ref="nameInput"
|
|
873
|
-
aria-label="
|
|
898
|
+
:aria-label="t('integration.nodeInspector.header.nameAria')"
|
|
874
899
|
:value="nameValue"
|
|
875
900
|
:placeholder="namePlaceholder"
|
|
876
901
|
class="w-full truncate rounded-sm bg-transparent text-sm font-semibold text-foreground outline-none placeholder:text-muted-foreground/60 focus:ring-2 focus:ring-primary/40"
|
|
@@ -895,13 +920,13 @@ async function runTestStep() {
|
|
|
895
920
|
the description at all. -->
|
|
896
921
|
<button
|
|
897
922
|
type="button"
|
|
898
|
-
aria-label="
|
|
923
|
+
:aria-label="t('integration.nodeInspector.header.whatThisDoes')"
|
|
899
924
|
class="relative flex size-4 shrink-0 items-center justify-center rounded-full border border-black/[0.12] text-[10px] font-semibold leading-none transition after:absolute after:-inset-1 after:content-[''] hover:border-black/25 hover:bg-black/[0.04] hover:text-foreground"
|
|
900
925
|
>
|
|
901
926
|
?
|
|
902
927
|
</button>
|
|
903
928
|
</PopoverTrigger>
|
|
904
|
-
<PopoverContent aria-label="
|
|
929
|
+
<PopoverContent :aria-label="t('integration.nodeInspector.header.whatThisDoes')" align="start" class="w-96 rounded-lg border border-black/[0.06] bg-white p-4 shadow-lg">
|
|
905
930
|
<div class="flex items-center gap-2.5">
|
|
906
931
|
<span class="flex size-8 shrink-0 items-center justify-center rounded-md border border-black/[0.06] bg-slate-50 text-slate-600">
|
|
907
932
|
<IntegrationsNodeMark :manifest="manifest" :size="18" />
|
|
@@ -943,13 +968,13 @@ async function runTestStep() {
|
|
|
943
968
|
{{ noteLabel }}
|
|
944
969
|
</button>
|
|
945
970
|
</PopoverTrigger>
|
|
946
|
-
<PopoverContent aria-label="
|
|
947
|
-
<Label class="text-mono text-muted-foreground">
|
|
971
|
+
<PopoverContent :aria-label="t('integration.nodeInspector.note.label')" align="end" class="w-80 space-y-1.5 rounded-lg border border-black/[0.06] bg-white p-3 shadow-lg">
|
|
972
|
+
<Label class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.note.label") }}</Label>
|
|
948
973
|
<Textarea
|
|
949
|
-
aria-label="
|
|
974
|
+
:aria-label="t('integration.nodeInspector.note.label')"
|
|
950
975
|
:model-value="draftNode.notes ?? ''"
|
|
951
976
|
:rows="4"
|
|
952
|
-
placeholder="
|
|
977
|
+
:placeholder="t('integration.nodeInspector.note.placeholder')"
|
|
953
978
|
class="rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white"
|
|
954
979
|
@update:model-value="writeNote"
|
|
955
980
|
/>
|
|
@@ -972,7 +997,7 @@ async function runTestStep() {
|
|
|
972
997
|
@click="inputPaneOpen = !inputPaneOpen"
|
|
973
998
|
>
|
|
974
999
|
<Database :size="12" />
|
|
975
|
-
|
|
1000
|
+
{{ t("integration.nodeInspector.header.inputData") }}
|
|
976
1001
|
</button>
|
|
977
1002
|
<!-- Icon-only controls: `title` alone leaves them nameless to a screen
|
|
978
1003
|
reader (and unaddressable to a test without a CSS selector), so each
|
|
@@ -981,8 +1006,8 @@ async function runTestStep() {
|
|
|
981
1006
|
<button
|
|
982
1007
|
type="button"
|
|
983
1008
|
:disabled="!hasPrev"
|
|
984
|
-
aria-label="
|
|
985
|
-
title="
|
|
1009
|
+
:aria-label="t('integration.nodeInspector.header.previousStep')"
|
|
1010
|
+
:title="t('integration.nodeInspector.header.previousStep')"
|
|
986
1011
|
class="flex size-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-black/[0.04] hover:text-foreground disabled:pointer-events-none disabled:opacity-30"
|
|
987
1012
|
@click="navigate(-1)"
|
|
988
1013
|
>
|
|
@@ -991,8 +1016,8 @@ async function runTestStep() {
|
|
|
991
1016
|
<button
|
|
992
1017
|
type="button"
|
|
993
1018
|
:disabled="!hasNext"
|
|
994
|
-
aria-label="
|
|
995
|
-
title="
|
|
1019
|
+
:aria-label="t('integration.nodeInspector.header.nextStep')"
|
|
1020
|
+
:title="t('integration.nodeInspector.header.nextStep')"
|
|
996
1021
|
class="flex size-7 items-center justify-center rounded-md text-muted-foreground transition hover:bg-black/[0.04] hover:text-foreground disabled:pointer-events-none disabled:opacity-30"
|
|
997
1022
|
@click="navigate(1)"
|
|
998
1023
|
>
|
|
@@ -1003,8 +1028,8 @@ async function runTestStep() {
|
|
|
1003
1028
|
(the DialogContent built-in X floats at its own offset). -->
|
|
1004
1029
|
<button
|
|
1005
1030
|
type="button"
|
|
1006
|
-
aria-label="
|
|
1007
|
-
title="
|
|
1031
|
+
:aria-label="t('integration.common.action.close')"
|
|
1032
|
+
:title="t('integration.common.action.close')"
|
|
1008
1033
|
class="-mr-1.5 flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-black/[0.04] hover:text-foreground"
|
|
1009
1034
|
@click="emit('close')"
|
|
1010
1035
|
>
|
|
@@ -1018,18 +1043,48 @@ async function runTestStep() {
|
|
|
1018
1043
|
<aside v-if="inputPaneOpen" class="flex w-[300px] shrink-0 flex-col border-r border-black/[0.06] bg-muted/10" @dragend="onChipDragEnd">
|
|
1019
1044
|
<div class="shrink-0 border-b border-black/[0.06] px-3 py-2.5">
|
|
1020
1045
|
<div class="text-mono text-muted-foreground">
|
|
1021
|
-
|
|
1046
|
+
{{ t("integration.nodeInspector.input.title") }}
|
|
1022
1047
|
</div>
|
|
1023
1048
|
<p class="mt-0.5 text-xs leading-snug text-muted-foreground">
|
|
1024
|
-
|
|
1049
|
+
{{ t("integration.nodeInspector.input.description") }}
|
|
1025
1050
|
</p>
|
|
1051
|
+
<!-- What this node accepts, as opposed to the list below, which is what
|
|
1052
|
+
other nodes offer it. It sits in the header because somebody
|
|
1053
|
+
choosing a value is already reading here, and because the port
|
|
1054
|
+
belongs to the node being configured rather than to any row in the
|
|
1055
|
+
list (AC-20). -->
|
|
1056
|
+
<div v-if="isNode && selfInputs.length" class="mt-2 space-y-1 border-t border-black/[0.06] pt-2">
|
|
1057
|
+
<div v-for="p in selfInputs" :key="p.name" class="space-y-0.5">
|
|
1058
|
+
<div class="flex items-baseline gap-1.5">
|
|
1059
|
+
<!-- Not the uppercase treatment the section labels below use, and
|
|
1060
|
+
that is the point: NODES / TRIGGER / VARIABLES each head a list
|
|
1061
|
+
of values a click inserts, so they are a series of *sources*.
|
|
1062
|
+
This heads nothing and is not a source — it is a statement about
|
|
1063
|
+
the node being configured. Dressed as a fourth member of that
|
|
1064
|
+
series it told the reader there was a fourth place to take data
|
|
1065
|
+
from. -->
|
|
1066
|
+
<span
|
|
1067
|
+
class="text-xs font-medium text-muted-foreground"
|
|
1068
|
+
:class="selfInputs.length > 1 ? 'font-mono' : ''"
|
|
1069
|
+
>
|
|
1070
|
+
{{ selfInputs.length > 1 ? `${p.name}:` : t("integration.nodeInspector.input.expects.heading") }}
|
|
1071
|
+
</span>
|
|
1072
|
+
<span class="font-mono text-[10px] text-slate-500">
|
|
1073
|
+
{{ p.dataType }}<template v-if="p.dataType"> · </template>{{ p.required ? t("integration.nodeInspector.input.expects.required") : t("integration.nodeInspector.input.expects.optional") }}
|
|
1074
|
+
</span>
|
|
1075
|
+
</div>
|
|
1076
|
+
<p v-if="resolveLocalized(p.description)" class="text-xs leading-snug text-muted-foreground">
|
|
1077
|
+
{{ resolveLocalized(p.description) }}
|
|
1078
|
+
</p>
|
|
1079
|
+
</div>
|
|
1080
|
+
</div>
|
|
1026
1081
|
<div class="relative mt-2">
|
|
1027
1082
|
<Search :size="12" class="pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
|
1028
1083
|
<input
|
|
1029
1084
|
v-model="inputSearch"
|
|
1030
1085
|
type="text"
|
|
1031
|
-
aria-label="
|
|
1032
|
-
placeholder="
|
|
1086
|
+
:aria-label="t('integration.nodeInspector.input.searchAria')"
|
|
1087
|
+
:placeholder="t('integration.nodeInspector.input.searchAria')"
|
|
1033
1088
|
class="h-7 w-full rounded-md border border-black/[0.08] bg-white pl-6.5 pr-2 text-xs outline-none transition placeholder:text-muted-foreground/60 focus:border-black/[0.16] focus:ring-2 focus:ring-primary/40"
|
|
1034
1089
|
>
|
|
1035
1090
|
</div>
|
|
@@ -1041,15 +1096,15 @@ async function runTestStep() {
|
|
|
1041
1096
|
class="rounded-md border border-black/[0.08] bg-white px-2 py-1 text-[11px] font-medium text-muted-foreground transition hover:bg-muted/30 hover:text-foreground disabled:opacity-50"
|
|
1042
1097
|
@click="loadRunData"
|
|
1043
1098
|
>
|
|
1044
|
-
{{ runLoading ? "
|
|
1099
|
+
{{ runLoading ? t("integration.nodeInspector.input.runData.loading") : t("integration.nodeInspector.input.runData.use") }}
|
|
1045
1100
|
</button>
|
|
1046
1101
|
<template v-else>
|
|
1047
1102
|
<span class="min-w-0 flex-1 truncate text-[11px] text-emerald-700">
|
|
1048
|
-
|
|
1103
|
+
{{ t("integration.nodeInspector.input.runData.from", { id: runInfo.id }) }}
|
|
1049
1104
|
</span>
|
|
1050
1105
|
<button
|
|
1051
1106
|
type="button"
|
|
1052
|
-
title="
|
|
1107
|
+
:title="t('integration.nodeInspector.input.runData.clear')"
|
|
1053
1108
|
class="flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:text-foreground"
|
|
1054
1109
|
@click="clearRunData"
|
|
1055
1110
|
>
|
|
@@ -1066,10 +1121,10 @@ async function runTestStep() {
|
|
|
1066
1121
|
<!-- Upstream nodes -->
|
|
1067
1122
|
<div v-if="!searchQuery || visibleSections.length" class="space-y-3">
|
|
1068
1123
|
<div class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
1069
|
-
|
|
1124
|
+
{{ t("integration.nodeInspector.input.nodes.heading") }}
|
|
1070
1125
|
</div>
|
|
1071
1126
|
<p v-if="!upstreamSections.length" class="text-xs leading-snug text-muted-foreground">
|
|
1072
|
-
|
|
1127
|
+
{{ t("integration.nodeInspector.input.nodes.empty") }}
|
|
1073
1128
|
</p>
|
|
1074
1129
|
<div v-for="s in visibleSections" :key="s.id" class="rounded-md border border-black/[0.06]">
|
|
1075
1130
|
<button
|
|
@@ -1083,13 +1138,36 @@ async function runTestStep() {
|
|
|
1083
1138
|
<span
|
|
1084
1139
|
v-if="!s.direct"
|
|
1085
1140
|
class="shrink-0 rounded bg-slate-100 px-1 py-px text-[9px] font-medium uppercase tracking-wider text-slate-500"
|
|
1086
|
-
title="
|
|
1141
|
+
:title="t('integration.nodeInspector.input.nodes.indirectTitle')"
|
|
1087
1142
|
>
|
|
1088
|
-
|
|
1143
|
+
{{ t("integration.nodeInspector.input.nodes.indirectBadge") }}
|
|
1089
1144
|
</span>
|
|
1090
1145
|
</button>
|
|
1091
1146
|
<div v-show="!sectionClosed(s.id)" class="space-y-2 border-t border-black/[0.06] px-2 py-2">
|
|
1092
|
-
|
|
1147
|
+
<!-- These ports belong to an UPSTREAM node, so the sentence is that
|
|
1148
|
+
node's. On a 300px column it is a tooltip rather than text: the
|
|
1149
|
+
values below are what this panel is for, and a sentence per port
|
|
1150
|
+
pushes them out of view. The readable copy is the Output tab
|
|
1151
|
+
(AC-19).
|
|
1152
|
+
|
|
1153
|
+
The title hangs on the port's whole block, not on its name row:
|
|
1154
|
+
that row is only drawn for a node with several ports or a
|
|
1155
|
+
branch/error one, so a single `default` port — which is most
|
|
1156
|
+
nodes, No Operation among them — would have had nowhere to carry
|
|
1157
|
+
it and the tooltip would have been dead in the commonest case.
|
|
1158
|
+
|
|
1159
|
+
What that buys is uneven, and it is the reason this is a
|
|
1160
|
+
convenience rather than a promise: each field row carries its own
|
|
1161
|
+
title (the expression to copy), and a child title wins, so the
|
|
1162
|
+
sentence is read by resting on the port's name or the space around
|
|
1163
|
+
its rows rather than on a row itself. Where a reader is owed the
|
|
1164
|
+
text outright, that is the Output tab. -->
|
|
1165
|
+
<div
|
|
1166
|
+
v-for="p in s.ports"
|
|
1167
|
+
:key="p.name"
|
|
1168
|
+
class="space-y-1 pl-0.5"
|
|
1169
|
+
:title="resolveLocalized(p.description) || void 0"
|
|
1170
|
+
>
|
|
1093
1171
|
<div v-if="s.ports.length > 1 || p.kind !== 'default'" class="flex items-center gap-1.5">
|
|
1094
1172
|
<span class="size-1.5 rounded-full" :class="portDotClass(p.kind, p.name)" />
|
|
1095
1173
|
<span class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">{{ p.name }}</span>
|
|
@@ -1134,7 +1212,7 @@ async function runTestStep() {
|
|
|
1134
1212
|
<!-- Trigger payload -->
|
|
1135
1213
|
<div v-if="showTriggerChip" class="space-y-1.5">
|
|
1136
1214
|
<div class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
1137
|
-
|
|
1215
|
+
{{ t("integration.nodeInspector.input.trigger.heading") }}
|
|
1138
1216
|
</div>
|
|
1139
1217
|
<div class="flex flex-wrap gap-1">
|
|
1140
1218
|
<button
|
|
@@ -1150,19 +1228,19 @@ async function runTestStep() {
|
|
|
1150
1228
|
</button>
|
|
1151
1229
|
</div>
|
|
1152
1230
|
<p class="text-xs leading-snug text-muted-foreground">
|
|
1153
|
-
|
|
1231
|
+
{{ t("integration.nodeInspector.input.trigger.hint") }}
|
|
1154
1232
|
</p>
|
|
1155
1233
|
</div>
|
|
1156
1234
|
</template>
|
|
1157
1235
|
|
|
1158
1236
|
<p v-else class="text-xs leading-snug text-muted-foreground">
|
|
1159
|
-
|
|
1237
|
+
{{ t("integration.nodeInspector.input.noUpstreamForTrigger") }}
|
|
1160
1238
|
</p>
|
|
1161
1239
|
|
|
1162
1240
|
<!-- Workflow variables -->
|
|
1163
1241
|
<div v-if="!searchQuery || visibleVariableNames.length" class="space-y-1.5">
|
|
1164
1242
|
<div class="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
|
|
1165
|
-
|
|
1243
|
+
{{ t("integration.nodeInspector.input.variables.heading") }}
|
|
1166
1244
|
</div>
|
|
1167
1245
|
<!-- Schema rows with the declared default as the value. -->
|
|
1168
1246
|
<div v-if="variableRows.length" class="space-y-px">
|
|
@@ -1183,12 +1261,12 @@ async function runTestStep() {
|
|
|
1183
1261
|
</button>
|
|
1184
1262
|
</div>
|
|
1185
1263
|
<p v-else class="text-xs leading-snug text-muted-foreground">
|
|
1186
|
-
|
|
1264
|
+
{{ t("integration.nodeInspector.input.variables.empty") }}
|
|
1187
1265
|
</p>
|
|
1188
1266
|
</div>
|
|
1189
1267
|
|
|
1190
1268
|
<p v-if="searchEmpty" class="text-xs leading-snug text-muted-foreground">
|
|
1191
|
-
|
|
1269
|
+
{{ t("integration.nodeInspector.input.search.empty", { query: inputSearch.trim() }) }}
|
|
1192
1270
|
</p>
|
|
1193
1271
|
</div>
|
|
1194
1272
|
</aside>
|
|
@@ -1198,6 +1276,19 @@ async function runTestStep() {
|
|
|
1198
1276
|
<!-- Same TabBar as the workflow page — one tab look everywhere. -->
|
|
1199
1277
|
<TabBar v-model="tab" :tabs="inspectorTabs" class="shrink-0 px-4" />
|
|
1200
1278
|
|
|
1279
|
+
<!-- The pinned version has no built code, so nothing here can be tested
|
|
1280
|
+
or resolved. Above the tabs' content rather than inside one of them:
|
|
1281
|
+
the state belongs to the node, not to a tab, and a reader who opens
|
|
1282
|
+
this node on Parameters would otherwise meet the card's mark outside
|
|
1283
|
+
and no word about it inside. Outside the scrolling area so it cannot
|
|
1284
|
+
be scrolled away from. -->
|
|
1285
|
+
<p
|
|
1286
|
+
v-if="isNode && !versionExecutable"
|
|
1287
|
+
class="mx-4 mb-1 shrink-0 rounded-md border border-rose-200 bg-rose-50 px-2.5 py-1.5 text-sm text-rose-700"
|
|
1288
|
+
>
|
|
1289
|
+
{{ readyVersion ? t("integration.nodeInspector.notice.versionNotReady.upgrade", { version: draftNode?.nodeVersion ?? "", ready: readyVersion }) : t("integration.nodeInspector.notice.versionNotReady.none", { version: draftNode?.nodeVersion ?? "" }) }}
|
|
1290
|
+
</p>
|
|
1291
|
+
|
|
1201
1292
|
<div
|
|
1202
1293
|
class="min-h-0 flex-1 overflow-y-auto p-4"
|
|
1203
1294
|
:class="dragActive ? 'chip-drag-live' : ''"
|
|
@@ -1215,15 +1306,15 @@ async function runTestStep() {
|
|
|
1215
1306
|
<!-- The name lives in the text next to the switch, which is not
|
|
1216
1307
|
a <label> — so the switch says it itself. -->
|
|
1217
1308
|
<Switch
|
|
1218
|
-
aria-label="
|
|
1309
|
+
:aria-label="t('integration.nodeInspector.trigger.active.label')"
|
|
1219
1310
|
class="data-[state=unchecked]:bg-slate-300"
|
|
1220
1311
|
:model-value="draftTrigger.active !== false"
|
|
1221
1312
|
@update:model-value="(v) => patchTrigger({ active: v })"
|
|
1222
1313
|
/>
|
|
1223
1314
|
<div>
|
|
1224
|
-
<div class="text-sm font-medium">
|
|
1315
|
+
<div class="text-sm font-medium">{{ t("integration.nodeInspector.trigger.active.label") }}</div>
|
|
1225
1316
|
<p class="text-xs text-muted-foreground">
|
|
1226
|
-
|
|
1317
|
+
{{ t("integration.nodeInspector.trigger.active.hint") }}
|
|
1227
1318
|
</p>
|
|
1228
1319
|
</div>
|
|
1229
1320
|
</div>
|
|
@@ -1237,29 +1328,29 @@ async function runTestStep() {
|
|
|
1237
1328
|
<div class="space-y-1.5">
|
|
1238
1329
|
<!-- "Frequency", not "Runs": a run is an execution everywhere
|
|
1239
1330
|
else in the studio, and there is a whole page of them. -->
|
|
1240
|
-
<Label for="ni-frequency" class="text-mono text-muted-foreground">
|
|
1331
|
+
<Label for="ni-frequency" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.frequency.label") }}</Label>
|
|
1241
1332
|
<Select :model-value="frequency" @update:model-value="applyFrequency">
|
|
1242
|
-
<SelectTrigger id="ni-frequency" aria-label="
|
|
1333
|
+
<SelectTrigger id="ni-frequency" :aria-label="t('integration.nodeInspector.schedule.frequency.label')" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1243
1334
|
<SelectValue />
|
|
1244
1335
|
</SelectTrigger>
|
|
1245
1336
|
<SelectContent>
|
|
1246
1337
|
<SelectItem v-for="f in FREQUENCY_OPTIONS" :key="f.value" :value="f.value">{{ f.label }}</SelectItem>
|
|
1247
1338
|
<SelectSeparator />
|
|
1248
|
-
<SelectItem :value="FREQUENCY_ADVANCED">
|
|
1339
|
+
<SelectItem :value="FREQUENCY_ADVANCED">{{ t("integration.nodeInspector.schedule.frequency.advanced") }}</SelectItem>
|
|
1249
1340
|
</SelectContent>
|
|
1250
1341
|
</Select>
|
|
1251
1342
|
</div>
|
|
1252
1343
|
|
|
1253
1344
|
<!-- Every few minutes: the gap between firings. -->
|
|
1254
1345
|
<div v-if="frequency === 'minutes'" class="space-y-1.5">
|
|
1255
|
-
<Label for="ni-minute-interval" class="text-mono text-muted-foreground">
|
|
1346
|
+
<Label for="ni-minute-interval" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.interval.label") }}</Label>
|
|
1256
1347
|
<Select :model-value="String(carry.minuteInterval)" @update:model-value="applyMinuteInterval">
|
|
1257
|
-
<SelectTrigger id="ni-minute-interval" aria-label="
|
|
1348
|
+
<SelectTrigger id="ni-minute-interval" :aria-label="t('integration.nodeInspector.schedule.interval.label')" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1258
1349
|
<SelectValue />
|
|
1259
1350
|
</SelectTrigger>
|
|
1260
1351
|
<SelectContent>
|
|
1261
1352
|
<SelectItem v-for="n in minuteIntervalOptions" :key="n" :value="String(n)">
|
|
1262
|
-
{{
|
|
1353
|
+
{{ t("integration.nodeInspector.schedule.minuteInterval.option", n) }}
|
|
1263
1354
|
</SelectItem>
|
|
1264
1355
|
</SelectContent>
|
|
1265
1356
|
</Select>
|
|
@@ -1267,21 +1358,21 @@ async function runTestStep() {
|
|
|
1267
1358
|
|
|
1268
1359
|
<!-- Every few hours: the gap, plus where in the hour it lands. -->
|
|
1269
1360
|
<div v-if="frequency === 'hours'" class="space-y-1.5">
|
|
1270
|
-
<Label for="ni-hour-interval" class="text-mono text-muted-foreground">
|
|
1361
|
+
<Label for="ni-hour-interval" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.interval.label") }}</Label>
|
|
1271
1362
|
<Select :model-value="String(carry.hourInterval)" @update:model-value="applyHourInterval">
|
|
1272
|
-
<SelectTrigger id="ni-hour-interval" aria-label="
|
|
1363
|
+
<SelectTrigger id="ni-hour-interval" :aria-label="t('integration.nodeInspector.schedule.interval.label')" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1273
1364
|
<SelectValue />
|
|
1274
1365
|
</SelectTrigger>
|
|
1275
1366
|
<SelectContent>
|
|
1276
|
-
<SelectItem v-for="n in HOUR_INTERVALS" :key="n" :value="String(n)">
|
|
1367
|
+
<SelectItem v-for="n in HOUR_INTERVALS" :key="n" :value="String(n)">{{ t("integration.nodeInspector.schedule.hourInterval.option", { n }) }}</SelectItem>
|
|
1277
1368
|
</SelectContent>
|
|
1278
1369
|
</Select>
|
|
1279
1370
|
</div>
|
|
1280
1371
|
|
|
1281
1372
|
<div v-if="frequency === 'hourly' || frequency === 'hours' || frequency === 'times'" class="space-y-1.5">
|
|
1282
|
-
<Label for="ni-minute-past" class="text-mono text-muted-foreground">
|
|
1373
|
+
<Label for="ni-minute-past" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.minutePast.label") }}</Label>
|
|
1283
1374
|
<Select :model-value="String(carry.minute)" @update:model-value="applyMinutePast">
|
|
1284
|
-
<SelectTrigger id="ni-minute-past" aria-label="
|
|
1375
|
+
<SelectTrigger id="ni-minute-past" :aria-label="t('integration.nodeInspector.schedule.minutePast.label')" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1285
1376
|
<SelectValue />
|
|
1286
1377
|
</SelectTrigger>
|
|
1287
1378
|
<SelectContent>
|
|
@@ -1294,11 +1385,11 @@ async function runTestStep() {
|
|
|
1294
1385
|
rhythms share, which is why it survives a change of
|
|
1295
1386
|
frequency. -->
|
|
1296
1387
|
<div v-if="frequency === 'daily' || frequency === 'weekly' || frequency === 'monthly'" class="space-y-1.5">
|
|
1297
|
-
<Label for="ni-time" class="text-mono text-muted-foreground">
|
|
1388
|
+
<Label for="ni-time" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.time.label") }}</Label>
|
|
1298
1389
|
<Input
|
|
1299
1390
|
id="ni-time"
|
|
1300
1391
|
type="time"
|
|
1301
|
-
aria-label="
|
|
1392
|
+
:aria-label="t('integration.nodeInspector.schedule.time.label')"
|
|
1302
1393
|
:model-value="timeValue"
|
|
1303
1394
|
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white tabular-nums"
|
|
1304
1395
|
@update:model-value="applyTime"
|
|
@@ -1306,9 +1397,9 @@ async function runTestStep() {
|
|
|
1306
1397
|
</div>
|
|
1307
1398
|
|
|
1308
1399
|
<div v-if="frequency === 'monthly'" class="space-y-1.5">
|
|
1309
|
-
<Label for="ni-day-of-month" class="text-mono text-muted-foreground">
|
|
1400
|
+
<Label for="ni-day-of-month" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.dayOfMonth.label") }}</Label>
|
|
1310
1401
|
<Select :model-value="String(carry.day)" @update:model-value="applyDayOfMonth">
|
|
1311
|
-
<SelectTrigger id="ni-day-of-month" aria-label="
|
|
1402
|
+
<SelectTrigger id="ni-day-of-month" :aria-label="t('integration.nodeInspector.schedule.dayOfMonth.label')" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1312
1403
|
<SelectValue />
|
|
1313
1404
|
</SelectTrigger>
|
|
1314
1405
|
<SelectContent>
|
|
@@ -1319,10 +1410,10 @@ async function runTestStep() {
|
|
|
1319
1410
|
|
|
1320
1411
|
<!-- Advanced: the expression itself, the only place it is shown. -->
|
|
1321
1412
|
<div v-if="frequency === FREQUENCY_ADVANCED" class="space-y-1.5">
|
|
1322
|
-
<Label for="ni-cron" class="text-mono text-muted-foreground">
|
|
1413
|
+
<Label for="ni-cron" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.cron.label") }}</Label>
|
|
1323
1414
|
<Input
|
|
1324
1415
|
id="ni-cron"
|
|
1325
|
-
aria-label="
|
|
1416
|
+
:aria-label="t('integration.nodeInspector.schedule.cron.label')"
|
|
1326
1417
|
:model-value="cronValue"
|
|
1327
1418
|
placeholder="0 9-17 * * 1-5"
|
|
1328
1419
|
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white font-mono"
|
|
@@ -1338,10 +1429,10 @@ async function runTestStep() {
|
|
|
1338
1429
|
schedule triggers rather than one field per row.
|
|
1339
1430
|
-->
|
|
1340
1431
|
<div v-if="frequency === 'times'" class="space-y-1.5">
|
|
1341
|
-
<Label class="text-mono text-muted-foreground">
|
|
1432
|
+
<Label class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.hours.label") }}</Label>
|
|
1342
1433
|
<ToggleGroup
|
|
1343
1434
|
type="multiple"
|
|
1344
|
-
aria-label="
|
|
1435
|
+
:aria-label="t('integration.nodeInspector.schedule.hours.label')"
|
|
1345
1436
|
:model-value="selectedHours"
|
|
1346
1437
|
class="grid w-fit grid-cols-12 gap-1"
|
|
1347
1438
|
@update:model-value="applyHours"
|
|
@@ -1361,10 +1452,10 @@ async function runTestStep() {
|
|
|
1361
1452
|
<!-- Weekdays: full width, because seven toggles in half of the
|
|
1362
1453
|
grid would wrap. Serves both rhythms that fall on days. -->
|
|
1363
1454
|
<div v-if="frequency === 'weekly' || frequency === 'times'" class="space-y-1.5">
|
|
1364
|
-
<Label class="text-mono text-muted-foreground">
|
|
1455
|
+
<Label class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.schedule.days.label") }}</Label>
|
|
1365
1456
|
<ToggleGroup
|
|
1366
1457
|
type="multiple"
|
|
1367
|
-
aria-label="
|
|
1458
|
+
:aria-label="t('integration.nodeInspector.schedule.days.label')"
|
|
1368
1459
|
:model-value="selectedDays"
|
|
1369
1460
|
class="justify-start gap-1"
|
|
1370
1461
|
@update:model-value="applyDays"
|
|
@@ -1386,25 +1477,25 @@ async function runTestStep() {
|
|
|
1386
1477
|
the expression's numbers as wall-clock time in that zone, so a time
|
|
1387
1478
|
without its zone would be half an answer.
|
|
1388
1479
|
-->
|
|
1389
|
-
<p v-if="scheduleSentence" aria-label="
|
|
1480
|
+
<p v-if="scheduleSentence" :aria-label="t('integration.nodeInspector.schedule.whenItFiresAria')" class="text-xs text-emerald-700">{{ scheduleSentence }}</p>
|
|
1390
1481
|
<!-- No expression at all is its own answer, and the same one the
|
|
1391
1482
|
Schedules page gives for this trigger: it is not written as
|
|
1392
1483
|
anything, and it does not run. A trigger placed from the palette
|
|
1393
1484
|
carries an expression, so this is for one that arrived without
|
|
1394
1485
|
one. -->
|
|
1395
|
-
<p v-else-if="!hasCron" aria-label="
|
|
1396
|
-
|
|
1486
|
+
<p v-else-if="!hasCron" :aria-label="t('integration.nodeInspector.schedule.whenItFiresAria')" class="text-xs text-muted-foreground">
|
|
1487
|
+
{{ t("integration.nodeInspector.schedule.noneSet") }}
|
|
1397
1488
|
</p>
|
|
1398
1489
|
<p v-else class="text-xs text-muted-foreground">
|
|
1399
|
-
|
|
1490
|
+
{{ t("integration.nodeInspector.schedule.writtenAsExpression") }}
|
|
1400
1491
|
</p>
|
|
1401
1492
|
<p v-if="frequency === 'monthly' && carry.day >= 29" class="text-xs text-muted-foreground">
|
|
1402
|
-
|
|
1493
|
+
{{ t("integration.nodeInspector.schedule.monthlySkipHint") }}
|
|
1403
1494
|
</p>
|
|
1404
1495
|
</template>
|
|
1405
1496
|
<template v-else-if="draftTrigger.type === 'webhook'">
|
|
1406
1497
|
<div class="space-y-1.5">
|
|
1407
|
-
<Label for="ni-webhook-url" class="text-mono text-muted-foreground">
|
|
1498
|
+
<Label for="ni-webhook-url" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.url.label") }}</Label>
|
|
1408
1499
|
<div v-if="webhookUrl" class="flex items-center gap-1.5">
|
|
1409
1500
|
<Input
|
|
1410
1501
|
id="ni-webhook-url"
|
|
@@ -1415,7 +1506,7 @@ async function runTestStep() {
|
|
|
1415
1506
|
/>
|
|
1416
1507
|
<button
|
|
1417
1508
|
type="button"
|
|
1418
|
-
title="
|
|
1509
|
+
:title="t('integration.nodeInspector.trigger.webhook.url.copyTitle')"
|
|
1419
1510
|
class="flex size-8 shrink-0 items-center justify-center rounded-md border border-black/[0.08] bg-white text-muted-foreground transition hover:text-foreground"
|
|
1420
1511
|
@click="copyWebhookUrl"
|
|
1421
1512
|
>
|
|
@@ -1423,27 +1514,27 @@ async function runTestStep() {
|
|
|
1423
1514
|
</button>
|
|
1424
1515
|
</div>
|
|
1425
1516
|
<p class="text-xs text-muted-foreground">
|
|
1426
|
-
<template v-if="webhookIsPublic">
|
|
1427
|
-
<template v-else>
|
|
1517
|
+
<template v-if="webhookIsPublic">{{ t("integration.nodeInspector.trigger.webhook.url.publicHint") }}</template>
|
|
1518
|
+
<template v-else>{{ t("integration.nodeInspector.trigger.webhook.url.privateHint") }}</template>
|
|
1428
1519
|
</p>
|
|
1429
1520
|
</div>
|
|
1430
1521
|
<div class="flex items-center gap-3 rounded-lg border border-black/[0.06] bg-muted/20 px-3 py-2">
|
|
1431
1522
|
<Switch
|
|
1432
|
-
aria-label="
|
|
1523
|
+
:aria-label="t('integration.nodeInspector.trigger.webhook.public.label')"
|
|
1433
1524
|
class="data-[state=unchecked]:bg-slate-300"
|
|
1434
1525
|
:model-value="webhookIsPublic"
|
|
1435
1526
|
@update:model-value="(v) => writeTriggerConfigPatch(v ? { public: true, ...draftTrigger.config.authProfile ? {} : configPatchForAuthChoice('token') } : { public: void 0 })"
|
|
1436
1527
|
/>
|
|
1437
1528
|
<div>
|
|
1438
|
-
<div class="text-sm font-medium">
|
|
1529
|
+
<div class="text-sm font-medium">{{ t("integration.nodeInspector.trigger.webhook.public.label") }}</div>
|
|
1439
1530
|
<p class="text-xs text-muted-foreground">
|
|
1440
|
-
|
|
1531
|
+
{{ t("integration.nodeInspector.trigger.webhook.public.hint") }}
|
|
1441
1532
|
</p>
|
|
1442
1533
|
</div>
|
|
1443
1534
|
</div>
|
|
1444
1535
|
<div class="grid grid-cols-2 gap-3">
|
|
1445
1536
|
<div class="space-y-1.5">
|
|
1446
|
-
<Label for="ni-webhook-method" class="text-mono text-muted-foreground">
|
|
1537
|
+
<Label for="ni-webhook-method" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.method.label") }}</Label>
|
|
1447
1538
|
<Select
|
|
1448
1539
|
:model-value="readTriggerConfig('method') ?? 'POST'"
|
|
1449
1540
|
@update:model-value="(v) => writeTriggerConfig('method', v)"
|
|
@@ -1461,23 +1552,23 @@ async function runTestStep() {
|
|
|
1461
1552
|
</Select>
|
|
1462
1553
|
</div>
|
|
1463
1554
|
<div v-if="webhookIsPublic" class="space-y-1.5">
|
|
1464
|
-
<Label for="ni-webhook-protection" class="text-mono text-muted-foreground">
|
|
1555
|
+
<Label for="ni-webhook-protection" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.protection.label") }}</Label>
|
|
1465
1556
|
<Select :model-value="authChoice" @update:model-value="applyAuthChoice">
|
|
1466
1557
|
<SelectTrigger id="ni-webhook-protection" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1467
1558
|
<SelectValue />
|
|
1468
1559
|
</SelectTrigger>
|
|
1469
1560
|
<SelectContent>
|
|
1470
|
-
<SelectItem value="token">
|
|
1471
|
-
<SelectItem value="bearer">
|
|
1472
|
-
<SelectItem value="basic">
|
|
1473
|
-
<SelectItem value="none">
|
|
1561
|
+
<SelectItem value="token">{{ t("integration.nodeInspector.trigger.webhook.protection.token") }}</SelectItem>
|
|
1562
|
+
<SelectItem value="bearer">{{ t("integration.nodeInspector.trigger.webhook.protection.bearer") }}</SelectItem>
|
|
1563
|
+
<SelectItem value="basic">{{ t("integration.nodeInspector.trigger.webhook.protection.basic") }}</SelectItem>
|
|
1564
|
+
<SelectItem value="none">{{ t("integration.nodeInspector.trigger.webhook.protection.none") }}</SelectItem>
|
|
1474
1565
|
</SelectContent>
|
|
1475
1566
|
</Select>
|
|
1476
|
-
<p class="text-xs text-muted-foreground">
|
|
1567
|
+
<p class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.protection.hint") }}</p>
|
|
1477
1568
|
</div>
|
|
1478
1569
|
</div>
|
|
1479
1570
|
<div v-if="webhookIsPublic && authChoice === 'token'" class="space-y-1.5">
|
|
1480
|
-
<Label for="ni-auth-header" class="text-mono text-muted-foreground">
|
|
1571
|
+
<Label for="ni-auth-header" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.header.label") }}</Label>
|
|
1481
1572
|
<Input
|
|
1482
1573
|
id="ni-auth-header"
|
|
1483
1574
|
:model-value="draftTrigger.config.authConfig?.header ?? ''"
|
|
@@ -1485,33 +1576,34 @@ async function runTestStep() {
|
|
|
1485
1576
|
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none bg-white text-sm"
|
|
1486
1577
|
@update:model-value="(v) => writeAuthConfigKey('header', String(v))"
|
|
1487
1578
|
/>
|
|
1488
|
-
<p class="text-xs text-muted-foreground">
|
|
1579
|
+
<p class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.header.hint") }}</p>
|
|
1489
1580
|
</div>
|
|
1490
1581
|
<div v-if="webhookIsPublic && authChoice === 'basic'" class="space-y-1.5">
|
|
1491
|
-
<Label for="ni-auth-username" class="text-mono text-muted-foreground">
|
|
1582
|
+
<Label for="ni-auth-username" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.username.label") }}</Label>
|
|
1492
1583
|
<Input
|
|
1493
1584
|
id="ni-auth-username"
|
|
1494
1585
|
:model-value="draftTrigger.config.authConfig?.username ?? ''"
|
|
1495
|
-
placeholder="
|
|
1586
|
+
:placeholder="t('integration.nodeInspector.trigger.webhook.username.placeholder')"
|
|
1496
1587
|
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none bg-white text-sm"
|
|
1497
1588
|
@update:model-value="(v) => writeAuthConfigKey('username', String(v))"
|
|
1498
1589
|
/>
|
|
1499
|
-
<p class="text-xs text-muted-foreground">
|
|
1590
|
+
<p class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.username.hint") }}</p>
|
|
1500
1591
|
</div>
|
|
1501
1592
|
<div v-if="webhookExposed && authChoice !== 'none'" class="flex items-center gap-2">
|
|
1502
1593
|
<Button type="button" variant="outline" size="sm" class="h-7 text-sm" :disabled="rotateBusy" @click="rotateEndpointSecret">
|
|
1503
|
-
{{ rotateBusy ? "
|
|
1594
|
+
{{ rotateBusy ? t("integration.nodeInspector.trigger.webhook.rotate.busy") : t("integration.nodeInspector.trigger.webhook.rotate.action") }}
|
|
1504
1595
|
</Button>
|
|
1505
|
-
<p class="text-xs text-muted-foreground">
|
|
1596
|
+
<p class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.trigger.webhook.rotate.hint") }}</p>
|
|
1506
1597
|
</div>
|
|
1507
1598
|
|
|
1508
1599
|
<Dialog v-model:open="rotateOpen">
|
|
1509
1600
|
<DialogContent class="sm:max-w-md">
|
|
1510
1601
|
<DialogHeader>
|
|
1511
|
-
<DialogTitle>
|
|
1602
|
+
<DialogTitle>{{ t("integration.nodeInspector.trigger.webhook.rotate.dialogTitle") }}</DialogTitle>
|
|
1512
1603
|
<DialogDescription>
|
|
1513
|
-
|
|
1514
|
-
|
|
1604
|
+
{{ t("integration.nodeInspector.trigger.webhook.rotate.dialogHint", {
|
|
1605
|
+
where: authChoice === "basic" ? t("integration.nodeInspector.trigger.webhook.rotate.whereBasic") : authChoice === "bearer" ? t("integration.nodeInspector.trigger.webhook.rotate.whereBearer") : t("integration.nodeInspector.trigger.webhook.rotate.whereHeader")
|
|
1606
|
+
}) }}
|
|
1515
1607
|
</DialogDescription>
|
|
1516
1608
|
</DialogHeader>
|
|
1517
1609
|
<div class="flex items-center gap-1.5">
|
|
@@ -1523,7 +1615,7 @@ async function runTestStep() {
|
|
|
1523
1615
|
/>
|
|
1524
1616
|
<button
|
|
1525
1617
|
type="button"
|
|
1526
|
-
title="
|
|
1618
|
+
:title="t('integration.nodeInspector.trigger.webhook.rotate.copyTitle')"
|
|
1527
1619
|
class="flex size-8 shrink-0 items-center justify-center rounded-md border border-black/[0.08] bg-white text-muted-foreground transition hover:text-foreground"
|
|
1528
1620
|
@click="copySecret"
|
|
1529
1621
|
>
|
|
@@ -1535,36 +1627,36 @@ async function runTestStep() {
|
|
|
1535
1627
|
</template>
|
|
1536
1628
|
<template v-else-if="draftTrigger.type === 'event'">
|
|
1537
1629
|
<div class="space-y-1.5">
|
|
1538
|
-
<Label for="ni-event-type" class="text-mono text-muted-foreground">
|
|
1630
|
+
<Label for="ni-event-type" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.event.label") }}</Label>
|
|
1539
1631
|
<EventTypeCombobox
|
|
1540
1632
|
input-id="ni-event-type"
|
|
1541
1633
|
:model-value="readTriggerConfig('event_type') ?? ''"
|
|
1542
1634
|
@update:model-value="(v) => writeTriggerConfig('event_type', v)"
|
|
1543
1635
|
/>
|
|
1544
1636
|
<p class="text-xs text-muted-foreground">
|
|
1545
|
-
|
|
1637
|
+
{{ t("integration.nodeInspector.trigger.event.hint.intro") }} <code class="font-mono">customer.created</code>, <code class="font-mono">order.placed</code>{{ t("integration.nodeInspector.trigger.event.hint.mid") }} <code class="font-mono">trigger.payload</code>.
|
|
1546
1638
|
</p>
|
|
1547
1639
|
</div>
|
|
1548
1640
|
<div v-if="eventSample" class="space-y-1.5">
|
|
1549
1641
|
<!-- A caption, not a field label: it heads a read-only block,
|
|
1550
1642
|
so it stays a plain line rather than an orphan <label>. -->
|
|
1551
|
-
<div class="text-mono text-muted-foreground">
|
|
1643
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.event.sample.heading") }}</div>
|
|
1552
1644
|
<pre class="max-h-72 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2.5 font-mono text-[11px] leading-relaxed text-slate-700">{{ eventSample }}</pre>
|
|
1553
|
-
<p class="text-xs text-muted-foreground">
|
|
1645
|
+
<p class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.trigger.event.sample.hint") }} <code class="font-mono">trigger.payload</code>.</p>
|
|
1554
1646
|
</div>
|
|
1555
1647
|
<p v-else-if="eventTopic" class="text-xs text-muted-foreground">
|
|
1556
|
-
|
|
1648
|
+
{{ t("integration.nodeInspector.trigger.event.sample.empty") }}
|
|
1557
1649
|
</p>
|
|
1558
1650
|
</template>
|
|
1559
1651
|
<template v-else>
|
|
1560
1652
|
<div class="space-y-1.5">
|
|
1561
1653
|
<!-- Caption for the field list below, not a label for one
|
|
1562
1654
|
control — the row's own controls are named individually. -->
|
|
1563
|
-
<div class="text-mono text-muted-foreground">
|
|
1655
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.trigger.manual.heading") }}</div>
|
|
1564
1656
|
<p class="text-xs text-muted-foreground">
|
|
1565
|
-
|
|
1566
|
-
<code class="font-mono">trigger.payload.<key></code
|
|
1567
|
-
|
|
1657
|
+
{{ t("integration.nodeInspector.trigger.manual.hint.intro") }}
|
|
1658
|
+
<code class="font-mono">trigger.payload.<key></code>{{ t("integration.nodeInspector.trigger.manual.hint.mid") }}
|
|
1659
|
+
<code class="font-mono">trigger.payload.*</code> {{ t("integration.nodeInspector.trigger.manual.hint.outro") }}
|
|
1568
1660
|
</p>
|
|
1569
1661
|
</div>
|
|
1570
1662
|
<div v-if="manualFields.length" class="space-y-1.5">
|
|
@@ -1572,41 +1664,41 @@ async function runTestStep() {
|
|
|
1572
1664
|
<!-- A repeating row has no room for visible labels, so each
|
|
1573
1665
|
control carries its own name and says which row it is. -->
|
|
1574
1666
|
<Select :model-value="mf.kind ?? 'text'" @update:model-value="(v) => setManualField(idx, { kind: v })">
|
|
1575
|
-
<SelectTrigger :aria-label="
|
|
1667
|
+
<SelectTrigger :aria-label="t('integration.nodeInspector.trigger.manual.field.kindAria', { n: idx + 1 })" size="sm" class="h-8 w-24 shrink-0 rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
1576
1668
|
<SelectValue />
|
|
1577
1669
|
</SelectTrigger>
|
|
1578
1670
|
<SelectContent>
|
|
1579
|
-
<SelectItem value="text">
|
|
1580
|
-
<SelectItem value="file">
|
|
1671
|
+
<SelectItem value="text">{{ t("integration.nodeInspector.trigger.manual.kind.text") }}</SelectItem>
|
|
1672
|
+
<SelectItem value="file">{{ t("integration.nodeInspector.trigger.manual.kind.file") }}</SelectItem>
|
|
1581
1673
|
</SelectContent>
|
|
1582
1674
|
</Select>
|
|
1583
1675
|
<Input
|
|
1584
|
-
:aria-label="
|
|
1676
|
+
:aria-label="t('integration.nodeInspector.trigger.manual.field.keyAria', { n: idx + 1 })"
|
|
1585
1677
|
:model-value="mf.path"
|
|
1586
|
-
placeholder="
|
|
1678
|
+
:placeholder="t('integration.nodeInspector.trigger.manual.field.keyPlaceholder')"
|
|
1587
1679
|
class="h-8 w-[38%] rounded-md border border-black/[0.08] ring-0 shadow-none bg-white font-mono text-sm"
|
|
1588
1680
|
@update:model-value="(v) => setManualField(idx, { path: String(v) })"
|
|
1589
1681
|
/>
|
|
1590
1682
|
<Input
|
|
1591
|
-
:aria-label="
|
|
1683
|
+
:aria-label="t('integration.nodeInspector.trigger.manual.field.labelAria', { n: idx + 1 })"
|
|
1592
1684
|
:model-value="mf.label ?? ''"
|
|
1593
|
-
placeholder="
|
|
1685
|
+
:placeholder="t('integration.nodeInspector.trigger.manual.field.labelPlaceholder')"
|
|
1594
1686
|
class="h-8 min-w-0 flex-1 rounded-md border border-black/[0.08] ring-0 shadow-none bg-white text-sm"
|
|
1595
1687
|
@update:model-value="(v) => setManualField(idx, { label: String(v) })"
|
|
1596
1688
|
/>
|
|
1597
1689
|
<button
|
|
1598
1690
|
type="button"
|
|
1599
|
-
:aria-label="
|
|
1691
|
+
:aria-label="t('integration.nodeInspector.trigger.manual.field.removeAria', { n: idx + 1 })"
|
|
1600
1692
|
class="flex size-7 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:text-rose-600"
|
|
1601
1693
|
@click="removeManualField(idx)"
|
|
1602
1694
|
>
|
|
1603
|
-
<
|
|
1695
|
+
<TrashBin :size="13" />
|
|
1604
1696
|
</button>
|
|
1605
1697
|
</div>
|
|
1606
1698
|
</div>
|
|
1607
1699
|
<Button type="button" variant="outline" size="sm" class="h-7 text-sm" @click="addManualField">
|
|
1608
1700
|
<Plus :size="12" />
|
|
1609
|
-
|
|
1701
|
+
{{ t("integration.nodeInspector.trigger.manual.action.addField") }}
|
|
1610
1702
|
</Button>
|
|
1611
1703
|
</template>
|
|
1612
1704
|
</div>
|
|
@@ -1615,23 +1707,23 @@ async function runTestStep() {
|
|
|
1615
1707
|
<div v-else-if="tab === 'parameters' && draftNode" class="mx-auto w-full max-w-5xl">
|
|
1616
1708
|
<div v-if="manifest?.config?.length" class="space-y-3">
|
|
1617
1709
|
<div class="flex items-center justify-between">
|
|
1618
|
-
<span class="text-mono text-muted-foreground">
|
|
1710
|
+
<span class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.parameters.heading") }}</span>
|
|
1619
1711
|
<div class="flex items-center gap-2">
|
|
1620
1712
|
<button
|
|
1621
1713
|
type="button"
|
|
1622
1714
|
class="flex items-center gap-1 rounded px-1 py-0.5 text-sm text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1623
|
-
title="
|
|
1715
|
+
:title="t('integration.nodeInspector.parameters.copyTitle')"
|
|
1624
1716
|
@click="copyConfig"
|
|
1625
1717
|
>
|
|
1626
|
-
<Copy :size="12" />
|
|
1718
|
+
<Copy :size="12" /> {{ t("integration.common.action.copy") }}
|
|
1627
1719
|
</button>
|
|
1628
1720
|
<button
|
|
1629
1721
|
type="button"
|
|
1630
1722
|
class="rounded px-1 py-0.5 text-sm text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
1631
|
-
title="
|
|
1723
|
+
:title="t('integration.nodeInspector.parameters.pasteTitle')"
|
|
1632
1724
|
@click="pasteConfig"
|
|
1633
1725
|
>
|
|
1634
|
-
|
|
1726
|
+
{{ t("integration.common.action.paste") }}
|
|
1635
1727
|
</button>
|
|
1636
1728
|
</div>
|
|
1637
1729
|
</div>
|
|
@@ -1644,6 +1736,7 @@ async function runTestStep() {
|
|
|
1644
1736
|
:version="draftNode.nodeVersion"
|
|
1645
1737
|
:secret-keys="secretKeys"
|
|
1646
1738
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1739
|
+
:sibling-fields="manifest.config ?? []"
|
|
1647
1740
|
:depth="0"
|
|
1648
1741
|
:variables="variables"
|
|
1649
1742
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1662,7 +1755,7 @@ async function runTestStep() {
|
|
|
1662
1755
|
@click="optionalOpen = true"
|
|
1663
1756
|
>
|
|
1664
1757
|
<ChevronRight :size="13" />
|
|
1665
|
-
|
|
1758
|
+
{{ t("integration.nodeInspector.parameters.optional.show", optionalFields.length) }}
|
|
1666
1759
|
</button>
|
|
1667
1760
|
<template v-else>
|
|
1668
1761
|
<button
|
|
@@ -1672,7 +1765,7 @@ async function runTestStep() {
|
|
|
1672
1765
|
@click="optionalOpen = false"
|
|
1673
1766
|
>
|
|
1674
1767
|
<ChevronDown :size="13" />
|
|
1675
|
-
|
|
1768
|
+
{{ t("integration.nodeInspector.parameters.optional.hide", optionalFields.length) }}
|
|
1676
1769
|
</button>
|
|
1677
1770
|
<IntegrationsNodeConfigField
|
|
1678
1771
|
v-for="field in optionalFields"
|
|
@@ -1683,6 +1776,7 @@ async function runTestStep() {
|
|
|
1683
1776
|
:version="draftNode.nodeVersion"
|
|
1684
1777
|
:secret-keys="secretKeys"
|
|
1685
1778
|
:static-keys="(manifest.config ?? []).map((f2) => f2.key)"
|
|
1779
|
+
:sibling-fields="manifest.config ?? []"
|
|
1686
1780
|
:depth="0"
|
|
1687
1781
|
:variables="variables"
|
|
1688
1782
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1693,11 +1787,11 @@ async function runTestStep() {
|
|
|
1693
1787
|
</template>
|
|
1694
1788
|
|
|
1695
1789
|
<p v-if="!parameterFields.length" class="text-sm text-muted-foreground italic">
|
|
1696
|
-
|
|
1790
|
+
{{ t("integration.nodeInspector.parameters.allOnSettingsTab") }}
|
|
1697
1791
|
</p>
|
|
1698
1792
|
</div>
|
|
1699
1793
|
<div v-else class="text-sm text-muted-foreground italic">
|
|
1700
|
-
|
|
1794
|
+
{{ t("integration.nodeInspector.parameters.noConfiguration") }}
|
|
1701
1795
|
</div>
|
|
1702
1796
|
</div>
|
|
1703
1797
|
|
|
@@ -1710,9 +1804,9 @@ async function runTestStep() {
|
|
|
1710
1804
|
identical-looking numbers with nothing to tell them apart. -->
|
|
1711
1805
|
<div v-if="settingsFields.length" class="space-y-3">
|
|
1712
1806
|
<div>
|
|
1713
|
-
<span class="text-mono text-muted-foreground">
|
|
1807
|
+
<span class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.ownOptions.heading") }}</span>
|
|
1714
1808
|
<p class="text-xs text-muted-foreground">
|
|
1715
|
-
|
|
1809
|
+
{{ t("integration.nodeInspector.settings.ownOptions.hint") }}
|
|
1716
1810
|
</p>
|
|
1717
1811
|
</div>
|
|
1718
1812
|
<IntegrationsNodeConfigField
|
|
@@ -1724,6 +1818,7 @@ async function runTestStep() {
|
|
|
1724
1818
|
:version="draftNode.nodeVersion"
|
|
1725
1819
|
:secret-keys="secretKeys"
|
|
1726
1820
|
:static-keys="(manifest?.config ?? []).map((f2) => f2.key)"
|
|
1821
|
+
:sibling-fields="manifest?.config ?? []"
|
|
1727
1822
|
:depth="0"
|
|
1728
1823
|
:variables="variables"
|
|
1729
1824
|
:upstream-outputs="upstreamOutputs"
|
|
@@ -1734,9 +1829,9 @@ async function runTestStep() {
|
|
|
1734
1829
|
<!-- The rule only earns its place when there is something above to
|
|
1735
1830
|
divide from. -->
|
|
1736
1831
|
<div :class="settingsFields.length ? 'border-t border-black/[0.06] pt-5' : ''">
|
|
1737
|
-
<span class="text-mono text-muted-foreground">
|
|
1832
|
+
<span class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.engine.heading") }}</span>
|
|
1738
1833
|
<p class="text-xs text-muted-foreground">
|
|
1739
|
-
|
|
1834
|
+
{{ t("integration.nodeInspector.settings.engine.hint") }}
|
|
1740
1835
|
</p>
|
|
1741
1836
|
</div>
|
|
1742
1837
|
|
|
@@ -1746,21 +1841,21 @@ async function runTestStep() {
|
|
|
1746
1841
|
<section class="rounded-lg border border-black/[0.06] bg-muted/20">
|
|
1747
1842
|
<div class="flex items-center gap-3 px-3 py-2">
|
|
1748
1843
|
<Switch
|
|
1749
|
-
aria-label="
|
|
1844
|
+
:aria-label="t('integration.nodeInspector.settings.retry.label')"
|
|
1750
1845
|
class="data-[state=unchecked]:bg-slate-300"
|
|
1751
1846
|
:model-value="!!draftNode.retry"
|
|
1752
1847
|
@update:model-value="(v) => patchNode({ retry: v ? { maxAttempts: 3, backoffSeconds: 1, strategy: 'exponential' } : void 0 })"
|
|
1753
1848
|
/>
|
|
1754
1849
|
<div>
|
|
1755
|
-
<div class="text-sm font-medium">
|
|
1850
|
+
<div class="text-sm font-medium">{{ t("integration.nodeInspector.settings.retry.label") }}</div>
|
|
1756
1851
|
<p class="text-xs text-muted-foreground">
|
|
1757
|
-
|
|
1852
|
+
{{ t("integration.nodeInspector.settings.retry.hint") }}
|
|
1758
1853
|
</p>
|
|
1759
1854
|
</div>
|
|
1760
1855
|
</div>
|
|
1761
1856
|
<div v-if="draftNode.retry" class="grid grid-cols-4 gap-3 border-t border-black/[0.06] p-3">
|
|
1762
1857
|
<div class="space-y-1.5">
|
|
1763
|
-
<Label for="ni-retry-attempts" class="text-mono text-muted-foreground">
|
|
1858
|
+
<Label for="ni-retry-attempts" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.retry.maxAttempts") }}</Label>
|
|
1764
1859
|
<Input
|
|
1765
1860
|
id="ni-retry-attempts"
|
|
1766
1861
|
type="number"
|
|
@@ -1771,7 +1866,7 @@ async function runTestStep() {
|
|
|
1771
1866
|
/>
|
|
1772
1867
|
</div>
|
|
1773
1868
|
<div class="space-y-1.5">
|
|
1774
|
-
<Label for="ni-retry-backoff" class="text-mono text-muted-foreground">
|
|
1869
|
+
<Label for="ni-retry-backoff" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.retry.backoffSeconds") }}</Label>
|
|
1775
1870
|
<Input
|
|
1776
1871
|
id="ni-retry-backoff"
|
|
1777
1872
|
type="number"
|
|
@@ -1782,7 +1877,7 @@ async function runTestStep() {
|
|
|
1782
1877
|
/>
|
|
1783
1878
|
</div>
|
|
1784
1879
|
<div class="col-span-2 space-y-1.5">
|
|
1785
|
-
<Label for="ni-retry-strategy" class="text-mono text-muted-foreground">
|
|
1880
|
+
<Label for="ni-retry-strategy" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.retry.strategy") }}</Label>
|
|
1786
1881
|
<Select
|
|
1787
1882
|
:model-value="draftNode.retry?.strategy ?? 'exponential'"
|
|
1788
1883
|
@update:model-value="(v) => patchNode({ retry: { ...draftNode.retry, strategy: v } })"
|
|
@@ -1791,8 +1886,8 @@ async function runTestStep() {
|
|
|
1791
1886
|
<SelectValue />
|
|
1792
1887
|
</SelectTrigger>
|
|
1793
1888
|
<SelectContent>
|
|
1794
|
-
<SelectItem value="linear">
|
|
1795
|
-
<SelectItem value="exponential">
|
|
1889
|
+
<SelectItem value="linear">{{ t("integration.nodeInspector.settings.retry.strategyLinear") }}</SelectItem>
|
|
1890
|
+
<SelectItem value="exponential">{{ t("integration.nodeInspector.settings.retry.strategyExponential") }}</SelectItem>
|
|
1796
1891
|
</SelectContent>
|
|
1797
1892
|
</Select>
|
|
1798
1893
|
</div>
|
|
@@ -1801,7 +1896,7 @@ async function runTestStep() {
|
|
|
1801
1896
|
|
|
1802
1897
|
<!-- Timeout: engine start-to-close ceiling for one attempt -->
|
|
1803
1898
|
<section class="space-y-1.5">
|
|
1804
|
-
<Label for="ni-timeout" class="text-mono text-muted-foreground">
|
|
1899
|
+
<Label for="ni-timeout" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.timeout.label") }}</Label>
|
|
1805
1900
|
<Input
|
|
1806
1901
|
id="ni-timeout"
|
|
1807
1902
|
type="number"
|
|
@@ -1813,13 +1908,13 @@ async function runTestStep() {
|
|
|
1813
1908
|
@update:model-value="(v) => patchNode({ timeoutSeconds: Number(v) > 0 ? Math.min(Math.trunc(Number(v)), 7200) : void 0 })"
|
|
1814
1909
|
/>
|
|
1815
1910
|
<p class="text-xs text-muted-foreground">
|
|
1816
|
-
|
|
1911
|
+
{{ t("integration.nodeInspector.settings.timeout.hint") }}
|
|
1817
1912
|
</p>
|
|
1818
1913
|
</section>
|
|
1819
1914
|
|
|
1820
1915
|
<!-- On error: what happens once the retries are exhausted -->
|
|
1821
1916
|
<section class="space-y-1.5">
|
|
1822
|
-
<Label for="ni-on-error" class="text-mono text-muted-foreground">
|
|
1917
|
+
<Label for="ni-on-error" class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.settings.onError.label") }}</Label>
|
|
1823
1918
|
<Select
|
|
1824
1919
|
:model-value="draftNode.onError ?? 'fail'"
|
|
1825
1920
|
@update:model-value="(v) => patchNode({ onError: v === 'route' ? 'route' : void 0 })"
|
|
@@ -1828,12 +1923,12 @@ async function runTestStep() {
|
|
|
1828
1923
|
<SelectValue />
|
|
1829
1924
|
</SelectTrigger>
|
|
1830
1925
|
<SelectContent>
|
|
1831
|
-
<SelectItem value="fail">
|
|
1832
|
-
<SelectItem value="route">
|
|
1926
|
+
<SelectItem value="fail">{{ t("integration.nodeInspector.settings.onError.fail") }}</SelectItem>
|
|
1927
|
+
<SelectItem value="route">{{ t("integration.nodeInspector.settings.onError.route") }}</SelectItem>
|
|
1833
1928
|
</SelectContent>
|
|
1834
1929
|
</Select>
|
|
1835
1930
|
<p class="text-xs text-muted-foreground">
|
|
1836
|
-
|
|
1931
|
+
{{ t("integration.nodeInspector.settings.onError.hint") }}
|
|
1837
1932
|
</p>
|
|
1838
1933
|
</section>
|
|
1839
1934
|
</div>
|
|
@@ -1845,15 +1940,14 @@ async function runTestStep() {
|
|
|
1845
1940
|
<section class="space-y-2">
|
|
1846
1941
|
<div class="flex items-center justify-between gap-2">
|
|
1847
1942
|
<p class="text-xs text-muted-foreground">
|
|
1848
|
-
|
|
1849
|
-
filled with real values once the step has run.
|
|
1943
|
+
{{ t("integration.nodeInspector.output.description") }}
|
|
1850
1944
|
</p>
|
|
1851
1945
|
<div class="flex shrink-0 items-center rounded-md border border-black/[0.06] bg-white p-0.5">
|
|
1852
1946
|
<button type="button" :class="outputView === 'schema' ? segActive : segIdle" @click="outputView = 'schema'">
|
|
1853
|
-
|
|
1947
|
+
{{ t("integration.nodeInspector.output.view.schema") }}
|
|
1854
1948
|
</button>
|
|
1855
1949
|
<button type="button" :disabled="!selfSample" :class="outputView === 'json' ? segActive : segIdle" @click="outputView = 'json'">
|
|
1856
|
-
|
|
1950
|
+
{{ t("integration.nodeInspector.output.view.json") }}
|
|
1857
1951
|
</button>
|
|
1858
1952
|
</div>
|
|
1859
1953
|
</div>
|
|
@@ -1864,32 +1958,32 @@ async function runTestStep() {
|
|
|
1864
1958
|
</section>
|
|
1865
1959
|
|
|
1866
1960
|
<section class="space-y-2 border-t border-black/[0.06] pt-4">
|
|
1867
|
-
<span class="text-mono text-muted-foreground">
|
|
1961
|
+
<span class="text-mono text-muted-foreground">{{ t("integration.nodeInspector.output.realValues.heading") }}</span>
|
|
1868
1962
|
<div class="flex items-center gap-2">
|
|
1869
1963
|
<Button
|
|
1870
1964
|
size="sm"
|
|
1871
1965
|
class="h-8"
|
|
1872
|
-
:disabled="testRunning"
|
|
1873
|
-
title="
|
|
1966
|
+
:disabled="testRunning || !versionExecutable"
|
|
1967
|
+
:title="versionExecutable ? t('integration.nodeInspector.output.test.runTitle') : void 0"
|
|
1874
1968
|
@click="runTestStep"
|
|
1875
1969
|
>
|
|
1876
|
-
{{ testRunning ? "
|
|
1970
|
+
{{ testRunning ? t("integration.nodeInspector.output.test.running") : t("integration.nodeInspector.output.test.action") }}
|
|
1877
1971
|
</Button>
|
|
1878
1972
|
<button
|
|
1879
1973
|
type="button"
|
|
1880
1974
|
class="rounded px-1 py-1 text-sm font-medium text-muted-foreground transition hover:text-foreground"
|
|
1881
1975
|
@click="toggleTestInput"
|
|
1882
1976
|
>
|
|
1883
|
-
{{ testInputOpen ? "
|
|
1977
|
+
{{ testInputOpen ? t("integration.nodeInspector.output.test.hideInput") : t("integration.nodeInspector.output.test.editInput") }}
|
|
1884
1978
|
</button>
|
|
1885
1979
|
<span class="text-xs text-muted-foreground">
|
|
1886
|
-
|
|
1980
|
+
{{ versionExecutable ? t("integration.nodeInspector.output.test.runHint") : "" }}
|
|
1887
1981
|
</span>
|
|
1888
1982
|
</div>
|
|
1889
1983
|
<Textarea
|
|
1890
1984
|
v-if="testInputOpen"
|
|
1891
1985
|
v-model="testInputDraft"
|
|
1892
|
-
aria-label="
|
|
1986
|
+
:aria-label="t('integration.nodeInspector.output.test.inputAria')"
|
|
1893
1987
|
:rows="8"
|
|
1894
1988
|
spellcheck="false"
|
|
1895
1989
|
class="rounded-md border border-black/[0.08] ring-0 shadow-none bg-white font-mono text-xs"
|
|
@@ -1913,7 +2007,7 @@ async function runTestStep() {
|
|
|
1913
2007
|
<template v-if="!pinEditorOpen">
|
|
1914
2008
|
<div class="flex items-center gap-2">
|
|
1915
2009
|
<Button variant="outline" size="sm" class="h-8" @click="openPinEditor">
|
|
1916
|
-
{{ selfPinned ? "
|
|
2010
|
+
{{ selfPinned ? t("integration.nodeInspector.output.pin.editAction") : t("integration.nodeInspector.output.pin.pinAction") }}
|
|
1917
2011
|
</Button>
|
|
1918
2012
|
<button
|
|
1919
2013
|
v-if="selfPinned"
|
|
@@ -1921,17 +2015,17 @@ async function runTestStep() {
|
|
|
1921
2015
|
class="text-sm font-medium text-rose-600 transition hover:text-rose-700"
|
|
1922
2016
|
@click="removePin"
|
|
1923
2017
|
>
|
|
1924
|
-
|
|
2018
|
+
{{ t("integration.nodeInspector.output.pin.unpinAction") }}
|
|
1925
2019
|
</button>
|
|
1926
2020
|
<span class="text-xs text-muted-foreground">
|
|
1927
|
-
|
|
2021
|
+
{{ t("integration.nodeInspector.output.pin.hint") }}
|
|
1928
2022
|
</span>
|
|
1929
2023
|
</div>
|
|
1930
2024
|
</template>
|
|
1931
2025
|
<template v-else>
|
|
1932
2026
|
<Textarea
|
|
1933
2027
|
v-model="pinDraft"
|
|
1934
|
-
aria-label="
|
|
2028
|
+
:aria-label="t('integration.nodeInspector.output.pin.editorAria')"
|
|
1935
2029
|
:rows="10"
|
|
1936
2030
|
spellcheck="false"
|
|
1937
2031
|
class="rounded-md border border-black/[0.08] ring-0 shadow-none bg-white font-mono text-xs"
|
|
@@ -1941,10 +2035,10 @@ async function runTestStep() {
|
|
|
1941
2035
|
</p>
|
|
1942
2036
|
<div class="flex items-center gap-2">
|
|
1943
2037
|
<Button size="sm" class="h-8" @click="savePin">
|
|
1944
|
-
|
|
2038
|
+
{{ t("integration.nodeInspector.output.pin.saveAction") }}
|
|
1945
2039
|
</Button>
|
|
1946
2040
|
<Button size="sm" variant="outline" class="h-8" @click="pinEditorOpen = false">
|
|
1947
|
-
|
|
2041
|
+
{{ t("integration.common.action.cancel") }}
|
|
1948
2042
|
</Button>
|
|
1949
2043
|
</div>
|
|
1950
2044
|
</template>
|
|
@@ -1973,24 +2067,24 @@ async function runTestStep() {
|
|
|
1973
2067
|
<Button
|
|
1974
2068
|
variant="ghost"
|
|
1975
2069
|
size="sm"
|
|
1976
|
-
aria-label="
|
|
2070
|
+
:aria-label="t('integration.nodeInspector.footer.removeFromWorkflow')"
|
|
1977
2071
|
class="group h-8 gap-0 text-sm text-muted-foreground transition-colors hover:bg-rose-500/10 hover:text-rose-700 focus-visible:bg-rose-500/10 focus-visible:text-rose-700"
|
|
1978
2072
|
@click="emit('remove')"
|
|
1979
2073
|
>
|
|
1980
|
-
<
|
|
2074
|
+
<TrashBin :size="13" />
|
|
1981
2075
|
<span
|
|
1982
2076
|
class="max-w-0 overflow-hidden whitespace-nowrap opacity-0 transition-all duration-150 group-hover:ml-1.5 group-hover:max-w-48 group-hover:opacity-100 group-focus-visible:ml-1.5 group-focus-visible:max-w-48 group-focus-visible:opacity-100"
|
|
1983
2077
|
>
|
|
1984
|
-
|
|
2078
|
+
{{ t("integration.nodeInspector.footer.removeFromWorkflow") }}
|
|
1985
2079
|
</span>
|
|
1986
2080
|
</Button>
|
|
1987
2081
|
<div class="flex items-center gap-2">
|
|
1988
|
-
<span v-if="dirty" class="text-xs text-muted-foreground">
|
|
2082
|
+
<span v-if="dirty" class="text-xs text-muted-foreground">{{ t("integration.nodeInspector.footer.unsavedChanges") }}</span>
|
|
1989
2083
|
<Button variant="outline" size="sm" class="h-8" @click="emit('close')">
|
|
1990
|
-
|
|
2084
|
+
{{ t("integration.common.action.cancel") }}
|
|
1991
2085
|
</Button>
|
|
1992
2086
|
<Button size="sm" class="h-8" @click="save">
|
|
1993
|
-
|
|
2087
|
+
{{ t("integration.nodeInspector.footer.done") }}
|
|
1994
2088
|
</Button>
|
|
1995
2089
|
</div>
|
|
1996
2090
|
</div>
|