@revenexx/studio-integrations 1.1.0 → 1.2.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 +520 -0
- package/README.md +3 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +6 -18
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +13 -23
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +7 -6
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +15 -4
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +77 -22
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +5 -4
- package/dist/runtime/components/Integrations/LoadingAnnouncement.d.vue.ts +38 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue +16 -0
- package/dist/runtime/components/Integrations/LoadingAnnouncement.vue.d.ts +38 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +23 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +25 -32
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +5 -3
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +5 -17
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +30 -8
- package/dist/runtime/components/Integrations/RunResponseModal.vue +92 -24
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +30 -8
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +61 -3
- package/dist/runtime/components/Integrations/RunsTable.vue +143 -20
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +61 -3
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +10 -1
- package/dist/runtime/components/Integrations/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +39 -28
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +2 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +30 -11
- package/dist/runtime/i18n/messages/en.json +27 -8
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +70 -12
- package/dist/runtime/pages/integrations/runs.vue +116 -7
- package/dist/runtime/pages/integrations/secrets.vue +2 -1
- package/dist/runtime/pages/integrations/templates/[slug].vue +2 -1
- package/dist/runtime/pages/integrations/templates/index.vue +18 -15
- package/dist/runtime/pages/integrations/workflows/[id].vue +177 -28
- package/dist/runtime/types/integrations.d.ts +5 -0
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/statusStyle.d.ts +24 -0
- package/dist/runtime/utils/statusStyle.js +1 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { toast } from "vue-sonner";
|
|
26
26
|
import { REVENEXX_SOURCE_KIND_BY_SLUG } from "../../utils/revenexxCredentialSource";
|
|
27
27
|
import { cockpitSources } from "../../utils/cockpitSources";
|
|
28
|
+
import { errorReason } from "../../utils/apiError";
|
|
28
29
|
const props = defineProps({
|
|
29
30
|
type: { type: [Object, null], required: true }
|
|
30
31
|
});
|
|
@@ -93,7 +94,7 @@ async function ensureLists() {
|
|
|
93
94
|
await fetchMarkets();
|
|
94
95
|
}
|
|
95
96
|
} catch (e) {
|
|
96
|
-
toast.error(e
|
|
97
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.loadResources")));
|
|
97
98
|
} finally {
|
|
98
99
|
loadingList.value = false;
|
|
99
100
|
}
|
|
@@ -124,7 +125,7 @@ async function applySftp() {
|
|
|
124
125
|
});
|
|
125
126
|
applied.value = `${acc.login_username}@${sftpHost}:${sftpPort}`;
|
|
126
127
|
} catch (e) {
|
|
127
|
-
toast.error(e
|
|
128
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.readSftpPassword")));
|
|
128
129
|
} finally {
|
|
129
130
|
applying.value = false;
|
|
130
131
|
}
|
|
@@ -147,7 +148,7 @@ async function applySmtp() {
|
|
|
147
148
|
});
|
|
148
149
|
applied.value = `${cred.smtp_username}@${mailSmtpHost}:${mailSmtpPort}`;
|
|
149
150
|
} catch (e) {
|
|
150
|
-
toast.error(e
|
|
151
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.readMailCredential")));
|
|
151
152
|
} finally {
|
|
152
153
|
applying.value = false;
|
|
153
154
|
}
|
|
@@ -202,7 +203,7 @@ async function createSftp() {
|
|
|
202
203
|
createOpen.value = false;
|
|
203
204
|
toast.success(t("integration.credentials.revenexxSource.toast.sftpCreated"));
|
|
204
205
|
} catch (e) {
|
|
205
|
-
toast.error(e
|
|
206
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createSftp")));
|
|
206
207
|
} finally {
|
|
207
208
|
creating.value = false;
|
|
208
209
|
provisioningNote.value = "";
|
|
@@ -230,7 +231,7 @@ async function createSmtp() {
|
|
|
230
231
|
createOpen.value = false;
|
|
231
232
|
toast.success(t("integration.credentials.revenexxSource.toast.smtpCreated"));
|
|
232
233
|
} catch (e) {
|
|
233
|
-
toast.error(e
|
|
234
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createSmtp")));
|
|
234
235
|
} finally {
|
|
235
236
|
creating.value = false;
|
|
236
237
|
}
|
|
@@ -246,7 +247,7 @@ async function createApiKey() {
|
|
|
246
247
|
createOpen.value = false;
|
|
247
248
|
toast.success(t("integration.credentials.revenexxSource.toast.apiKeyCreated"));
|
|
248
249
|
} catch (e) {
|
|
249
|
-
toast.error(e
|
|
250
|
+
toast.error(errorReason(e, t("integration.credentials.revenexxSource.error.createApiKey")));
|
|
250
251
|
} finally {
|
|
251
252
|
creating.value = false;
|
|
252
253
|
}
|
|
@@ -3,7 +3,9 @@ import { onMounted, ref, watch } from "vue";
|
|
|
3
3
|
import { RefreshCircle as Loader2, Refresh as ReplayIcon, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
4
|
import { Badge, Button } from "@revenexx/studio";
|
|
5
5
|
import { toast } from "vue-sonner";
|
|
6
|
+
import LoadingAnnouncement from "./LoadingAnnouncement.vue";
|
|
6
7
|
import { deadLetterStatusClass, deadLetterStatusLabel } from "../../utils/statusStyle";
|
|
8
|
+
import { errorReason } from "../../utils/apiError";
|
|
7
9
|
const props = defineProps({
|
|
8
10
|
workflowId: { type: Number, required: true }
|
|
9
11
|
});
|
|
@@ -27,7 +29,7 @@ async function load(p = page.value) {
|
|
|
27
29
|
lastPage.value = res.lastPage;
|
|
28
30
|
page.value = res.page;
|
|
29
31
|
} catch (e) {
|
|
30
|
-
error.value = e
|
|
32
|
+
error.value = errorReason(e, t("integration.deadLetters.error.load"));
|
|
31
33
|
} finally {
|
|
32
34
|
loading.value = false;
|
|
33
35
|
}
|
|
@@ -46,7 +48,7 @@ async function onReplay(dl) {
|
|
|
46
48
|
toast.success(newId ? t("integration.deadLetters.toast.replay.started", { id: newId }) : t("integration.deadLetters.toast.replay.done"));
|
|
47
49
|
await load();
|
|
48
50
|
} catch (e) {
|
|
49
|
-
toast.error(e
|
|
51
|
+
toast.error(errorReason(e, t("integration.deadLetters.toast.replay.error")));
|
|
50
52
|
} finally {
|
|
51
53
|
busyId.value = null;
|
|
52
54
|
}
|
|
@@ -58,7 +60,7 @@ async function onDiscard(dl) {
|
|
|
58
60
|
toast.success(t("integration.deadLetters.toast.discard.done"));
|
|
59
61
|
await load();
|
|
60
62
|
} catch (e) {
|
|
61
|
-
toast.error(e
|
|
63
|
+
toast.error(errorReason(e, t("integration.deadLetters.toast.discard.error")));
|
|
62
64
|
} finally {
|
|
63
65
|
busyId.value = null;
|
|
64
66
|
}
|
|
@@ -75,6 +77,15 @@ function fmt(iso) {
|
|
|
75
77
|
|
|
76
78
|
<template>
|
|
77
79
|
<div class="space-y-3">
|
|
80
|
+
<!-- Outside the branch below, so the words are on the page before the answer is
|
|
81
|
+
(PO-251). This panel is the workflow editor's own copy of the missed-runs
|
|
82
|
+
list; the region is named after the panel rather than the tab. -->
|
|
83
|
+
<LoadingAnnouncement
|
|
84
|
+
:label="t('integration.deadLetters.panel.title')"
|
|
85
|
+
:loading="loading && !items.length"
|
|
86
|
+
:text="t('integration.deadLetters.state.loading')"
|
|
87
|
+
/>
|
|
88
|
+
|
|
78
89
|
<div class="flex items-center justify-between">
|
|
79
90
|
<div>
|
|
80
91
|
<h3 class="text-sm font-semibold">{{ t("integration.deadLetters.panel.title") }}</h3>
|
|
@@ -92,7 +103,7 @@ function fmt(iso) {
|
|
|
92
103
|
{{ error }}
|
|
93
104
|
</div>
|
|
94
105
|
|
|
95
|
-
<div v-if="loading && !items.length">
|
|
106
|
+
<div v-if="loading && !items.length" aria-hidden="true">
|
|
96
107
|
<div class="rounded-lg border border-black/[0.06] bg-white divide-y divide-black/[0.04]">
|
|
97
108
|
<div v-for="n in 4" :key="`sk-${n}`" class="flex items-start gap-3 px-3 py-3">
|
|
98
109
|
<div class="h-5 w-16 shrink-0 rounded bg-black/[0.07] animate-pulse" />
|
|
@@ -14,6 +14,10 @@ type __VLS_Props = {
|
|
|
14
14
|
invalid?: boolean;
|
|
15
15
|
/** The setting's label, so the editable region has an accessible name. */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** The setting takes several lines: Enter opens one instead of being
|
|
18
|
+
* swallowed, and the box opens at four rows and grows to ten before it
|
|
19
|
+
* scrolls. */
|
|
20
|
+
multiline?: boolean;
|
|
17
21
|
};
|
|
18
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
23
|
"update:modelValue": (value: string) => any;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
2
|
+
import { computed, inject, onBeforeUnmount, onMounted, ref, useId, watch } from "vue";
|
|
3
3
|
import { Editor, EditorContent } from "@tiptap/vue-3";
|
|
4
4
|
import { Node } from "@tiptap/core";
|
|
5
5
|
import Document from "@tiptap/extension-document";
|
|
@@ -14,7 +14,8 @@ const props = defineProps({
|
|
|
14
14
|
variables: { type: Object, required: false },
|
|
15
15
|
upstreamOutputs: { type: Array, required: false },
|
|
16
16
|
invalid: { type: Boolean, required: false },
|
|
17
|
-
label: { type: String, required: false }
|
|
17
|
+
label: { type: String, required: false },
|
|
18
|
+
multiline: { type: Boolean, required: false }
|
|
18
19
|
});
|
|
19
20
|
const emit = defineEmits(["update:modelValue"]);
|
|
20
21
|
const t = useIntegrationsT();
|
|
@@ -76,23 +77,40 @@ const ExprToken = Node.create({
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
});
|
|
80
|
+
const lineMode = () => ({ multiline: props.multiline });
|
|
81
|
+
function editableAttributes() {
|
|
82
|
+
return {
|
|
83
|
+
"role": "textbox",
|
|
84
|
+
...props.multiline ? { "aria-multiline": "true" } : {},
|
|
85
|
+
...props.label ? { "aria-label": props.label } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function endOfLine(line) {
|
|
89
|
+
const doc = editor.state.doc;
|
|
90
|
+
const index = Math.min(Math.max(line, 0), doc.childCount - 1);
|
|
91
|
+
let pos = 0;
|
|
92
|
+
doc.forEach((node, offset, i) => {
|
|
93
|
+
if (i === index) pos = offset + node.nodeSize - 1;
|
|
94
|
+
});
|
|
95
|
+
return pos;
|
|
96
|
+
}
|
|
79
97
|
const editor = new Editor({
|
|
80
98
|
extensions: [Document, Paragraph, Text, History, ExprToken],
|
|
81
|
-
content: textToDoc(text.value),
|
|
99
|
+
content: textToDoc(text.value, lineMode()),
|
|
82
100
|
editorProps: {
|
|
83
|
-
// Single line: swallow Enter (paragraph splits would break the field).
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// otherwise be an anonymous blob to a screen reader.
|
|
88
|
-
attributes: { role: "textbox", ...props.label ? { "aria-label": props.label } : {} }
|
|
101
|
+
// Single line: swallow Enter (paragraph splits would break the field). A
|
|
102
|
+
// multiline field wants exactly that split, so it lets the key through.
|
|
103
|
+
handleKeyDown: (_view, event) => !props.multiline && event.key === "Enter" && !event.isComposing,
|
|
104
|
+
attributes: editableAttributes()
|
|
89
105
|
},
|
|
90
106
|
onUpdate({ editor: ed }) {
|
|
91
107
|
const json = ed.getJSON();
|
|
92
|
-
let raw = docToText(json);
|
|
108
|
+
let raw = docToText(json, lineMode());
|
|
93
109
|
if (hasRawTokenText(json)) {
|
|
94
|
-
ed.
|
|
95
|
-
raw
|
|
110
|
+
const line = ed.state.selection.$from.index(0);
|
|
111
|
+
ed.commands.setContent(textToDoc(raw, lineMode()));
|
|
112
|
+
ed.commands.setTextSelection(endOfLine(line));
|
|
113
|
+
raw = docToText(ed.getJSON(), lineMode());
|
|
96
114
|
}
|
|
97
115
|
if (raw !== text.value) emit("update:modelValue", raw);
|
|
98
116
|
},
|
|
@@ -106,10 +124,14 @@ onBeforeUnmount(() => {
|
|
|
106
124
|
editor.destroy();
|
|
107
125
|
});
|
|
108
126
|
watch(text, (t2) => {
|
|
109
|
-
if (docToText(editor.getJSON()) !== t2) {
|
|
110
|
-
editor.commands.setContent(textToDoc(t2));
|
|
127
|
+
if (docToText(editor.getJSON(), lineMode()) !== t2) {
|
|
128
|
+
editor.commands.setContent(textToDoc(t2, lineMode()));
|
|
111
129
|
}
|
|
112
130
|
});
|
|
131
|
+
watch(() => props.multiline, () => {
|
|
132
|
+
editor.setOptions({ editorProps: { ...editor.options.editorProps, attributes: editableAttributes() } });
|
|
133
|
+
editor.commands.setContent(textToDoc(text.value, lineMode()));
|
|
134
|
+
});
|
|
113
135
|
function insertToken(expr) {
|
|
114
136
|
if (editor.isDestroyed) return;
|
|
115
137
|
editor.chain().focus().insertContent({ type: EXPR_TOKEN_NODE, attrs: { token: expr } }).run();
|
|
@@ -158,10 +180,16 @@ const filteredSections = computed(() => {
|
|
|
158
180
|
});
|
|
159
181
|
const open = ref(false);
|
|
160
182
|
const wrapRef = ref(null);
|
|
183
|
+
const triggerRef = ref(null);
|
|
184
|
+
const listOpen = computed(() => open.value && hasSuggestions.value);
|
|
185
|
+
const pickerId = useId();
|
|
161
186
|
function toggle() {
|
|
162
187
|
if (!open.value && !hasSuggestions.value) return;
|
|
163
188
|
open.value = !open.value;
|
|
164
189
|
}
|
|
190
|
+
function onTriggerClick(e) {
|
|
191
|
+
if (e.detail === 0) toggle();
|
|
192
|
+
}
|
|
165
193
|
function onFocusOut(e) {
|
|
166
194
|
if (wrapRef.value && e.relatedTarget instanceof globalThis.Node && wrapRef.value.contains(e.relatedTarget)) return;
|
|
167
195
|
open.value = false;
|
|
@@ -173,6 +201,13 @@ function onKeydown(e) {
|
|
|
173
201
|
open.value = false;
|
|
174
202
|
}
|
|
175
203
|
}
|
|
204
|
+
function onPickerKeydown(e) {
|
|
205
|
+
if (e.key !== "Escape" || !open.value) return;
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
e.preventDefault();
|
|
208
|
+
open.value = false;
|
|
209
|
+
triggerRef.value?.focus();
|
|
210
|
+
}
|
|
176
211
|
function onDocMousedown(e) {
|
|
177
212
|
if (wrapRef.value && !wrapRef.value.contains(e.target)) open.value = false;
|
|
178
213
|
}
|
|
@@ -188,24 +223,42 @@ function insert(expr) {
|
|
|
188
223
|
</script>
|
|
189
224
|
|
|
190
225
|
<template>
|
|
191
|
-
|
|
226
|
+
<!-- `keydown` on the widget rather than on the editor alone: with focus on the
|
|
227
|
+
trigger, Escape used to reach the configuration lightbox and close the whole
|
|
228
|
+
dialog. The editor keeps its own handler, which stops the event before it
|
|
229
|
+
arrives here. -->
|
|
230
|
+
<div ref="wrapRef" class="relative" @focusout="onFocusOut" @keydown="onKeydown">
|
|
192
231
|
<div class="flex items-center gap-1">
|
|
193
232
|
<div
|
|
194
|
-
class="expr-editor relative
|
|
195
|
-
:class="
|
|
233
|
+
class="expr-editor relative min-w-0 flex-1 rounded-md border bg-white px-2 text-sm transition focus-within:outline-2 focus-within:-outline-offset-1 focus-within:outline-ring"
|
|
234
|
+
:class="[
|
|
235
|
+
multiline ? 'expr-multiline' : 'h-8 overflow-hidden',
|
|
236
|
+
invalid ? 'border-rose-400' : 'border-black/[0.08]'
|
|
237
|
+
]"
|
|
196
238
|
>
|
|
197
239
|
<span
|
|
198
240
|
v-if="!text"
|
|
199
|
-
class="pointer-events-none absolute left-2
|
|
241
|
+
class="pointer-events-none absolute left-2 truncate text-muted-foreground/60"
|
|
242
|
+
:class="multiline ? 'top-1' : 'top-1/2 -translate-y-1/2'"
|
|
200
243
|
>{{ placeholder }}</span>
|
|
201
|
-
<EditorContent :editor="editor" class="h-full" @keydown="onKeydown" />
|
|
244
|
+
<EditorContent :editor="editor" :class="multiline ? '' : 'h-full'" @keydown="onKeydown" />
|
|
202
245
|
</div>
|
|
246
|
+
<!-- The name says what the button does AND what it opens, because the glyph
|
|
247
|
+
says neither; `aria-expanded` says whether the list is there, and
|
|
248
|
+
`aria-controls` only while it is — an id pointing at nothing is worse
|
|
249
|
+
than no link. See the docblock on `listOpen` for why this is not the
|
|
250
|
+
shared popover. -->
|
|
203
251
|
<button
|
|
252
|
+
ref="triggerRef"
|
|
204
253
|
type="button"
|
|
205
254
|
:title="t('integration.nodeConfig.expression.insertTitle')"
|
|
255
|
+
:aria-label="t('integration.nodeConfig.expression.insertAria')"
|
|
256
|
+
:aria-expanded="listOpen"
|
|
257
|
+
:aria-controls="listOpen ? pickerId : void 0"
|
|
206
258
|
class="flex h-8 shrink-0 items-center rounded-md border border-black/[0.08] bg-white px-1.5 font-mono text-sm text-muted-foreground transition hover:text-foreground hover:bg-muted/30"
|
|
207
|
-
:class="
|
|
259
|
+
:class="[listOpen ? 'ring-2 ring-emerald-500/40' : '', multiline ? 'self-start' : '']"
|
|
208
260
|
@mousedown.prevent="toggle"
|
|
261
|
+
@click="onTriggerClick"
|
|
209
262
|
>
|
|
210
263
|
{{ triggerLabel }}
|
|
211
264
|
</button>
|
|
@@ -224,8 +277,10 @@ function insert(expr) {
|
|
|
224
277
|
here and in the panel beside the form — and a popup nothing can address
|
|
225
278
|
is a popup nothing can test either. -->
|
|
226
279
|
<div
|
|
227
|
-
v-if="
|
|
280
|
+
v-if="listOpen"
|
|
281
|
+
:id="pickerId"
|
|
228
282
|
role="group"
|
|
283
|
+
@keydown="onPickerKeydown"
|
|
229
284
|
:aria-label="t('integration.nodeConfig.expression.picker.aria')"
|
|
230
285
|
class="absolute right-0 top-full z-50 mt-1 flex max-h-72 w-80 flex-col rounded-md border border-black/[0.08] bg-white text-sm shadow-lg"
|
|
231
286
|
>
|
|
@@ -267,5 +322,5 @@ function insert(expr) {
|
|
|
267
322
|
</template>
|
|
268
323
|
|
|
269
324
|
<style>
|
|
270
|
-
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-flex;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;height:20px;line-height:1;margin:0 2px;padding:0 7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
325
|
+
.expr-editor .ProseMirror{align-items:center;display:flex;height:100%;outline:none;overflow-x:auto;scrollbar-width:none}.expr-editor .ProseMirror p{line-height:30px;margin:0;white-space:pre}.expr-editor.expr-multiline{--expr-line:1.375rem;max-height:calc(var(--expr-line)*10 + .5rem);min-height:calc(var(--expr-line)*4 + .5rem);overflow-x:hidden;overflow-y:auto;padding-bottom:.25rem;padding-top:.25rem}.expr-editor.expr-multiline .ProseMirror{display:block;height:auto;overflow-x:visible}.expr-editor.expr-multiline .ProseMirror p{line-height:var(--expr-line);white-space:pre-wrap}.expr-pill{align-items:center;background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.25);border-radius:6px;color:#047857;display:inline-flex;font-family:ui-sans-serif,system-ui,sans-serif;font-size:11px;height:20px;line-height:1;margin:0 2px;padding:0 7px;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.expr-pill.ProseMirror-selectednode{outline:2px solid rgba(16,185,129,.5)}
|
|
271
326
|
</style>
|
|
@@ -14,6 +14,10 @@ type __VLS_Props = {
|
|
|
14
14
|
invalid?: boolean;
|
|
15
15
|
/** The setting's label, so the editable region has an accessible name. */
|
|
16
16
|
label?: string;
|
|
17
|
+
/** The setting takes several lines: Enter opens one instead of being
|
|
18
|
+
* swallowed, and the box opens at four rows and grows to ten before it
|
|
19
|
+
* scrolls. */
|
|
20
|
+
multiline?: boolean;
|
|
17
21
|
};
|
|
18
22
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
23
|
"update:modelValue": (value: string) => any;
|
|
@@ -4,6 +4,7 @@ import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import { ChevronsDownUp, ChevronsUpDown, Ungroup } from "lucide-vue-next";
|
|
6
6
|
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
7
8
|
import { resolveDisplayName } from "../../utils/localize";
|
|
8
9
|
const props = defineProps({
|
|
9
10
|
id: { type: String, required: true },
|
|
@@ -100,9 +101,9 @@ function onResizeEnd() {
|
|
|
100
101
|
v-for="(c, i) in GROUP_COLORS"
|
|
101
102
|
:key="i"
|
|
102
103
|
type="button"
|
|
103
|
-
class="size-
|
|
104
|
+
class="size-6 rounded-full border transition hover:scale-110"
|
|
104
105
|
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
105
|
-
:style="{ backgroundColor: c.chip, borderColor: c.border
|
|
106
|
+
:style="{ backgroundColor: c.chip, borderColor: c.border }"
|
|
106
107
|
:title="t(c.nameKey)"
|
|
107
108
|
:aria-label="t(c.nameKey)"
|
|
108
109
|
@click="data.actions?.setColor(i)"
|
|
@@ -138,7 +139,7 @@ function onResizeEnd() {
|
|
|
138
139
|
<!-- Collapsed: compact stand-in card with generic edge handles. -->
|
|
139
140
|
<template v-if="collapsed">
|
|
140
141
|
<Handle
|
|
141
|
-
id="
|
|
142
|
+
:id="inputHandleId('top')"
|
|
142
143
|
type="target"
|
|
143
144
|
:position="Position.Top"
|
|
144
145
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
@@ -165,7 +166,7 @@ function onResizeEnd() {
|
|
|
165
166
|
</div>
|
|
166
167
|
</div>
|
|
167
168
|
<Handle
|
|
168
|
-
id="
|
|
169
|
+
:id="outputHandleId('default', 'bottom')"
|
|
169
170
|
type="source"
|
|
170
171
|
:position="Position.Bottom"
|
|
171
172
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a surface is doing, in words, for somebody who cannot see the shimmer.
|
|
3
|
+
*
|
|
4
|
+
* A studio surface that is waiting draws placeholder blocks and — before PO-251 —
|
|
5
|
+
* said nothing, which is exactly what it says when there is genuinely nothing to
|
|
6
|
+
* show. Those two states ask for opposite reactions: wait, or go and do something
|
|
7
|
+
* about it. The shimmer separates them for a sighted reader and is not information a
|
|
8
|
+
* screen reader can reach.
|
|
9
|
+
*
|
|
10
|
+
* Three things this component exists to keep from drifting, all of them learned on
|
|
11
|
+
* the template gallery (PO-246), which was the first surface to get this right:
|
|
12
|
+
*
|
|
13
|
+
* - **It is always in the DOM**, and only its text changes. A live region that
|
|
14
|
+
* appears together with its content is a region assistive technology may never
|
|
15
|
+
* have been watching: what gets announced is a change *inside* an existing region.
|
|
16
|
+
* So mount this outside the `v-if` chain it describes, never inside the branch
|
|
17
|
+
* that draws the placeholders.
|
|
18
|
+
* - **It is named**, because a page can hold more than one. The template gallery
|
|
19
|
+
* draws one per card for its flow miniature; the dashboard has four panels that
|
|
20
|
+
* each load on their own. Two unnamed status regions on one page are two things
|
|
21
|
+
* neither a reader nor a test can tell apart.
|
|
22
|
+
* - **`aria-busy` is not what this is.** The attribute only asks assistive
|
|
23
|
+
* technology to defer exposing changes to an element, and on a role-less `div`
|
|
24
|
+
* that is not a live region NVDA and JAWS effectively ignore it — it expresses the
|
|
25
|
+
* state without keeping anything from being walked. The placeholders themselves
|
|
26
|
+
* get `aria-hidden` at the call site instead: a placeholder carries no
|
|
27
|
+
* information, so there is nothing to lose by removing it from the tree.
|
|
28
|
+
*/
|
|
29
|
+
type __VLS_Props = {
|
|
30
|
+
/** Names the region, so several on one page stay distinguishable. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Whether the surface this speaks for is waiting. */
|
|
33
|
+
loading: boolean;
|
|
34
|
+
/** What to say while it is — "Loading the runs…", in the surface's own words. */
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
label: { type: String, required: true },
|
|
4
|
+
loading: { type: Boolean, required: true },
|
|
5
|
+
text: { type: String, required: true }
|
|
6
|
+
});
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<p
|
|
11
|
+
role="status"
|
|
12
|
+
aria-live="polite"
|
|
13
|
+
:aria-label="props.label"
|
|
14
|
+
class="sr-only"
|
|
15
|
+
>{{ props.loading ? props.text : "" }}</p>
|
|
16
|
+
</template>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a surface is doing, in words, for somebody who cannot see the shimmer.
|
|
3
|
+
*
|
|
4
|
+
* A studio surface that is waiting draws placeholder blocks and — before PO-251 —
|
|
5
|
+
* said nothing, which is exactly what it says when there is genuinely nothing to
|
|
6
|
+
* show. Those two states ask for opposite reactions: wait, or go and do something
|
|
7
|
+
* about it. The shimmer separates them for a sighted reader and is not information a
|
|
8
|
+
* screen reader can reach.
|
|
9
|
+
*
|
|
10
|
+
* Three things this component exists to keep from drifting, all of them learned on
|
|
11
|
+
* the template gallery (PO-246), which was the first surface to get this right:
|
|
12
|
+
*
|
|
13
|
+
* - **It is always in the DOM**, and only its text changes. A live region that
|
|
14
|
+
* appears together with its content is a region assistive technology may never
|
|
15
|
+
* have been watching: what gets announced is a change *inside* an existing region.
|
|
16
|
+
* So mount this outside the `v-if` chain it describes, never inside the branch
|
|
17
|
+
* that draws the placeholders.
|
|
18
|
+
* - **It is named**, because a page can hold more than one. The template gallery
|
|
19
|
+
* draws one per card for its flow miniature; the dashboard has four panels that
|
|
20
|
+
* each load on their own. Two unnamed status regions on one page are two things
|
|
21
|
+
* neither a reader nor a test can tell apart.
|
|
22
|
+
* - **`aria-busy` is not what this is.** The attribute only asks assistive
|
|
23
|
+
* technology to defer exposing changes to an element, and on a role-less `div`
|
|
24
|
+
* that is not a live region NVDA and JAWS effectively ignore it — it expresses the
|
|
25
|
+
* state without keeping anything from being walked. The placeholders themselves
|
|
26
|
+
* get `aria-hidden` at the call site instead: a placeholder carries no
|
|
27
|
+
* information, so there is nothing to lose by removing it from the tree.
|
|
28
|
+
*/
|
|
29
|
+
type __VLS_Props = {
|
|
30
|
+
/** Names the region, so several on one page stay distinguishable. */
|
|
31
|
+
label: string;
|
|
32
|
+
/** Whether the surface this speaks for is waiting. */
|
|
33
|
+
loading: boolean;
|
|
34
|
+
/** What to say while it is — "Loading the runs…", in the surface's own words. */
|
|
35
|
+
text: string;
|
|
36
|
+
};
|
|
37
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
38
|
+
export default _default;
|
|
@@ -195,6 +195,9 @@ function removeKvRow(idx) {
|
|
|
195
195
|
kvRows.value = kvRows.value.filter((_, i) => i !== idx);
|
|
196
196
|
commitKvRows();
|
|
197
197
|
}
|
|
198
|
+
const isMultiline = computed(
|
|
199
|
+
() => Boolean(props.field.multiline) && (props.field.type === "string" || props.field.type === "expression")
|
|
200
|
+
);
|
|
198
201
|
const isQueryField = computed(
|
|
199
202
|
() => props.field.type === "array" && props.field.items?.type !== "object" && /(^|__)queries$/.test(props.field.key)
|
|
200
203
|
);
|
|
@@ -383,6 +386,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
383
386
|
:upstream-outputs="upstreamOutputs"
|
|
384
387
|
:invalid="invalid"
|
|
385
388
|
:label="resolveLocalized(field.label)"
|
|
389
|
+
:multiline="isMultiline"
|
|
386
390
|
@update:model-value="(v) => write(v)"
|
|
387
391
|
/>
|
|
388
392
|
|
|
@@ -409,6 +413,25 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
409
413
|
:upstream-outputs="upstreamOutputs"
|
|
410
414
|
:invalid="invalid"
|
|
411
415
|
:label="resolveLocalized(field.label)"
|
|
416
|
+
:multiline="isMultiline"
|
|
417
|
+
@update:model-value="(v) => write(v)"
|
|
418
|
+
/>
|
|
419
|
+
|
|
420
|
+
<!-- plain string the node says takes several lines. Same accessible name as
|
|
421
|
+
the one-line box below, because it is the same setting. Opens at four
|
|
422
|
+
rows, grows with what is typed and scrolls past ten — the same rule the
|
|
423
|
+
expression editor follows, so the two controls for one kind of setting
|
|
424
|
+
do not behave differently. The two heights are lines × 1.25rem plus the
|
|
425
|
+
1rem of vertical padding; `rows` alone would not set them, because the
|
|
426
|
+
shared control sizes itself to its content and ignores it. -->
|
|
427
|
+
<Textarea
|
|
428
|
+
v-else-if="field.type === 'string' && isMultiline"
|
|
429
|
+
:aria-label="resolveLocalized(field.label)"
|
|
430
|
+
:model-value="value ?? ''"
|
|
431
|
+
:placeholder="resolveLocalized(field.placeholder)"
|
|
432
|
+
:rows="4"
|
|
433
|
+
class="min-h-24 max-h-[13.5rem] resize-none rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white"
|
|
434
|
+
:class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
|
|
412
435
|
@update:model-value="(v) => write(v)"
|
|
413
436
|
/>
|
|
414
437
|
|
|
@@ -29,13 +29,13 @@ type __VLS_Props = {
|
|
|
29
29
|
};
|
|
30
30
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
31
|
update: (next: WorkflowTrigger | WorkflowNode) => any;
|
|
32
|
-
close: () => any;
|
|
33
32
|
remove: () => any;
|
|
33
|
+
close: () => any;
|
|
34
34
|
navigate: (id: string) => any;
|
|
35
35
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
36
|
onUpdate?: ((next: WorkflowTrigger | WorkflowNode) => any) | undefined;
|
|
37
|
-
onClose?: (() => any) | undefined;
|
|
38
37
|
onRemove?: (() => any) | undefined;
|
|
38
|
+
onClose?: (() => any) | undefined;
|
|
39
39
|
onNavigate?: ((id: string) => any) | undefined;
|
|
40
40
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
41
41
|
export default _default;
|
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
} from "../../data/nodeInputData";
|
|
40
40
|
import { portDotClass } from "../../data/portTone";
|
|
41
41
|
import { computeLoopBodies, manifestIterates } from "../../data/loopBody";
|
|
42
|
+
import { deepClone } from "../../utils/deepClone";
|
|
43
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
42
44
|
import { nodeDisplayLabel } from "../../utils/validationFindings";
|
|
43
45
|
import { authChoiceFromConfig, configPatchForAuthChoice } from "../../data/webhookAuth";
|
|
44
46
|
import {
|
|
@@ -49,6 +51,7 @@ import {
|
|
|
49
51
|
} from "../../data/nodeRunData";
|
|
50
52
|
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
51
53
|
import { describeRecurrence, parseCron, scheduleTimezone, toCron } from "../../utils/schedule";
|
|
54
|
+
import { errorReason } from "../../utils/apiError";
|
|
52
55
|
const t = useIntegrationsT();
|
|
53
56
|
const props = defineProps({
|
|
54
57
|
selection: { type: [Object, null], required: true },
|
|
@@ -64,19 +67,16 @@ const props = defineProps({
|
|
|
64
67
|
refusedFields: { type: Object, required: false }
|
|
65
68
|
});
|
|
66
69
|
const emit = defineEmits(["update", "close", "remove", "navigate"]);
|
|
67
|
-
|
|
68
|
-
return JSON.parse(JSON.stringify(v));
|
|
69
|
-
}
|
|
70
|
-
const draft = ref(clone(props.selection));
|
|
70
|
+
const draft = ref(deepClone(props.selection));
|
|
71
71
|
watch(() => props.selection?.id, () => {
|
|
72
|
-
if (props.selection && props.selection.id !== draft.value.id) draft.value =
|
|
72
|
+
if (props.selection && props.selection.id !== draft.value.id) draft.value = deepClone(props.selection);
|
|
73
73
|
});
|
|
74
74
|
const isNode = computed(() => "nodeSlug" in draft.value);
|
|
75
75
|
const draftNode = computed(() => isNode.value ? draft.value : null);
|
|
76
76
|
const draftTrigger = computed(() => isNode.value ? null : draft.value);
|
|
77
77
|
const dirty = computed(() => JSON.stringify(draft.value) !== JSON.stringify(props.selection));
|
|
78
78
|
function save() {
|
|
79
|
-
if (dirty.value) emit("update",
|
|
79
|
+
if (dirty.value) emit("update", deepClone(draft.value));
|
|
80
80
|
emit("close");
|
|
81
81
|
}
|
|
82
82
|
const nameInput = ref(null);
|
|
@@ -203,12 +203,10 @@ function patchTrigger(patch) {
|
|
|
203
203
|
}
|
|
204
204
|
async function copyConfig() {
|
|
205
205
|
if (!draftNode.value) return;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
toast.error(t("integration.nodeInspector.toast.configCopyFailed"));
|
|
211
|
-
}
|
|
206
|
+
await copyToClipboard(JSON.stringify(draftNode.value.config ?? {}, null, 2), {
|
|
207
|
+
copied: t("integration.nodeInspector.toast.configCopied"),
|
|
208
|
+
failed: t("integration.nodeInspector.toast.configCopyFailed")
|
|
209
|
+
});
|
|
212
210
|
}
|
|
213
211
|
async function pasteConfig() {
|
|
214
212
|
if (!draftNode.value) return;
|
|
@@ -440,28 +438,24 @@ async function rotateEndpointSecret() {
|
|
|
440
438
|
toast.error(t("integration.nodeInspector.toast.rotateNoSecret"));
|
|
441
439
|
}
|
|
442
440
|
} catch (e) {
|
|
443
|
-
toast.error(e
|
|
441
|
+
toast.error(errorReason(e, t("integration.nodeInspector.toast.rotateFailed")));
|
|
444
442
|
} finally {
|
|
445
443
|
rotateBusy.value = false;
|
|
446
444
|
}
|
|
447
445
|
}
|
|
448
446
|
async function copySecret() {
|
|
449
447
|
if (!rotatedSecret.value) return;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
toast.error(t("integration.nodeInspector.toast.secretCopyFailed"));
|
|
455
|
-
}
|
|
448
|
+
await copyToClipboard(rotatedSecret.value, {
|
|
449
|
+
copied: t("integration.nodeInspector.toast.secretCopied"),
|
|
450
|
+
failed: t("integration.nodeInspector.toast.secretCopyFailed")
|
|
451
|
+
});
|
|
456
452
|
}
|
|
457
453
|
async function copyWebhookUrl() {
|
|
458
454
|
if (!webhookUrl.value) return;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
toast.error(t("integration.nodeInspector.toast.webhookUrlCopyFailed"));
|
|
464
|
-
}
|
|
455
|
+
await copyToClipboard(webhookUrl.value, {
|
|
456
|
+
copied: t("integration.nodeInspector.toast.webhookUrlCopied"),
|
|
457
|
+
failed: t("integration.nodeInspector.toast.webhookUrlCopyFailed")
|
|
458
|
+
});
|
|
465
459
|
}
|
|
466
460
|
const definedEvents = typeof useDefinedEvents === "function" ? useDefinedEvents() : { emits: ref([]), load: async () => {
|
|
467
461
|
} };
|
|
@@ -526,7 +520,7 @@ async function loadRunData() {
|
|
|
526
520
|
runData.value = map;
|
|
527
521
|
runInfo.value = { id: source.id, startedAt: source.startedAt };
|
|
528
522
|
} catch (e) {
|
|
529
|
-
runError.value = e
|
|
523
|
+
runError.value = errorReason(e, t("integration.nodeInspector.input.runData.loadFailed"));
|
|
530
524
|
} finally {
|
|
531
525
|
runLoading.value = false;
|
|
532
526
|
}
|
|
@@ -640,11 +634,10 @@ function insertExpression(expression) {
|
|
|
640
634
|
el.focus();
|
|
641
635
|
return;
|
|
642
636
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
toast(t("integration.nodeInspector.toast.expressionCopied"));
|
|
637
|
+
void copyToClipboard(expression, {
|
|
638
|
+
copied: t("integration.nodeInspector.toast.expressionCopied"),
|
|
639
|
+
failed: t("integration.nodeInspector.toast.expressionCopyFailed")
|
|
640
|
+
});
|
|
648
641
|
}
|
|
649
642
|
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";
|
|
650
643
|
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";
|
|
@@ -767,7 +760,7 @@ const hasNext = computed(() => seqIndex.value >= 0 && seqIndex.value < sequence.
|
|
|
767
760
|
function navigate(offset) {
|
|
768
761
|
const target = sequence.value[seqIndex.value + offset];
|
|
769
762
|
if (!target) return;
|
|
770
|
-
if (dirty.value) emit("update",
|
|
763
|
+
if (dirty.value) emit("update", deepClone(draft.value));
|
|
771
764
|
emit("navigate", target);
|
|
772
765
|
}
|
|
773
766
|
const outputView = ref("schema");
|