@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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { computed, nextTick, ref } from "vue";
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
|
-
import { ChevronsDownUp, ChevronsUpDown,
|
|
5
|
+
import { ChevronsDownUp, ChevronsUpDown, Ungroup } from "lucide-vue-next";
|
|
6
|
+
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
6
7
|
import { resolveDisplayName } from "../../utils/localize";
|
|
7
8
|
const props = defineProps({
|
|
8
9
|
id: { type: String, required: true },
|
|
@@ -10,22 +11,28 @@ const props = defineProps({
|
|
|
10
11
|
selected: { type: Boolean, required: false }
|
|
11
12
|
});
|
|
12
13
|
defineEmits(["updateNodeInternals"]);
|
|
14
|
+
const t = useIntegrationsT();
|
|
13
15
|
const GROUP_COLORS = [
|
|
14
|
-
{
|
|
15
|
-
{
|
|
16
|
-
{
|
|
17
|
-
{
|
|
18
|
-
{
|
|
19
|
-
{
|
|
20
|
-
{
|
|
16
|
+
{ nameKey: "integration.canvas.color.amber", bg: "rgba(254,243,199,0.45)", border: "#fcd34d", chip: "#f59e0b" },
|
|
17
|
+
{ nameKey: "integration.canvas.color.orange", bg: "rgba(255,237,213,0.45)", border: "#fdba74", chip: "#f97316" },
|
|
18
|
+
{ nameKey: "integration.canvas.color.rose", bg: "rgba(255,228,230,0.45)", border: "#fda4af", chip: "#f43f5e" },
|
|
19
|
+
{ nameKey: "integration.canvas.color.green", bg: "rgba(220,252,231,0.45)", border: "#86efac", chip: "#10b981" },
|
|
20
|
+
{ nameKey: "integration.canvas.color.blue", bg: "rgba(219,234,254,0.45)", border: "#93c5fd", chip: "#3b82f6" },
|
|
21
|
+
{ nameKey: "integration.canvas.color.violet", bg: "rgba(237,233,254,0.45)", border: "#c4b5fd", chip: "#8b5cf6" },
|
|
22
|
+
{ nameKey: "integration.canvas.color.gray", bg: "rgba(245,245,244,0.6)", border: "#d6d3d1", chip: "#78716c" }
|
|
21
23
|
];
|
|
22
24
|
const color = computed(() => GROUP_COLORS[props.data.group.color ?? 4] ?? GROUP_COLORS[4]);
|
|
23
|
-
const label = computed(() => resolveDisplayName(props.data.group.label) || "
|
|
25
|
+
const label = computed(() => resolveDisplayName(props.data.group.label) || t("integration.canvas.group.defaultLabel"));
|
|
24
26
|
const collapsed = computed(() => props.data.group.collapsed === true);
|
|
25
27
|
const editable = computed(() => !!props.data.actions);
|
|
26
28
|
const editing = ref(false);
|
|
27
29
|
const draft = ref("");
|
|
28
30
|
const inputEl = ref(null);
|
|
31
|
+
function onLabelDoubleClick(event) {
|
|
32
|
+
if (!editable.value) return;
|
|
33
|
+
event.stopPropagation();
|
|
34
|
+
startRename();
|
|
35
|
+
}
|
|
29
36
|
function startRename() {
|
|
30
37
|
if (!editable.value) return;
|
|
31
38
|
draft.value = label.value;
|
|
@@ -51,7 +58,7 @@ function onHoverLeave() {
|
|
|
51
58
|
}, 250);
|
|
52
59
|
}
|
|
53
60
|
const toolbarVisible = computed(() => editable.value && !editing.value && (hovered.value || !!props.selected));
|
|
54
|
-
const memberCountText = computed(() =>
|
|
61
|
+
const memberCountText = computed(() => t("integration.canvas.group.memberCount", props.data.memberCount));
|
|
55
62
|
const flow = useVueFlow();
|
|
56
63
|
const resizing = ref(false);
|
|
57
64
|
const liveW = ref(0);
|
|
@@ -96,15 +103,15 @@ function onResizeEnd() {
|
|
|
96
103
|
class="size-4.5 rounded-full border transition hover:scale-110"
|
|
97
104
|
:class="(data.group.color ?? 4) === i ? 'ring-2 ring-black/25 ring-offset-1' : ''"
|
|
98
105
|
:style="{ backgroundColor: c.chip, borderColor: c.border, width: '16px', height: '16px' }"
|
|
99
|
-
:title="c.
|
|
100
|
-
:aria-label="c.
|
|
106
|
+
:title="t(c.nameKey)"
|
|
107
|
+
:aria-label="t(c.nameKey)"
|
|
101
108
|
@click="data.actions?.setColor(i)"
|
|
102
109
|
/>
|
|
103
110
|
<div class="mx-0.5 h-4 w-px bg-black/10" />
|
|
104
111
|
<button
|
|
105
112
|
type="button"
|
|
106
113
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
107
|
-
:title="collapsed ? '
|
|
114
|
+
:title="collapsed ? t('integration.canvas.group.action.expand') : t('integration.canvas.group.action.collapse')"
|
|
108
115
|
@click="data.actions?.toggleCollapse()"
|
|
109
116
|
>
|
|
110
117
|
<component :is="collapsed ? ChevronsUpDown : ChevronsDownUp" :size="13" />
|
|
@@ -112,7 +119,7 @@ function onResizeEnd() {
|
|
|
112
119
|
<button
|
|
113
120
|
type="button"
|
|
114
121
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
|
115
|
-
title="
|
|
122
|
+
:title="t('integration.canvas.group.action.ungroup')"
|
|
116
123
|
@click="data.actions?.ungroup()"
|
|
117
124
|
>
|
|
118
125
|
<Ungroup :size="13" />
|
|
@@ -120,10 +127,10 @@ function onResizeEnd() {
|
|
|
120
127
|
<button
|
|
121
128
|
type="button"
|
|
122
129
|
class="flex size-6 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
123
|
-
title="
|
|
130
|
+
:title="t('integration.canvas.group.action.removeFrame')"
|
|
124
131
|
@click="data.actions?.ungroup()"
|
|
125
132
|
>
|
|
126
|
-
<
|
|
133
|
+
<TrashBin :size="13" />
|
|
127
134
|
</button>
|
|
128
135
|
</div>
|
|
129
136
|
</NodeToolbar>
|
|
@@ -136,6 +143,9 @@ function onResizeEnd() {
|
|
|
136
143
|
:position="Position.Top"
|
|
137
144
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm"
|
|
138
145
|
/>
|
|
146
|
+
<!-- The collapsed card stops its own double-click unconditionally, which is
|
|
147
|
+
safe: both read-only panels build every frame with `collapsed: false`, so
|
|
148
|
+
this surface is the editor's alone. -->
|
|
139
149
|
<div
|
|
140
150
|
class="w-[220px] rounded-lg border-2 bg-white transition-all duration-150"
|
|
141
151
|
:style="{ borderColor: color.border }"
|
|
@@ -183,7 +193,7 @@ function onResizeEnd() {
|
|
|
183
193
|
v-if="editing"
|
|
184
194
|
ref="inputEl"
|
|
185
195
|
v-model="draft"
|
|
186
|
-
aria-label="
|
|
196
|
+
:aria-label="t('integration.canvas.group.input.nameAria')"
|
|
187
197
|
class="nodrag w-40 rounded border border-black/[0.1] bg-white px-1 py-0.5 text-sm font-semibold focus:outline-none focus:ring-2 focus:ring-primary/40"
|
|
188
198
|
@blur="commitRename"
|
|
189
199
|
@keydown.enter.prevent="commitRename"
|
|
@@ -194,7 +204,7 @@ function onResizeEnd() {
|
|
|
194
204
|
v-else
|
|
195
205
|
class="select-none text-sm font-semibold"
|
|
196
206
|
:style="{ color: color.chip }"
|
|
197
|
-
@dblclick
|
|
207
|
+
@dblclick="onLabelDoubleClick"
|
|
198
208
|
>
|
|
199
209
|
{{ label }}
|
|
200
210
|
</span>
|
|
@@ -206,7 +216,7 @@ function onResizeEnd() {
|
|
|
206
216
|
v-if="editable"
|
|
207
217
|
class="nodrag pointer-events-auto absolute bottom-0 right-0 flex size-5 cursor-se-resize items-end justify-end p-1"
|
|
208
218
|
:style="{ color: color.chip }"
|
|
209
|
-
title="
|
|
219
|
+
:title="t('integration.canvas.group.action.resize')"
|
|
210
220
|
@pointerdown="onResizeStart"
|
|
211
221
|
@pointermove="onResizeMove"
|
|
212
222
|
@pointerup="onResizeEnd"
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
+
import { nextTick, onMounted, ref } from "vue";
|
|
2
3
|
import Sidebar from "./Sidebar.vue";
|
|
4
|
+
const t = useIntegrationsT();
|
|
3
5
|
const { focused } = useIntegrationsFocus();
|
|
6
|
+
const animate = ref(false);
|
|
7
|
+
onMounted(() => nextTick(() => {
|
|
8
|
+
animate.value = true;
|
|
9
|
+
}));
|
|
4
10
|
</script>
|
|
5
11
|
|
|
6
12
|
<template>
|
|
@@ -10,11 +16,30 @@ const { focused } = useIntegrationsFocus();
|
|
|
10
16
|
href="#integrations-main"
|
|
11
17
|
class="sr-only focus:not-sr-only focus:absolute focus:left-3 focus:top-3 focus:z-50 focus:rounded-md focus:bg-white focus:px-3 focus:py-1.5 focus:text-sm focus:font-medium focus:ring-2 focus:ring-primary/40"
|
|
12
18
|
>
|
|
13
|
-
|
|
19
|
+
{{ t("integration.shell.action.skipToContent") }}
|
|
14
20
|
</a>
|
|
21
|
+
<!-- `inert` + `aria-hidden` are what make the fold real. The inner wrapper
|
|
22
|
+
below keeps its own `w-64` so the slide-out animates, so a folded rail
|
|
23
|
+
is merely clipped by `overflow-hidden`: its links keep their size and
|
|
24
|
+
stay focusable at a negative x, and a keyboard walks into a rail nobody
|
|
25
|
+
can see (PO-273). `display: none` would fix that too and take the
|
|
26
|
+
animation with it.
|
|
27
|
+
|
|
28
|
+
Both attributes are written to be *absent* rather than false when the
|
|
29
|
+
rail stands: `inert` is presence-based, so a renderer that emitted
|
|
30
|
+
`inert="false"` as an attribute would hold the standing rail back
|
|
31
|
+
instead. That the standing rail really does take focus is asserted in
|
|
32
|
+
its own right — specs/studio-navigation.md AC-1 is the control for AC-5.
|
|
33
|
+
|
|
34
|
+
The id is how a test reaches the rail while it is folded: out of the
|
|
35
|
+
accessibility tree, it can no longer be addressed by its role, and
|
|
36
|
+
`nav` alone would catch any navigation the page grows later. -->
|
|
15
37
|
<div
|
|
16
|
-
|
|
17
|
-
|
|
38
|
+
id="integrations-nav"
|
|
39
|
+
class="shrink-0 overflow-hidden border-r border-black/[0.06] bg-white"
|
|
40
|
+
:class="[focused ? 'w-0 !border-r-0' : 'w-64', animate ? 'transition-[width] duration-200 ease-out' : '']"
|
|
41
|
+
:inert="focused || void 0"
|
|
42
|
+
:aria-hidden="focused ? 'true' : void 0"
|
|
18
43
|
>
|
|
19
44
|
<div class="h-full w-64 overflow-y-auto">
|
|
20
45
|
<Sidebar />
|
|
@@ -5,6 +5,9 @@ type __VLS_Props = {
|
|
|
5
5
|
field: ConfigField;
|
|
6
6
|
config: Record<string, unknown>;
|
|
7
7
|
modelValue: unknown;
|
|
8
|
+
/** The settings this one sits among, so a dependency can be named as the
|
|
9
|
+
* operator sees it rather than by its config key. */
|
|
10
|
+
siblingFields?: ConfigField[];
|
|
8
11
|
};
|
|
9
12
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
13
|
"update:modelValue": (value: unknown) => any;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
+
import { isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
3
4
|
import { ChevronDown } from "lucide-vue-next";
|
|
4
5
|
import { Input } from "@revenexx/studio";
|
|
5
6
|
import { resolveLocalized } from "../../utils/localize";
|
|
@@ -8,11 +9,14 @@ const props = defineProps({
|
|
|
8
9
|
version: { type: String, required: true },
|
|
9
10
|
field: { type: Object, required: true },
|
|
10
11
|
config: { type: Object, required: true },
|
|
11
|
-
modelValue: { type: null, required: true }
|
|
12
|
+
modelValue: { type: null, required: true },
|
|
13
|
+
siblingFields: { type: Array, required: false }
|
|
12
14
|
});
|
|
13
15
|
const emit = defineEmits(["update:modelValue"]);
|
|
16
|
+
const t = useIntegrationsT();
|
|
14
17
|
const search = ref("");
|
|
15
18
|
const focused = ref(false);
|
|
19
|
+
const versionNotReady = computed(() => !isVersionExecutable(props.slug, props.version));
|
|
16
20
|
const q = useNodeConfigResolve({
|
|
17
21
|
slug: () => props.slug,
|
|
18
22
|
version: () => props.version,
|
|
@@ -21,6 +25,10 @@ const q = useNodeConfigResolve({
|
|
|
21
25
|
dependsOn: () => props.field.dependsOn,
|
|
22
26
|
search: () => focused.value ? search.value : ""
|
|
23
27
|
});
|
|
28
|
+
const unsatisfiedLabels = computed(() => q.unsatisfied.value.map((key) => {
|
|
29
|
+
const sibling = props.siblingFields?.find((f) => f.key === key);
|
|
30
|
+
return sibling && resolveLocalized(sibling.label) || key;
|
|
31
|
+
}));
|
|
24
32
|
const options = computed(() => q.data.value?.options ?? []);
|
|
25
33
|
const isMulti = computed(() => props.field.type === "multiselect");
|
|
26
34
|
const disabledControlClass = "flex h-8 items-center rounded-md border border-black/[0.08] bg-white px-2 text-sm text-muted-foreground opacity-50";
|
|
@@ -75,28 +83,34 @@ function toggleMulti(optValue) {
|
|
|
75
83
|
|
|
76
84
|
<template>
|
|
77
85
|
<div>
|
|
86
|
+
<!-- the pinned version cannot be run, so there is nothing to resolve -->
|
|
87
|
+
<template v-if="versionNotReady">
|
|
88
|
+
<div :class="disabledControlClass">—</div>
|
|
89
|
+
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.versionNotReady") }}</p>
|
|
90
|
+
</template>
|
|
91
|
+
|
|
78
92
|
<!-- gated: required dependencies still unset -->
|
|
79
|
-
<template v-if="q.gated.value">
|
|
93
|
+
<template v-else-if="q.gated.value">
|
|
80
94
|
<div :class="disabledControlClass">—</div>
|
|
81
|
-
<p class="mt-1 text-sm text-amber-600">
|
|
95
|
+
<p class="mt-1 text-sm text-amber-600">{{ t("integration.nodeConfig.dynamicField.gated", { keys: unsatisfiedLabels.join(", ") }) }}</p>
|
|
82
96
|
</template>
|
|
83
97
|
|
|
84
98
|
<!-- first load: no data yet -->
|
|
85
99
|
<template v-else-if="q.isFetching.value && options.length === 0">
|
|
86
|
-
<div :class="disabledControlClass">resolving
|
|
100
|
+
<div :class="disabledControlClass">{{ t("integration.nodeConfig.dynamicField.resolving") }}</div>
|
|
87
101
|
</template>
|
|
88
102
|
|
|
89
103
|
<!-- error: build/runtime/etc -->
|
|
90
104
|
<template v-else-if="q.error.value">
|
|
91
105
|
<div :class="disabledControlClass">—</div>
|
|
92
106
|
<div class="mt-1 flex items-center gap-2 text-sm text-amber-600">
|
|
93
|
-
<span>{{ q.error.value.kind === "building" ? "
|
|
107
|
+
<span>{{ q.error.value.kind === "building" ? t("integration.nodeConfig.resolve.error.building") : q.error.value.message }}</span>
|
|
94
108
|
<button
|
|
95
109
|
type="button"
|
|
96
110
|
class="rounded border border-black/[0.08] px-1.5 py-0.5 text-mono text-muted-foreground transition hover:text-foreground"
|
|
97
111
|
@click="q.refetch()"
|
|
98
112
|
>
|
|
99
|
-
|
|
113
|
+
{{ t("integration.nodeConfig.resolve.action.retry") }}
|
|
100
114
|
</button>
|
|
101
115
|
</div>
|
|
102
116
|
</template>
|
|
@@ -106,13 +120,13 @@ function toggleMulti(optValue) {
|
|
|
106
120
|
<Input
|
|
107
121
|
v-if="options.length > 10"
|
|
108
122
|
v-model="multiFilter"
|
|
109
|
-
:aria-label="
|
|
110
|
-
placeholder="
|
|
123
|
+
:aria-label="t('integration.nodeConfig.dynamicField.multiselect.filterAria', { label: resolveLocalized(field.label) })"
|
|
124
|
+
:placeholder="t('integration.nodeConfig.dynamicField.multiselect.filterPlaceholder')"
|
|
111
125
|
class="mb-1.5 h-7 bg-white text-sm"
|
|
112
126
|
/>
|
|
113
127
|
<div role="group" :aria-label="resolveLocalized(field.label)" class="flex flex-wrap gap-1">
|
|
114
128
|
<span v-if="visibleMultiOptions.length === 0" class="text-sm text-muted-foreground">
|
|
115
|
-
{{ options.length === 0 ? "
|
|
129
|
+
{{ options.length === 0 ? t("integration.nodeConfig.dynamicField.empty") : t("integration.nodeConfig.dynamicField.emptyFiltered") }}
|
|
116
130
|
</span>
|
|
117
131
|
<button
|
|
118
132
|
v-for="opt in visibleMultiOptions"
|
|
@@ -131,10 +145,10 @@ function toggleMulti(optValue) {
|
|
|
131
145
|
:key="m"
|
|
132
146
|
class="rounded-md border border-black/[0.08] bg-white px-1.5 py-0.5 text-sm text-muted-foreground opacity-60"
|
|
133
147
|
>
|
|
134
|
-
{{
|
|
148
|
+
{{ t("integration.nodeConfig.dynamicField.notInList", { value: m }) }}
|
|
135
149
|
</span>
|
|
136
150
|
</div>
|
|
137
|
-
<p v-if="q.isFetching.value && q.hasData.value" class="mt-1 text-sm text-muted-foreground">reloading
|
|
151
|
+
<p v-if="q.isFetching.value && q.hasData.value" class="mt-1 text-sm text-muted-foreground">{{ t("integration.nodeConfig.dynamicField.reloading") }}</p>
|
|
138
152
|
</template>
|
|
139
153
|
|
|
140
154
|
<!-- single select: searchable combobox; typing re-resolves with the term -->
|
|
@@ -143,7 +157,7 @@ function toggleMulti(optValue) {
|
|
|
143
157
|
<Input
|
|
144
158
|
:model-value="focused ? search : currentLabel"
|
|
145
159
|
:aria-label="resolveLocalized(field.label)"
|
|
146
|
-
:placeholder="resolveLocalized(field.placeholder) || '
|
|
160
|
+
:placeholder="resolveLocalized(field.placeholder) || t('integration.nodeConfig.select.placeholder')"
|
|
147
161
|
class="h-8 bg-white pr-7 text-sm"
|
|
148
162
|
@update:model-value="(v) => {
|
|
149
163
|
search = String(v);
|
|
@@ -157,7 +171,7 @@ function toggleMulti(optValue) {
|
|
|
157
171
|
class="absolute left-0 right-0 top-full z-30 mt-1 max-h-56 overflow-y-auto rounded-md border border-black/[0.08] bg-white py-1 shadow-md"
|
|
158
172
|
>
|
|
159
173
|
<div v-if="q.isFetching.value" class="px-2.5 py-1.5 text-sm text-muted-foreground">
|
|
160
|
-
{{ search ? "searching
|
|
174
|
+
{{ search ? t("integration.nodeConfig.dynamicField.searching") : t("integration.nodeConfig.dynamicField.resolving") }}
|
|
161
175
|
</div>
|
|
162
176
|
<button
|
|
163
177
|
v-for="opt in visibleOptions"
|
|
@@ -170,14 +184,14 @@ function toggleMulti(optValue) {
|
|
|
170
184
|
<span class="min-w-0 truncate">{{ resolveLocalized(opt.label) || String(opt.value) }}</span>
|
|
171
185
|
</button>
|
|
172
186
|
<div v-if="!q.isFetching.value && visibleOptions.length === 0" class="px-2.5 py-1.5 text-sm text-muted-foreground">
|
|
173
|
-
{{ search ?
|
|
187
|
+
{{ search ? t("integration.nodeConfig.dynamicField.noMatch", { query: search }) : t("integration.nodeConfig.dynamicField.empty") }}
|
|
174
188
|
</div>
|
|
175
189
|
</div>
|
|
176
190
|
</div>
|
|
177
191
|
<p v-if="currentValue !== '' && !currentKnown" class="mt-1 text-sm text-amber-600">
|
|
178
|
-
|
|
192
|
+
{{ t("integration.nodeConfig.dynamicField.currentNotResolved") }}
|
|
179
193
|
</p>
|
|
180
|
-
<p v-if="q.isFetching.value && q.hasData.value && !focused" class="mt-1 text-sm text-muted-foreground">reloading
|
|
194
|
+
<p v-if="q.isFetching.value && q.hasData.value && !focused" class="mt-1 text-sm text-muted-foreground">{{ t("integration.nodeConfig.dynamicField.reloading") }}</p>
|
|
181
195
|
</template>
|
|
182
196
|
</div>
|
|
183
197
|
</template>
|
|
@@ -5,6 +5,9 @@ type __VLS_Props = {
|
|
|
5
5
|
field: ConfigField;
|
|
6
6
|
config: Record<string, unknown>;
|
|
7
7
|
modelValue: unknown;
|
|
8
|
+
/** The settings this one sits among, so a dependency can be named as the
|
|
9
|
+
* operator sees it rather than by its config key. */
|
|
10
|
+
siblingFields?: ConfigField[];
|
|
8
11
|
};
|
|
9
12
|
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
13
|
"update:modelValue": (value: unknown) => any;
|
|
@@ -13,6 +13,7 @@ const props = defineProps({
|
|
|
13
13
|
depth: { type: Number, required: false }
|
|
14
14
|
});
|
|
15
15
|
const emit = defineEmits(["set"]);
|
|
16
|
+
const t = useIntegrationsT();
|
|
16
17
|
void IntegrationsApiError;
|
|
17
18
|
const MAX_DEPTH = 5;
|
|
18
19
|
const q = useNodeConfigResolve({
|
|
@@ -29,17 +30,17 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
29
30
|
<div>
|
|
30
31
|
<!-- gated: required dependencies still unset -->
|
|
31
32
|
<p v-if="q.gated.value" class="text-sm text-amber-600">
|
|
32
|
-
|
|
33
|
+
{{ t("integration.nodeConfig.dynamicField.gated", { keys: q.unsatisfied.value.join(", ") }) }}
|
|
33
34
|
</p>
|
|
34
35
|
|
|
35
36
|
<!-- first load: no fields yet -->
|
|
36
37
|
<p v-else-if="q.isFetching.value && !resolvedFields.length" class="text-sm text-muted-foreground">
|
|
37
|
-
|
|
38
|
+
{{ t("integration.nodeConfig.dynamicSchema.resolving") }}
|
|
38
39
|
</p>
|
|
39
40
|
|
|
40
41
|
<!-- error: build/runtime/etc -->
|
|
41
42
|
<div v-else-if="q.error.value" class="flex items-center gap-2 text-sm text-amber-600">
|
|
42
|
-
<span>{{ q.error.value.kind === "building" ? "
|
|
43
|
+
<span>{{ q.error.value.kind === "building" ? t("integration.nodeConfig.resolve.error.building") : q.error.value.message }}</span>
|
|
43
44
|
<Button
|
|
44
45
|
type="button"
|
|
45
46
|
variant="outline"
|
|
@@ -47,13 +48,13 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
47
48
|
class="h-6 px-1.5 text-mono text-muted-foreground"
|
|
48
49
|
@click="q.refetch()"
|
|
49
50
|
>
|
|
50
|
-
|
|
51
|
+
{{ t("integration.nodeConfig.resolve.action.retry") }}
|
|
51
52
|
</Button>
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
55
|
<!-- empty: resolved but no fields -->
|
|
55
56
|
<p v-else-if="!resolvedFields.length" class="text-sm text-muted-foreground">
|
|
56
|
-
|
|
57
|
+
{{ t("integration.nodeConfig.dynamicSchema.empty") }}
|
|
57
58
|
</p>
|
|
58
59
|
|
|
59
60
|
<!-- fieldset: the resolved child fields, rendered into the flat node config -->
|
|
@@ -69,12 +70,12 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
69
70
|
<template v-for="f in resolvedFields" :key="f.key">
|
|
70
71
|
<!-- collision guard: a resolved key that shadows a static config field is skipped -->
|
|
71
72
|
<p v-if="staticKeys.includes(f.key)" class="text-sm text-amber-600">
|
|
72
|
-
|
|
73
|
+
{{ t("integration.nodeConfig.dynamicSchema.fieldCollision", { key: f.key }) }}
|
|
73
74
|
</p>
|
|
74
75
|
|
|
75
76
|
<!-- depth cap: don't expand nested dynamic-schema past MAX_DEPTH -->
|
|
76
77
|
<p v-else-if="f.type === 'dynamic-schema' && (depth ?? 0) >= MAX_DEPTH" class="text-sm text-muted-foreground">
|
|
77
|
-
|
|
78
|
+
{{ t("integration.nodeConfig.dynamicSchema.depthCapped") }}
|
|
78
79
|
</p>
|
|
79
80
|
|
|
80
81
|
<!-- a resolved field, rendered exactly like a static node config field -->
|
|
@@ -86,13 +87,14 @@ const resolvedFields = computed(() => q.data.value?.fields ?? []);
|
|
|
86
87
|
:version="version"
|
|
87
88
|
:secret-keys="secretKeys"
|
|
88
89
|
:static-keys="staticKeys"
|
|
90
|
+
:sibling-fields="resolvedFields"
|
|
89
91
|
:depth="(depth ?? 0) + 1"
|
|
90
92
|
@set="(k, v) => emit('set', k, v)"
|
|
91
93
|
/>
|
|
92
94
|
</template>
|
|
93
95
|
|
|
94
96
|
<p v-if="q.isFetching.value && q.hasData.value" class="text-sm text-muted-foreground">
|
|
95
|
-
reloading
|
|
97
|
+
{{ t("integration.nodeConfig.dynamicSchema.reloading") }}
|
|
96
98
|
</p>
|
|
97
99
|
</fieldset>
|
|
98
100
|
</div>
|
|
@@ -6,6 +6,9 @@ type __VLS_Props = {
|
|
|
6
6
|
version: string;
|
|
7
7
|
secretKeys: string[];
|
|
8
8
|
staticKeys?: string[];
|
|
9
|
+
/** The whole set of settings this one belongs to — a `dynamic` field names the
|
|
10
|
+
* ones it waits for out of it (NodeConfigDynamicField). */
|
|
11
|
+
siblingFields?: ConfigField[];
|
|
9
12
|
depth?: number;
|
|
10
13
|
variables?: Record<string, WorkflowVariable>;
|
|
11
14
|
upstreamOutputs?: Array<{
|