@revenexx/studio-integrations 0.5.1 → 0.6.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 +201 -0
- package/README.md +17 -4
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +15 -14
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +91 -19
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +58 -21
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +4 -8
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +4 -2
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +54 -25
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +12 -12
- package/dist/runtime/components/Integrations/Layout.vue +21 -2
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +10 -11
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +2 -5
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +2 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +160 -67
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +2 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +6 -0
- package/dist/runtime/components/Integrations/NodeInspector.vue +1075 -303
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +6 -0
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +2 -30
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/NodeMark.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/NodeMark.vue +79 -0
- package/dist/runtime/components/Integrations/NodeMark.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +42 -0
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/NodePalette.vue +100 -34
- package/dist/runtime/components/Integrations/QueryBuilder.vue +8 -1
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +3 -1
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +17 -4
- package/dist/runtime/components/Integrations/StickyNoteView.vue +2 -1
- package/dist/runtime/components/Integrations/TemplateCard.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue +109 -0
- package/dist/runtime/components/Integrations/TemplateCard.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +7 -10
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +21 -37
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +23 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +18 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +167 -46
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +20 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +4 -20
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +11 -2
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +19 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +189 -46
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +4 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +4 -20
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +15 -9
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +7 -2
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +1 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +4 -2
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +30 -17
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +47 -7
- package/dist/runtime/composables/useCredentialTypes.js +4 -4
- package/dist/runtime/composables/useIntegrationsAudit.js +4 -3
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -4
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +7 -6
- package/dist/runtime/composables/useIntegrationsNodes.js +4 -4
- package/dist/runtime/composables/useIntegrationsRuns.js +11 -6
- package/dist/runtime/composables/useIntegrationsSchedules.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsSchedules.js +10 -3
- package/dist/runtime/composables/useIntegrationsSecrets.js +10 -7
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -1
- package/dist/runtime/composables/useIntegrationsTemplates.js +12 -8
- package/dist/runtime/composables/useIntegrationsTriggers.js +6 -6
- package/dist/runtime/composables/useIntegrationsWorkflows.js +8 -4
- package/dist/runtime/composables/useLoadGate.d.ts +17 -0
- package/dist/runtime/composables/useLoadGate.js +8 -0
- package/dist/runtime/data/branchReorder.d.ts +37 -0
- package/dist/runtime/data/branchReorder.js +71 -0
- package/dist/runtime/data/handleId.d.ts +23 -0
- package/dist/runtime/data/handleId.js +6 -0
- package/dist/runtime/data/integrationTemplates.d.ts +39 -0
- package/dist/runtime/data/integrationTemplates.js +15 -0
- package/dist/runtime/data/integrationWorkflowSamples.js +1 -1
- package/dist/runtime/data/nodeIcons.d.ts +2 -0
- package/dist/runtime/data/nodeIcons.js +73 -0
- package/dist/runtime/data/nodeInputData.d.ts +16 -4
- package/dist/runtime/data/nodeInputData.js +4 -0
- package/dist/runtime/data/payloadForm.d.ts +9 -2
- package/dist/runtime/data/payloadForm.js +27 -3
- package/dist/runtime/data/portTone.d.ts +17 -0
- package/dist/runtime/data/portTone.js +7 -0
- package/dist/runtime/data/webhookAuth.d.ts +18 -0
- package/dist/runtime/data/webhookAuth.js +19 -0
- package/dist/runtime/data/workflowIconCatalog.js +2 -5
- package/dist/runtime/pages/integrations/audit.vue +91 -6
- package/dist/runtime/pages/integrations/credentials/oauth-complete.vue +6 -4
- package/dist/runtime/pages/integrations/credentials.vue +119 -21
- package/dist/runtime/pages/integrations/index.vue +49 -44
- package/dist/runtime/pages/integrations/runs.vue +1 -1
- package/dist/runtime/pages/integrations/schedules.vue +51 -7
- package/dist/runtime/pages/integrations/secrets.vue +29 -7
- package/dist/runtime/pages/integrations/templates/[slug].vue +52 -30
- package/dist/runtime/pages/integrations/templates/index.vue +57 -86
- package/dist/runtime/pages/integrations/workflows/[id].vue +196 -8
- package/dist/runtime/pages/integrations/workflows/index.vue +15 -2
- package/dist/runtime/types/integrations.d.ts +65 -6
- package/dist/runtime/utils/apiError.d.ts +47 -0
- package/dist/runtime/utils/apiError.js +32 -0
- package/dist/runtime/utils/cockpitSources.d.ts +153 -0
- package/dist/runtime/utils/cockpitSources.js +14 -0
- package/dist/runtime/utils/loadGate.d.ts +61 -0
- package/dist/runtime/utils/loadGate.js +22 -0
- package/dist/runtime/utils/localize.d.ts +29 -0
- package/dist/runtime/utils/localize.js +4 -0
- package/dist/runtime/utils/notify.d.ts +59 -0
- package/dist/runtime/utils/notify.js +21 -0
- package/dist/runtime/utils/revenexxCredentialSource.d.ts +26 -0
- package/dist/runtime/utils/revenexxCredentialSource.js +8 -0
- package/dist/runtime/utils/schedule.d.ts +141 -0
- package/dist/runtime/utils/schedule.js +207 -0
- package/dist/runtime/utils/validationFindings.d.ts +101 -0
- package/dist/runtime/utils/validationFindings.js +53 -0
- package/dist/runtime/utils/whenIdle.d.ts +42 -0
- package/dist/runtime/utils/whenIdle.js +12 -0
- package/package.json +8 -2
|
@@ -8,11 +8,14 @@ import {
|
|
|
8
8
|
Select,
|
|
9
9
|
SelectContent,
|
|
10
10
|
SelectItem,
|
|
11
|
+
SelectSeparator,
|
|
11
12
|
SelectTrigger,
|
|
12
13
|
SelectValue,
|
|
13
14
|
Switch,
|
|
14
15
|
Textarea
|
|
15
16
|
} from "@revenexx/studio";
|
|
17
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
18
|
+
import { credentialState } from "../../utils/credentialState";
|
|
16
19
|
const props = defineProps({
|
|
17
20
|
field: { type: Object, required: true },
|
|
18
21
|
config: { type: Object, required: true },
|
|
@@ -22,23 +25,19 @@ const props = defineProps({
|
|
|
22
25
|
staticKeys: { type: Array, required: false },
|
|
23
26
|
depth: { type: Number, required: false },
|
|
24
27
|
variables: { type: Object, required: false },
|
|
25
|
-
upstreamOutputs: { type: Array, required: false }
|
|
28
|
+
upstreamOutputs: { type: Array, required: false },
|
|
29
|
+
refused: { type: String, required: false }
|
|
26
30
|
});
|
|
27
31
|
const emit = defineEmits(["set"]);
|
|
28
32
|
const value = computed(() => props.config[props.field.key]);
|
|
29
33
|
function write(v) {
|
|
30
34
|
emit("set", props.field.key, v);
|
|
31
35
|
}
|
|
32
|
-
function labelText(v) {
|
|
33
|
-
if (!v) return "";
|
|
34
|
-
return typeof v === "string" ? v : v.en ?? Object.values(v)[0] ?? "";
|
|
35
|
-
}
|
|
36
36
|
const exprHint = "${{ \u2026 }}";
|
|
37
|
-
const descriptionText = computed(() =>
|
|
37
|
+
const descriptionText = computed(() => resolveLocalized(props.field.description));
|
|
38
38
|
function isEmpty(v) {
|
|
39
39
|
return v === void 0 || v === null || v === "" || Array.isArray(v) && v.length === 0;
|
|
40
40
|
}
|
|
41
|
-
const requiredMissing = computed(() => !!props.field.required && isEmpty(value.value));
|
|
42
41
|
const formatError = computed(() => {
|
|
43
42
|
const v = value.value;
|
|
44
43
|
if (isEmpty(v)) return null;
|
|
@@ -60,7 +59,8 @@ const formatError = computed(() => {
|
|
|
60
59
|
}
|
|
61
60
|
return null;
|
|
62
61
|
});
|
|
63
|
-
const
|
|
62
|
+
const fieldError = computed(() => formatError.value ?? props.refused ?? null);
|
|
63
|
+
const invalid = computed(() => !!fieldError.value);
|
|
64
64
|
const constraintHint = computed(() => {
|
|
65
65
|
const val = props.field.validation;
|
|
66
66
|
if (!val) return "";
|
|
@@ -75,7 +75,7 @@ const constraintHint = computed(() => {
|
|
|
75
75
|
}
|
|
76
76
|
return "";
|
|
77
77
|
});
|
|
78
|
-
const { credentials, load: loadCredentials } = useIntegrationsCredentials();
|
|
78
|
+
const { credentials, loaded: credentialsLoaded, load: loadCredentials } = useIntegrationsCredentials();
|
|
79
79
|
onMounted(() => {
|
|
80
80
|
void loadCredentials();
|
|
81
81
|
});
|
|
@@ -95,11 +95,36 @@ function credsForField(field) {
|
|
|
95
95
|
}
|
|
96
96
|
return list;
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const bound = computed(() => {
|
|
99
|
+
const id = value.value;
|
|
100
|
+
const c = typeof id === "string" && id ? credentials.value.find((x) => x.id === id) : void 0;
|
|
101
|
+
return c ? { credential: c, state: credentialState(c) } : null;
|
|
102
|
+
});
|
|
103
|
+
const credOptions = computed(
|
|
104
|
+
() => credsForField(props.field).map((c) => ({ credential: c, state: credentialState(c) }))
|
|
105
|
+
);
|
|
106
|
+
const UNKNOWN_CREDENTIAL = "Unknown credential \u2014 pick again";
|
|
107
|
+
function credTriggerLabel(field) {
|
|
108
|
+
const label = resolveLocalized(field.label);
|
|
109
|
+
if (bound.value) return `${label}: ${bound.value.credential.name}, ${bound.value.state.label}`;
|
|
110
|
+
if (value.value && credentialsLoaded.value) return `${label}: ${UNKNOWN_CREDENTIAL}`;
|
|
111
|
+
return label;
|
|
100
112
|
}
|
|
101
113
|
const credCreateOpen = ref(false);
|
|
102
114
|
const sftpImportOpen = ref(false);
|
|
115
|
+
const CRED_ACTION_NEW = "__action_new_credential__";
|
|
116
|
+
const CRED_ACTION_SFTP = "__action_import_sftp__";
|
|
117
|
+
function onCredentialSelect(v) {
|
|
118
|
+
if (v === CRED_ACTION_NEW) {
|
|
119
|
+
credCreateOpen.value = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (v === CRED_ACTION_SFTP) {
|
|
123
|
+
sftpImportOpen.value = true;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
write(v);
|
|
127
|
+
}
|
|
103
128
|
function acceptsSftp(field) {
|
|
104
129
|
const t = field.credentialType;
|
|
105
130
|
return t === "revenexx:sftp" || Array.isArray(t) && t.includes("revenexx:sftp");
|
|
@@ -230,8 +255,18 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
230
255
|
<template>
|
|
231
256
|
<div class="space-y-1.5">
|
|
232
257
|
<div class="flex items-center justify-between gap-2">
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
<!-- The label carries the fault as well as the control does. Only a few
|
|
259
|
+
of the widget types below are a bare input that can take a red
|
|
260
|
+
border, so a credential picker or a switch at fault would otherwise
|
|
261
|
+
be marked only by the sentence underneath.
|
|
262
|
+
|
|
263
|
+
No `for`: which control this heads depends on the field's type — one
|
|
264
|
+
of a dozen branches, some of them a group rather than a single
|
|
265
|
+
element. Each branch instead carries the same text as its own name,
|
|
266
|
+
without the required marker, which is a state and not part of a
|
|
267
|
+
name. -->
|
|
268
|
+
<Label class="text-mono" :class="invalid ? 'text-rose-600' : 'text-muted-foreground'">
|
|
269
|
+
{{ resolveLocalized(field.label) }}<span v-if="field.required" class="text-rose-500" title="Required"> *</span>
|
|
235
270
|
</Label>
|
|
236
271
|
<div class="flex shrink-0 items-center gap-1.5">
|
|
237
272
|
<div v-if="isObjectField && !showExpression" class="flex items-center rounded border border-black/[0.08] bg-white p-px">
|
|
@@ -274,11 +309,6 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
274
309
|
</span>
|
|
275
310
|
</div>
|
|
276
311
|
</div>
|
|
277
|
-
<!-- inline help (boolean uses its description as the toggle label instead) -->
|
|
278
|
-
<p v-if="descriptionText && field.type !== 'boolean'" class="text-sm text-muted-foreground leading-snug">
|
|
279
|
-
{{ descriptionText }}
|
|
280
|
-
</p>
|
|
281
|
-
|
|
282
312
|
<!-- object / plain-array fields: JSON editor (expressions stay usable
|
|
283
313
|
inside string values). Wins over expressionAllowed — an object value
|
|
284
314
|
in a text input would render as [object Object]. -->
|
|
@@ -291,11 +321,12 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
291
321
|
|
|
292
322
|
<template v-else-if="isJsonField">
|
|
293
323
|
<Textarea
|
|
324
|
+
:aria-label="resolveLocalized(field.label)"
|
|
294
325
|
:model-value="jsonDraft"
|
|
295
326
|
:rows="8"
|
|
296
327
|
spellcheck="false"
|
|
297
|
-
class="font-mono text-xs"
|
|
298
|
-
:placeholder="
|
|
328
|
+
class="rounded-md border border-black/[0.08] ring-0 shadow-none bg-white font-mono text-xs"
|
|
329
|
+
:placeholder="resolveLocalized(field.placeholder) || '{ }'"
|
|
299
330
|
@update:model-value="(v) => writeJson(String(v))"
|
|
300
331
|
/>
|
|
301
332
|
<p v-if="jsonError" class="text-sm text-amber-600">{{ jsonError }}</p>
|
|
@@ -304,20 +335,25 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
304
335
|
<!-- object field in key/value mode -->
|
|
305
336
|
<div v-else-if="isObjectField && objectMode === 'form'" class="space-y-1.5">
|
|
306
337
|
<div v-for="(row, idx) in kvRows" :key="idx" class="flex items-center gap-1.5">
|
|
338
|
+
<!-- A repeating row has no room for visible labels, so each control
|
|
339
|
+
names itself and says which row of which field it belongs to. -->
|
|
307
340
|
<Input
|
|
341
|
+
:aria-label="`${resolveLocalized(field.label)} \u2014 key of row ${idx + 1}`"
|
|
308
342
|
:model-value="row.key"
|
|
309
343
|
placeholder="key"
|
|
310
|
-
class="h-7 w-[38%] bg-white
|
|
344
|
+
class="h-7 w-[38%] rounded-md border border-black/[0.08] ring-0 shadow-none bg-white text-sm"
|
|
311
345
|
@update:model-value="(v) => setKvRow(idx, { key: String(v) })"
|
|
312
346
|
/>
|
|
313
347
|
<Input
|
|
348
|
+
:aria-label="`${resolveLocalized(field.label)} \u2014 value of row ${idx + 1}`"
|
|
314
349
|
:model-value="row.value"
|
|
315
350
|
placeholder="value"
|
|
316
|
-
class="h-7 min-w-0 flex-1 bg-white text-sm"
|
|
351
|
+
class="h-7 min-w-0 flex-1 rounded-md border border-black/[0.08] ring-0 shadow-none bg-white text-sm"
|
|
317
352
|
@update:model-value="(v) => setKvRow(idx, { value: String(v) })"
|
|
318
353
|
/>
|
|
319
354
|
<button
|
|
320
355
|
type="button"
|
|
356
|
+
:aria-label="`Remove row ${idx + 1} of ${resolveLocalized(field.label)}`"
|
|
321
357
|
class="flex size-6 shrink-0 items-center justify-center rounded text-muted-foreground transition hover:text-rose-600"
|
|
322
358
|
@click="removeKvRow(idx)"
|
|
323
359
|
>
|
|
@@ -340,9 +376,11 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
340
376
|
<IntegrationsExpressionInput
|
|
341
377
|
v-else-if="showExpression"
|
|
342
378
|
:model-value="typeof value === 'string' ? value : ''"
|
|
343
|
-
:placeholder="
|
|
379
|
+
:placeholder="resolveLocalized(field.placeholder)"
|
|
344
380
|
:variables="variables"
|
|
345
381
|
:upstream-outputs="upstreamOutputs"
|
|
382
|
+
:invalid="invalid"
|
|
383
|
+
:label="resolveLocalized(field.label)"
|
|
346
384
|
@update:model-value="(v) => write(v)"
|
|
347
385
|
/>
|
|
348
386
|
|
|
@@ -363,18 +401,21 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
363
401
|
<IntegrationsExpressionInput
|
|
364
402
|
v-else-if="field.type === 'expression' || field.expressionAllowed"
|
|
365
403
|
:model-value="value"
|
|
366
|
-
:placeholder="
|
|
404
|
+
:placeholder="resolveLocalized(field.placeholder)"
|
|
367
405
|
:variables="variables"
|
|
368
406
|
:upstream-outputs="upstreamOutputs"
|
|
407
|
+
:invalid="invalid"
|
|
408
|
+
:label="resolveLocalized(field.label)"
|
|
369
409
|
@update:model-value="(v) => write(v)"
|
|
370
410
|
/>
|
|
371
411
|
|
|
372
412
|
<!-- plain string (no expressions) -->
|
|
373
413
|
<Input
|
|
374
414
|
v-else-if="field.type === 'string'"
|
|
415
|
+
:aria-label="resolveLocalized(field.label)"
|
|
375
416
|
:model-value="value ?? ''"
|
|
376
|
-
:placeholder="
|
|
377
|
-
class="h-8 text-sm bg-white"
|
|
417
|
+
:placeholder="resolveLocalized(field.placeholder)"
|
|
418
|
+
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white"
|
|
378
419
|
:class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
|
|
379
420
|
@update:model-value="(v) => write(v)"
|
|
380
421
|
/>
|
|
@@ -382,9 +423,10 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
382
423
|
<!-- number -->
|
|
383
424
|
<Input
|
|
384
425
|
v-else-if="field.type === 'number'"
|
|
426
|
+
:aria-label="resolveLocalized(field.label)"
|
|
385
427
|
type="number"
|
|
386
428
|
:model-value="value ?? ''"
|
|
387
|
-
class="h-8 text-sm bg-white"
|
|
429
|
+
class="h-8 rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white"
|
|
388
430
|
:class="invalid ? 'border-rose-400 focus-visible:ring-rose-400/40' : ''"
|
|
389
431
|
@update:model-value="(v) => write(v === '' ? null : Number(v))"
|
|
390
432
|
/>
|
|
@@ -392,11 +434,13 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
392
434
|
<!-- boolean -->
|
|
393
435
|
<div v-else-if="field.type === 'boolean'" class="flex items-center gap-2 pt-1">
|
|
394
436
|
<Switch
|
|
437
|
+
:aria-label="resolveLocalized(field.label)"
|
|
438
|
+
class="data-[state=unchecked]:bg-slate-300"
|
|
395
439
|
:model-value="Boolean(value)"
|
|
396
440
|
@update:model-value="(v) => write(v)"
|
|
397
441
|
/>
|
|
398
442
|
<span class="text-sm text-muted-foreground">
|
|
399
|
-
{{
|
|
443
|
+
{{ resolveLocalized(field.description) }}
|
|
400
444
|
</span>
|
|
401
445
|
</div>
|
|
402
446
|
|
|
@@ -419,18 +463,18 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
419
463
|
:model-value="value ?? ''"
|
|
420
464
|
@update:model-value="(v) => write(v)"
|
|
421
465
|
>
|
|
422
|
-
<SelectTrigger class="h-8 text-sm bg-white">
|
|
423
|
-
<SelectValue :placeholder="
|
|
466
|
+
<SelectTrigger :aria-label="resolveLocalized(field.label)" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
467
|
+
<SelectValue :placeholder="resolveLocalized(field.placeholder) || 'Pick one'" />
|
|
424
468
|
</SelectTrigger>
|
|
425
469
|
<SelectContent>
|
|
426
470
|
<SelectItem v-for="opt in field.options ?? []" :key="String(opt.value)" :value="String(opt.value)">
|
|
427
|
-
{{
|
|
471
|
+
{{ resolveLocalized(opt.label) }}
|
|
428
472
|
</SelectItem>
|
|
429
473
|
</SelectContent>
|
|
430
474
|
</Select>
|
|
431
475
|
|
|
432
476
|
<!-- multiselect — pill toggles for now -->
|
|
433
|
-
<div v-else-if="field.type === 'multiselect'" class="flex flex-wrap gap-1">
|
|
477
|
+
<div v-else-if="field.type === 'multiselect'" role="group" :aria-label="resolveLocalized(field.label)" class="flex flex-wrap gap-1">
|
|
434
478
|
<button
|
|
435
479
|
v-for="opt in field.options ?? []"
|
|
436
480
|
:key="String(opt.value)"
|
|
@@ -443,7 +487,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
443
487
|
write(has ? current.filter((v) => v !== opt.value) : [...current, opt.value]);
|
|
444
488
|
}"
|
|
445
489
|
>
|
|
446
|
-
{{
|
|
490
|
+
{{ resolveLocalized(opt.label) }}
|
|
447
491
|
</button>
|
|
448
492
|
</div>
|
|
449
493
|
|
|
@@ -453,7 +497,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
453
497
|
:model-value="value ?? ''"
|
|
454
498
|
@update:model-value="(v) => write(v)"
|
|
455
499
|
>
|
|
456
|
-
<SelectTrigger class="h-8 text-sm bg-white">
|
|
500
|
+
<SelectTrigger :aria-label="resolveLocalized(field.label)" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
457
501
|
<SelectValue placeholder="Pick a secret" />
|
|
458
502
|
</SelectTrigger>
|
|
459
503
|
<SelectContent>
|
|
@@ -461,44 +505,82 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
461
505
|
</SelectContent>
|
|
462
506
|
</Select>
|
|
463
507
|
|
|
464
|
-
<!-- credentials-ref — pick a credential of the required type
|
|
508
|
+
<!-- credentials-ref — pick a credential of the required type. Compact:
|
|
509
|
+
creating and importing live INSIDE the picker as action rows; no
|
|
510
|
+
Manage link (it navigated out of the editor mid-edit). -->
|
|
465
511
|
<template v-else-if="field.type === 'credentials-ref'">
|
|
466
512
|
<Select
|
|
467
513
|
:model-value="value ?? ''"
|
|
468
|
-
@update:model-value="(v) =>
|
|
514
|
+
@update:model-value="(v) => onCredentialSelect(v)"
|
|
469
515
|
>
|
|
470
|
-
<SelectTrigger class="h-8 text-sm bg-white">
|
|
471
|
-
|
|
516
|
+
<SelectTrigger :aria-label="credTriggerLabel(field)" size="sm" class="h-8 w-full rounded-md border border-black/[0.08] ring-0 shadow-none text-sm bg-white">
|
|
517
|
+
<!-- The trigger renders the bound credential itself instead of letting
|
|
518
|
+
`SelectValue` echo the chosen row. Left to itself it shows that row's
|
|
519
|
+
`textContent`, which glues the name and the state pill into one word —
|
|
520
|
+
"Revenexx APIConnected". Rendering it here keeps the pill a pill, and
|
|
521
|
+
the placeholder has to be drawn by hand because a slot replaces the
|
|
522
|
+
fallback that would otherwise show it. -->
|
|
523
|
+
<SelectValue placeholder="Pick a credential">
|
|
524
|
+
<span v-if="bound" class="flex min-w-0 items-center gap-1.5">
|
|
525
|
+
<span class="min-w-0 truncate">{{ bound.credential.name }}</span>
|
|
526
|
+
<!-- No `title` on this badge: `SelectValue` renders itself with
|
|
527
|
+
`pointer-events: none`, so nothing inside it is ever hovered and a
|
|
528
|
+
tooltip here would be a reason nobody can reach. A failed test's
|
|
529
|
+
reason is prose beneath the field instead. -->
|
|
530
|
+
<span
|
|
531
|
+
class="shrink-0 rounded-full border px-1.5 py-px text-[10px] font-medium"
|
|
532
|
+
:class="bound.state.toneClass"
|
|
533
|
+
>
|
|
534
|
+
{{ bound.state.label }}
|
|
535
|
+
</span>
|
|
536
|
+
</span>
|
|
537
|
+
<!-- A stored id the tenant's credentials no longer contain: the
|
|
538
|
+
credential was deleted, and the field would otherwise look untouched
|
|
539
|
+
while the workflow still runs into the dead reference. `loaded`
|
|
540
|
+
keeps the wait before the first response out of it. -->
|
|
541
|
+
<span v-else-if="value && credentialsLoaded" class="text-rose-600">{{ UNKNOWN_CREDENTIAL }}</span>
|
|
542
|
+
<span v-else class="text-muted-foreground">Pick a credential</span>
|
|
543
|
+
</SelectValue>
|
|
472
544
|
</SelectTrigger>
|
|
473
545
|
<SelectContent>
|
|
474
|
-
|
|
546
|
+
<!-- The state comes from `credentialState`, the same derivation the
|
|
547
|
+
credentials table and the detail dialog use. This picker used to read
|
|
548
|
+
`status` alone and call everything that was not `needs_reauth`
|
|
549
|
+
"Connected" — including a credential nobody had ever tested, which is
|
|
550
|
+
a pass claimed for a test that never ran. -->
|
|
551
|
+
<SelectItem v-for="o in credOptions" :key="o.credential.id" :value="o.credential.id">
|
|
475
552
|
<span class="flex items-center gap-1.5">
|
|
476
|
-
<span class="
|
|
477
|
-
|
|
478
|
-
|
|
553
|
+
<span class="min-w-0 truncate">{{ o.credential.name }}</span>
|
|
554
|
+
<!-- `title` carries why a failed test failed, the way the credentials
|
|
555
|
+
table does it. Without it "Failed" is a verdict with no appeal. A
|
|
556
|
+
row is a real hit target, unlike the closed trigger above. -->
|
|
557
|
+
<span
|
|
558
|
+
class="shrink-0 rounded-full border px-1.5 py-px text-[10px] font-medium"
|
|
559
|
+
:class="o.state.toneClass"
|
|
560
|
+
:title="o.state.message ?? void 0"
|
|
561
|
+
>{{ o.state.label }}</span>
|
|
479
562
|
</span>
|
|
480
563
|
</SelectItem>
|
|
564
|
+
<div v-if="!credOptions.length" class="px-2 py-1.5 text-sm text-muted-foreground">
|
|
565
|
+
No credentials yet.
|
|
566
|
+
</div>
|
|
567
|
+
<SelectSeparator />
|
|
568
|
+
<SelectItem :value="CRED_ACTION_NEW" class="font-medium text-violet-600 data-[highlighted]:text-violet-700">
|
|
569
|
+
New credential
|
|
570
|
+
</SelectItem>
|
|
571
|
+
<SelectItem v-if="acceptsSftp(field)" :value="CRED_ACTION_SFTP" class="font-medium text-violet-600 data-[highlighted]:text-violet-700">
|
|
572
|
+
Import existing Revenue Cloud SFTP account
|
|
573
|
+
</SelectItem>
|
|
481
574
|
</SelectContent>
|
|
482
575
|
</Select>
|
|
483
|
-
|
|
576
|
+
<!-- What the state has to add, as text rather than as a tooltip — the shape
|
|
577
|
+
the detail dialog uses beneath its own badge. -->
|
|
578
|
+
<p v-if="bound?.state.key === 'needs_reauth'" class="text-sm text-amber-600">
|
|
484
579
|
This credential needs reconnect.
|
|
485
580
|
</p>
|
|
486
|
-
<
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
</button>
|
|
490
|
-
<button
|
|
491
|
-
v-if="acceptsSftp(field)"
|
|
492
|
-
type="button"
|
|
493
|
-
class="text-sm font-medium text-violet-600 hover:underline"
|
|
494
|
-
@click="sftpImportOpen = true"
|
|
495
|
-
>
|
|
496
|
-
Import SFTP account
|
|
497
|
-
</button>
|
|
498
|
-
<NuxtLink to="/integrations/credentials" class="text-sm text-muted-foreground hover:underline">
|
|
499
|
-
Manage →
|
|
500
|
-
</NuxtLink>
|
|
501
|
-
</div>
|
|
581
|
+
<p v-else-if="bound?.state.key === 'failed' && bound.state.message" class="text-sm break-words text-rose-600">
|
|
582
|
+
Last test failed: {{ bound.state.message }}
|
|
583
|
+
</p>
|
|
502
584
|
<IntegrationsCredentialCreateDialog
|
|
503
585
|
v-model:open="credCreateOpen"
|
|
504
586
|
:credential-type="field.credentialType"
|
|
@@ -524,6 +606,7 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
524
606
|
</span>
|
|
525
607
|
<button
|
|
526
608
|
type="button"
|
|
609
|
+
:aria-label="`Remove item ${idx + 1} of ${resolveLocalized(field.label)}`"
|
|
527
610
|
class="flex size-5 items-center justify-center rounded text-muted-foreground hover:text-rose-600"
|
|
528
611
|
@click="removeArrayItem(idx)"
|
|
529
612
|
>
|
|
@@ -536,11 +619,14 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
536
619
|
class="space-y-1"
|
|
537
620
|
>
|
|
538
621
|
<Label class="text-mono text-muted-foreground">
|
|
539
|
-
{{
|
|
622
|
+
{{ resolveLocalized(sub.label) }}
|
|
540
623
|
</Label>
|
|
624
|
+
<!-- The label above repeats per item, so it cannot own an id; the
|
|
625
|
+
input carries its own name including the item number. -->
|
|
541
626
|
<Input
|
|
627
|
+
:aria-label="`${resolveLocalized(sub.label)} of item ${idx + 1}`"
|
|
542
628
|
:model-value="item[sub.key] ?? ''"
|
|
543
|
-
:placeholder="
|
|
629
|
+
:placeholder="resolveLocalized(sub.placeholder)"
|
|
544
630
|
class="h-7 text-sm bg-white"
|
|
545
631
|
:class="sub.type === 'expression' ? 'font-mono' : ''"
|
|
546
632
|
@update:model-value="(v) => setArrayItemField(idx, sub.key, v)"
|
|
@@ -562,9 +648,10 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
562
648
|
<!-- fallback: JSON textarea for complex types -->
|
|
563
649
|
<Textarea
|
|
564
650
|
v-else
|
|
651
|
+
:aria-label="resolveLocalized(field.label)"
|
|
565
652
|
:model-value="value === void 0 ? '' : JSON.stringify(value, null, 2)"
|
|
566
653
|
rows="3"
|
|
567
|
-
class="text-sm font-mono bg-white"
|
|
654
|
+
class="rounded-md border border-black/[0.08] ring-0 shadow-none text-sm font-mono bg-white"
|
|
568
655
|
@update:model-value="(v) => {
|
|
569
656
|
try {
|
|
570
657
|
write(v ? JSON.parse(String(v)) : void 0);
|
|
@@ -573,12 +660,18 @@ function setArrayItemField(index, fieldKey, v) {
|
|
|
573
660
|
}"
|
|
574
661
|
/>
|
|
575
662
|
|
|
576
|
-
<!--
|
|
577
|
-
|
|
578
|
-
|
|
663
|
+
<!-- inline help BELOW the control, quiet — same pattern as the Settings
|
|
664
|
+
tab (boolean uses its description as the toggle label instead) -->
|
|
665
|
+
<p v-if="descriptionText && field.type !== 'boolean'" class="text-xs text-muted-foreground leading-snug">
|
|
666
|
+
{{ descriptionText }}
|
|
579
667
|
</p>
|
|
580
|
-
|
|
581
|
-
|
|
668
|
+
|
|
669
|
+
<!-- validation feedback: the error (local format violation, else what the
|
|
670
|
+
last save refused), otherwise the constraint hint. A required field the
|
|
671
|
+
user has simply not reached yet says nothing beyond the * on its
|
|
672
|
+
label — the nagging would outnumber the help. -->
|
|
673
|
+
<p v-if="fieldError" class="text-sm text-rose-600">
|
|
674
|
+
{{ fieldError }}
|
|
582
675
|
</p>
|
|
583
676
|
<p v-else-if="constraintHint" class="text-sm text-muted-foreground">
|
|
584
677
|
{{ constraintHint }}
|
|
@@ -14,6 +14,8 @@ type __VLS_Props = {
|
|
|
14
14
|
output: string;
|
|
15
15
|
fields?: string[];
|
|
16
16
|
}>;
|
|
17
|
+
/** Why the last save refused this setting, if it did. */
|
|
18
|
+
refused?: string;
|
|
17
19
|
};
|
|
18
20
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
21
|
set: (key: string, value: unknown) => any;
|
|
@@ -20,6 +20,12 @@ type __VLS_Props = {
|
|
|
20
20
|
dynamicOutputs?: Record<string, NodeManifestOutput[]>;
|
|
21
21
|
/** Workflow id — enables sampling real values from the latest run. */
|
|
22
22
|
workflowId?: number | string;
|
|
23
|
+
/**
|
|
24
|
+
* Config key → why the LAST SAVE refused it. Not live validation: the server
|
|
25
|
+
* is the only judge, so a fix shows once it is saved. Absent for a node the
|
|
26
|
+
* last save had no complaint about.
|
|
27
|
+
*/
|
|
28
|
+
refusedFields?: Record<string, string>;
|
|
23
29
|
};
|
|
24
30
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
25
31
|
close: () => any;
|