@revenexx/studio-integrations 1.1.1 → 1.3.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 +638 -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 +45 -19
- 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/NodeConfigDynamicSchema.vue +10 -2
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue +88 -1
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +7 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +125 -54
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +7 -3
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue +133 -0
- package/dist/runtime/components/Integrations/NodeVersionMoveDialog.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +2 -1
- 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 +129 -18
- 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/StateNamespaceCreateDialog.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue +111 -0
- package/dist/runtime/components/Integrations/StateNamespaceCreateDialog.vue.d.ts +16 -0
- 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 +78 -30
- 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/WorkflowStateNamespacesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue +216 -0
- package/dist/runtime/components/Integrations/WorkflowStateNamespacesDialog.vue.d.ts +13 -0
- 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 +7 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +10 -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/integrationNodeCatalog.d.ts +25 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -0
- package/dist/runtime/data/nodeVersionMove.d.ts +72 -0
- package/dist/runtime/data/nodeVersionMove.js +65 -0
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +79 -12
- package/dist/runtime/i18n/messages/en.json +79 -12
- 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 +55 -8
- 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 +169 -24
- package/dist/runtime/pages/integrations/workflows/index.vue +23 -4
- package/dist/runtime/types/integrations.d.ts +49 -1
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/buildSettle.d.ts +68 -0
- package/dist/runtime/utils/buildSettle.js +29 -0
- 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/settingCondition.d.ts +48 -0
- package/dist/runtime/utils/settingCondition.js +84 -0
- package/dist/runtime/utils/stateNamespaceName.d.ts +23 -0
- package/dist/runtime/utils/stateNamespaceName.js +8 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { AddCircle as Plus, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogContent,
|
|
8
|
+
DialogDescription,
|
|
9
|
+
DialogFooter,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
Input,
|
|
13
|
+
Label,
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue
|
|
19
|
+
} from "@revenexx/studio";
|
|
20
|
+
import { deepClone } from "../../utils/deepClone";
|
|
21
|
+
import { stateNamespaceErrorKey } from "../../utils/stateNamespaceName";
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
open: { type: Boolean, required: true },
|
|
24
|
+
state: { type: Array, required: true }
|
|
25
|
+
});
|
|
26
|
+
const emit = defineEmits(["update:open", "update:state"]);
|
|
27
|
+
const t = useIntegrationsT();
|
|
28
|
+
const ROLES = ["mapping", "cursor", "dedupe", "digest"];
|
|
29
|
+
const working = ref([]);
|
|
30
|
+
watch(() => props.open, (isOpen) => {
|
|
31
|
+
if (!isOpen) return;
|
|
32
|
+
working.value = deepClone(props.state ?? []);
|
|
33
|
+
newName.value = "";
|
|
34
|
+
newRole.value = "mapping";
|
|
35
|
+
newVisibility.value = "private";
|
|
36
|
+
});
|
|
37
|
+
const changed = computed(() => JSON.stringify(working.value) !== JSON.stringify(props.state ?? []));
|
|
38
|
+
function applyAndClose() {
|
|
39
|
+
if (changed.value) emit("update:state", working.value);
|
|
40
|
+
emit("update:open", false);
|
|
41
|
+
}
|
|
42
|
+
function setVisibility(index, visibility) {
|
|
43
|
+
const next = [...working.value];
|
|
44
|
+
const row = next[index];
|
|
45
|
+
if (!row) return;
|
|
46
|
+
next[index] = { ...row, visibility };
|
|
47
|
+
working.value = next;
|
|
48
|
+
}
|
|
49
|
+
function removeNamespace(index) {
|
|
50
|
+
working.value = working.value.filter((_, i) => i !== index);
|
|
51
|
+
}
|
|
52
|
+
const newName = ref("");
|
|
53
|
+
const newRole = ref("mapping");
|
|
54
|
+
const newVisibility = ref("private");
|
|
55
|
+
const newNameError = computed(() => {
|
|
56
|
+
const key = stateNamespaceErrorKey(newName.value, working.value.map((n) => n.namespace));
|
|
57
|
+
return key ? t(key) : null;
|
|
58
|
+
});
|
|
59
|
+
function addNamespace() {
|
|
60
|
+
const namespace = newName.value.trim();
|
|
61
|
+
if (!namespace || newNameError.value) return;
|
|
62
|
+
working.value = [...working.value, { namespace, role: newRole.value, visibility: newVisibility.value }];
|
|
63
|
+
newName.value = "";
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<Dialog :open="open" @update:open="(v) => emit('update:open', v)">
|
|
69
|
+
<DialogContent class="max-w-[94vw] sm:max-w-3xl">
|
|
70
|
+
<DialogHeader>
|
|
71
|
+
<DialogTitle>{{ t("integration.workflows.state.dialog.title") }}</DialogTitle>
|
|
72
|
+
<DialogDescription class="text-sm">
|
|
73
|
+
{{ t("integration.workflows.state.dialog.description") }}
|
|
74
|
+
</DialogDescription>
|
|
75
|
+
</DialogHeader>
|
|
76
|
+
|
|
77
|
+
<div class="max-h-[62vh] space-y-3 overflow-y-auto pr-1">
|
|
78
|
+
<p v-if="!working.length" class="rounded-lg border border-dashed border-black/[0.12] px-4 py-6 text-center text-sm text-muted-foreground">
|
|
79
|
+
{{ t("integration.workflows.state.empty") }}
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<div
|
|
83
|
+
v-for="(row, index) in working"
|
|
84
|
+
:key="row.namespace"
|
|
85
|
+
role="group"
|
|
86
|
+
:aria-label="t('integration.workflows.state.row.aria', { namespace: row.namespace })"
|
|
87
|
+
class="rounded-lg border border-black/[0.06] bg-white p-3"
|
|
88
|
+
>
|
|
89
|
+
<div class="flex items-center gap-2">
|
|
90
|
+
<code class="min-w-0 truncate font-mono text-sm font-semibold">{{ row.namespace }}</code>
|
|
91
|
+
<!-- The role is stated, not offered: entries written under one
|
|
92
|
+
meaning cannot be re-read under another (specs AC-3). -->
|
|
93
|
+
<span class="shrink-0 rounded-lg bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
|
|
94
|
+
{{ t(`integration.workflows.state.role.${row.role}.label`) }}
|
|
95
|
+
</span>
|
|
96
|
+
<div class="flex-1" />
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
class="flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition hover:bg-rose-50 hover:text-rose-600"
|
|
100
|
+
:title="t('integration.workflows.state.action.remove')"
|
|
101
|
+
@click="removeNamespace(index)"
|
|
102
|
+
>
|
|
103
|
+
<TrashBin :size="13" />
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<p class="mt-1 text-xs text-muted-foreground">
|
|
108
|
+
{{ t(`integration.workflows.state.role.${row.role}.hint`) }}
|
|
109
|
+
{{ t("integration.workflows.state.role.fixed") }}
|
|
110
|
+
</p>
|
|
111
|
+
|
|
112
|
+
<div class="mt-2 grid gap-2 sm:grid-cols-[10rem_1fr]">
|
|
113
|
+
<div class="space-y-1">
|
|
114
|
+
<Label :for="`state-${row.namespace}-visibility`" class="text-mono text-muted-foreground">
|
|
115
|
+
{{ t("integration.workflows.state.field.visibility.label") }}
|
|
116
|
+
</Label>
|
|
117
|
+
<Select
|
|
118
|
+
:model-value="row.visibility ?? 'private'"
|
|
119
|
+
@update:model-value="(v) => setVisibility(index, String(v))"
|
|
120
|
+
>
|
|
121
|
+
<SelectTrigger
|
|
122
|
+
:id="`state-${row.namespace}-visibility`"
|
|
123
|
+
:aria-label="t('integration.workflows.state.field.visibility.label')"
|
|
124
|
+
class="h-8 bg-white text-sm"
|
|
125
|
+
>
|
|
126
|
+
<SelectValue />
|
|
127
|
+
</SelectTrigger>
|
|
128
|
+
<SelectContent>
|
|
129
|
+
<SelectItem value="private">
|
|
130
|
+
{{ t("integration.workflows.state.visibility.private.label") }}
|
|
131
|
+
</SelectItem>
|
|
132
|
+
<SelectItem value="shared">
|
|
133
|
+
{{ t("integration.workflows.state.visibility.shared.label") }}
|
|
134
|
+
</SelectItem>
|
|
135
|
+
</SelectContent>
|
|
136
|
+
</Select>
|
|
137
|
+
</div>
|
|
138
|
+
<!-- What the choice costs, beside the choice — a shared namespace is
|
|
139
|
+
written by every workflow in the tenant and outlives this one
|
|
140
|
+
(specs AC-4). -->
|
|
141
|
+
<p class="self-end pb-1 text-xs text-muted-foreground">
|
|
142
|
+
{{ t(`integration.workflows.state.visibility.${row.visibility ?? "private"}.hint`) }}
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<p class="mt-2 text-xs text-muted-foreground">
|
|
147
|
+
{{ t("integration.workflows.state.action.remove.hint") }}
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div class="rounded-lg border border-black/[0.06] bg-muted/30 p-3">
|
|
152
|
+
<Label for="state-new-name" class="text-mono text-muted-foreground">
|
|
153
|
+
{{ t("integration.workflows.state.field.newName.label") }}
|
|
154
|
+
</Label>
|
|
155
|
+
<div class="mt-1 grid gap-2 sm:grid-cols-[1fr_10rem_10rem_auto]">
|
|
156
|
+
<Input
|
|
157
|
+
id="state-new-name"
|
|
158
|
+
v-model="newName"
|
|
159
|
+
class="h-8 bg-white font-mono text-sm"
|
|
160
|
+
:placeholder="t('integration.workflows.state.field.newName.placeholder')"
|
|
161
|
+
@keydown.enter.prevent="addNamespace"
|
|
162
|
+
/>
|
|
163
|
+
<!-- The role IS chosen here, and only here — the positive half of
|
|
164
|
+
AC-3, without which "no role control on a row" proves nothing. -->
|
|
165
|
+
<Select v-model="newRole">
|
|
166
|
+
<SelectTrigger
|
|
167
|
+
id="state-new-role"
|
|
168
|
+
:aria-label="t('integration.workflows.state.field.newRole.label')"
|
|
169
|
+
class="h-8 bg-white text-sm"
|
|
170
|
+
>
|
|
171
|
+
<SelectValue />
|
|
172
|
+
</SelectTrigger>
|
|
173
|
+
<SelectContent>
|
|
174
|
+
<SelectItem v-for="role in ROLES" :key="role" :value="role">
|
|
175
|
+
{{ t(`integration.workflows.state.role.${role}.label`) }}
|
|
176
|
+
</SelectItem>
|
|
177
|
+
</SelectContent>
|
|
178
|
+
</Select>
|
|
179
|
+
<Select v-model="newVisibility">
|
|
180
|
+
<SelectTrigger
|
|
181
|
+
id="state-new-visibility"
|
|
182
|
+
:aria-label="t('integration.workflows.state.field.newVisibility.label')"
|
|
183
|
+
class="h-8 bg-white text-sm"
|
|
184
|
+
>
|
|
185
|
+
<SelectValue />
|
|
186
|
+
</SelectTrigger>
|
|
187
|
+
<SelectContent>
|
|
188
|
+
<SelectItem value="private">
|
|
189
|
+
{{ t("integration.workflows.state.visibility.private.label") }}
|
|
190
|
+
</SelectItem>
|
|
191
|
+
<SelectItem value="shared">
|
|
192
|
+
{{ t("integration.workflows.state.visibility.shared.label") }}
|
|
193
|
+
</SelectItem>
|
|
194
|
+
</SelectContent>
|
|
195
|
+
</Select>
|
|
196
|
+
<Button size="sm" class="h-8" :disabled="!newName.trim() || !!newNameError" @click="addNamespace">
|
|
197
|
+
<Plus :size="14" class="mr-1" />
|
|
198
|
+
{{ t("integration.workflows.state.action.add") }}
|
|
199
|
+
</Button>
|
|
200
|
+
</div>
|
|
201
|
+
<p v-if="newNameError" class="mt-1 text-xs text-rose-600">{{ newNameError }}</p>
|
|
202
|
+
<p class="mt-1 text-xs text-muted-foreground">{{ t(`integration.workflows.state.role.${newRole}.hint`) }}</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<DialogFooter>
|
|
207
|
+
<Button variant="outline" size="sm" class="h-8" @click="emit('update:open', false)">
|
|
208
|
+
{{ t("integration.common.action.cancel") }}
|
|
209
|
+
</Button>
|
|
210
|
+
<Button size="sm" class="h-8" @click="applyAndClose">
|
|
211
|
+
{{ t("integration.workflows.state.action.done") }}
|
|
212
|
+
</Button>
|
|
213
|
+
</DialogFooter>
|
|
214
|
+
</DialogContent>
|
|
215
|
+
</Dialog>
|
|
216
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WorkflowStateNamespace } from '../../types/integrations.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
state: WorkflowStateNamespace[];
|
|
5
|
+
};
|
|
6
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
|
+
"update:open": (args_0: boolean) => any;
|
|
8
|
+
"update:state": (next: WorkflowStateNamespace[]) => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
"onUpdate:open"?: ((args_0: boolean) => any) | undefined;
|
|
11
|
+
"onUpdate:state"?: ((next: WorkflowStateNamespace[]) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
export default _default;
|
|
@@ -3,6 +3,8 @@ import { computed, ref } from "vue";
|
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import { TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
6
|
+
import { SIDE_POSITION } from "./canvasSides";
|
|
7
|
+
import { outputHandleId } from "../../data/handleId";
|
|
6
8
|
import { triggerGlyph as glyphForTrigger } from "../../data/triggerGlyphs";
|
|
7
9
|
import { resolveDisplayName } from "../../utils/localize";
|
|
8
10
|
import { triggerTypeLabel } from "../../data/integrationNodeCatalog";
|
|
@@ -45,21 +47,18 @@ function remove() {
|
|
|
45
47
|
flow.removeNodes([props.id]);
|
|
46
48
|
}
|
|
47
49
|
const OUT_SIDES = ["bottom"];
|
|
48
|
-
function sideToPosition(s) {
|
|
49
|
-
return s === "top" ? Position.Top : s === "right" ? Position.Right : s === "bottom" ? Position.Bottom : Position.Left;
|
|
50
|
-
}
|
|
51
50
|
</script>
|
|
52
51
|
|
|
53
52
|
<template>
|
|
54
53
|
<NodeToolbar :is-visible="hovered || selected" :position="Position.Top" :offset="8" class="flex items-center gap-1 rounded-md border border-black/[0.08] bg-white px-1 py-1 shadow-md" @mouseenter="onHoverEnter" @mouseleave="onHoverLeave">
|
|
55
54
|
<button
|
|
56
55
|
type="button"
|
|
57
|
-
class="flex
|
|
56
|
+
class="flex size-6 items-center justify-center rounded text-sm text-rose-600 hover:bg-rose-50"
|
|
58
57
|
:title="t('integration.common.action.delete')"
|
|
59
58
|
:aria-label="t('integration.canvas.trigger.action.deleteAria')"
|
|
60
59
|
@click="remove"
|
|
61
60
|
>
|
|
62
|
-
<TrashBin :size="
|
|
61
|
+
<TrashBin :size="13" />
|
|
63
62
|
</button>
|
|
64
63
|
</NodeToolbar>
|
|
65
64
|
|
|
@@ -97,10 +96,10 @@ function sideToPosition(s) {
|
|
|
97
96
|
"default"; the side suffix is stripped on persist. -->
|
|
98
97
|
<Handle
|
|
99
98
|
v-for="side in OUT_SIDES"
|
|
100
|
-
:id="
|
|
99
|
+
:id="outputHandleId('default', side)"
|
|
101
100
|
:key="side"
|
|
102
101
|
type="source"
|
|
103
|
-
:position="
|
|
102
|
+
:position="SIDE_POSITION[side]"
|
|
104
103
|
class="!size-3 !rounded-full !border-2 !border-white !bg-emerald-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-emerald-600"
|
|
105
104
|
/>
|
|
106
105
|
</template>
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
SelectValue
|
|
20
20
|
} from "@revenexx/studio";
|
|
21
21
|
import { toast } from "vue-sonner";
|
|
22
|
+
import { deepClone } from "../../utils/deepClone";
|
|
23
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
22
24
|
import { languageKeyRead, languageKeyToWrite, resolveDisplayName } from "../../utils/localize";
|
|
23
25
|
const props = defineProps({
|
|
24
26
|
open: { type: Boolean, required: true },
|
|
@@ -34,7 +36,7 @@ const jsonErrors = reactive({});
|
|
|
34
36
|
const descDrafts = reactive({});
|
|
35
37
|
watch(() => props.open, (o) => {
|
|
36
38
|
if (!o) return;
|
|
37
|
-
working.value =
|
|
39
|
+
working.value = deepClone(props.variables);
|
|
38
40
|
for (const k of Object.keys(jsonDrafts)) delete jsonDrafts[k];
|
|
39
41
|
for (const k of Object.keys(jsonErrors)) delete jsonErrors[k];
|
|
40
42
|
for (const k of Object.keys(descDrafts)) delete descDrafts[k];
|
|
@@ -146,12 +148,10 @@ function addVariable() {
|
|
|
146
148
|
newKey.value = "";
|
|
147
149
|
}
|
|
148
150
|
async function copyUsage(key) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
toast.error(t("integration.workflows.variables.toast.copyFailed"));
|
|
154
|
-
}
|
|
151
|
+
await copyToClipboard(`\${{ vars.${key} }}`, {
|
|
152
|
+
copied: t("integration.workflows.variables.toast.copied"),
|
|
153
|
+
failed: t("integration.workflows.variables.toast.copyFailed")
|
|
154
|
+
});
|
|
155
155
|
}
|
|
156
156
|
</script>
|
|
157
157
|
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { MenuDots as MoreHorizontal } from "#solar-icons";
|
|
3
3
|
import { toast } from "vue-sonner";
|
|
4
|
-
import { apiValidationIssues } from "../../utils/apiError";
|
|
4
|
+
import { apiValidationIssues, errorReason } from "../../utils/apiError";
|
|
5
|
+
import WorkflowDeleteDialog from "./WorkflowDeleteDialog.vue";
|
|
5
6
|
import WorkflowDuplicateDialog from "./WorkflowDuplicateDialog.vue";
|
|
6
7
|
import WorkflowIconView from "./WorkflowIconView.vue";
|
|
7
8
|
import { Layers } from "#solar-icons";
|
|
8
9
|
import {
|
|
9
|
-
AlertDialog,
|
|
10
|
-
AlertDialogContent,
|
|
11
|
-
AlertDialogDescription,
|
|
12
|
-
AlertDialogFooter,
|
|
13
|
-
AlertDialogHeader,
|
|
14
|
-
AlertDialogTitle,
|
|
15
10
|
Badge,
|
|
16
11
|
Button,
|
|
17
12
|
DropdownMenu,
|
|
@@ -27,7 +22,7 @@ defineProps({
|
|
|
27
22
|
});
|
|
28
23
|
const t = useIntegrationsT();
|
|
29
24
|
const router = useRouter();
|
|
30
|
-
const {
|
|
25
|
+
const { toggleActive } = useIntegrationsWorkflows();
|
|
31
26
|
const duplicateTarget = ref(null);
|
|
32
27
|
const duplicateOpen = ref(false);
|
|
33
28
|
function askDuplicate(wf) {
|
|
@@ -77,21 +72,11 @@ function runBadge(status) {
|
|
|
77
72
|
return { label: t("integration.workflows.table.status.run.cancelled"), class: "bg-slate-100 text-slate-700 border-slate-200" };
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
|
-
const
|
|
81
|
-
const
|
|
75
|
+
const deleteOpen = ref(false);
|
|
76
|
+
const deleteTarget = ref(null);
|
|
82
77
|
function askDelete(wf) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
async function confirmDelete() {
|
|
86
|
-
const wf = pendingDelete.value;
|
|
87
|
-
if (!wf) return;
|
|
88
|
-
deleting.value = true;
|
|
89
|
-
try {
|
|
90
|
-
await remove(wf.id);
|
|
91
|
-
pendingDelete.value = null;
|
|
92
|
-
} finally {
|
|
93
|
-
deleting.value = false;
|
|
94
|
-
}
|
|
78
|
+
deleteTarget.value = { id: wf.id, name: wf.name };
|
|
79
|
+
deleteOpen.value = true;
|
|
95
80
|
}
|
|
96
81
|
async function onToggleActive(wf) {
|
|
97
82
|
try {
|
|
@@ -102,7 +87,7 @@ async function onToggleActive(wf) {
|
|
|
102
87
|
toast.error(t("integration.workflows.table.toast.activateBlocked", issues.length, { name: wf.name }));
|
|
103
88
|
return;
|
|
104
89
|
}
|
|
105
|
-
toast.error(e
|
|
90
|
+
toast.error(errorReason(e, t("integration.workflows.table.toast.statusChangeFailed")));
|
|
106
91
|
}
|
|
107
92
|
}
|
|
108
93
|
function lifecycleOf(wf) {
|
|
@@ -405,30 +390,7 @@ const columns = computed(() => [
|
|
|
405
390
|
</template>
|
|
406
391
|
</DataTable>
|
|
407
392
|
|
|
408
|
-
<
|
|
409
|
-
if (!v) pendingDelete = null;
|
|
410
|
-
}">
|
|
411
|
-
<AlertDialogContent>
|
|
412
|
-
<AlertDialogHeader>
|
|
413
|
-
<AlertDialogTitle>{{ t("integration.workflows.table.deleteDialog.title") }}</AlertDialogTitle>
|
|
414
|
-
<AlertDialogDescription>
|
|
415
|
-
<span v-if="pendingDelete">
|
|
416
|
-
<span class="font-medium text-foreground">{{ pendingDelete.name }}</span>
|
|
417
|
-
{{ t("integration.workflows.table.deleteDialog.description") }}
|
|
418
|
-
</span>
|
|
419
|
-
</AlertDialogDescription>
|
|
420
|
-
</AlertDialogHeader>
|
|
421
|
-
<AlertDialogFooter>
|
|
422
|
-
<Button variant="outline" :disabled="deleting" @click="pendingDelete = null">
|
|
423
|
-
{{ t("integration.common.action.cancel") }}
|
|
424
|
-
</Button>
|
|
425
|
-
<Button class="bg-destructive text-white hover:bg-destructive/90" :disabled="deleting" @click="confirmDelete">
|
|
426
|
-
<Loader2 v-if="deleting" :size="14" class="animate-spin" />
|
|
427
|
-
{{ deleting ? t("integration.workflows.table.deleteDialog.action.deleting") : t("integration.common.action.delete") }}
|
|
428
|
-
</Button>
|
|
429
|
-
</AlertDialogFooter>
|
|
430
|
-
</AlertDialogContent>
|
|
431
|
-
</AlertDialog>
|
|
393
|
+
<WorkflowDeleteDialog v-model:open="deleteOpen" :workflow="deleteTarget" />
|
|
432
394
|
|
|
433
395
|
<WorkflowDuplicateDialog v-model:open="duplicateOpen" :workflow="duplicateTarget" @duplicated="onDuplicated" />
|
|
434
396
|
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Position } from '@vue-flow/core';
|
|
2
|
+
import type { Side } from '../../data/handleId.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a connection dot's side means to the canvas: where the library draws it, what
|
|
5
|
+
* faces it, and where along the edge it sits.
|
|
6
|
+
*
|
|
7
|
+
* `data/handleId.ts` owns what a side *is* — the closed set of four, and the id a dot
|
|
8
|
+
* carries. This owns what the canvas does with one. The split is the import: that
|
|
9
|
+
* module has none, so its round trip can be proved without the canvas library, and
|
|
10
|
+
* `Position` is Vue Flow's. Keeping the two apart is what stops a string-shape contract
|
|
11
|
+
* from depending on a rendering library.
|
|
12
|
+
*
|
|
13
|
+
* **Every map here is a `Record<Side, …>`, and that is the point of the module.** Each
|
|
14
|
+
* was a four-branch ternary ending in an `else`, in a component, and two components had
|
|
15
|
+
* their own copy of the first one. An `else` absorbs a fifth side silently: a `centre`
|
|
16
|
+
* dot would have been drawn on the **left**, called `right` its opposite, and spread
|
|
17
|
+
* along the wrong axis — with no type error and nothing on screen to notice, because
|
|
18
|
+
* every one of those is a plausible answer for *some* side.
|
|
19
|
+
*
|
|
20
|
+
* A `Record<Side, …>` cannot do that. Add a name to `SIDES` and all three of these stop
|
|
21
|
+
* compiling until they say what it means. That is weaker than PO-258's "one edit" and
|
|
22
|
+
* better than it: it is still more than one edit, but none of them can be forgotten.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The side a dot is on → the position Vue Flow draws it at.
|
|
26
|
+
*
|
|
27
|
+
* The two vocabularies happen to be the same four strings today — `Position.Top` is
|
|
28
|
+
* `'top'` — so this could be a cast, and is not. A cast would tie us to that
|
|
29
|
+
* coincidence holding in a library we do not own, and it would say nothing when it
|
|
30
|
+
* stopped: the studio's sides are its own, and this is where they are translated.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SIDE_POSITION: Record<Side, Position>;
|
|
33
|
+
/**
|
|
34
|
+
* The side facing a given one, which is where an edge arriving from it should land.
|
|
35
|
+
*
|
|
36
|
+
* "Opposite" is only meaningful because the four sides are two axes. A fifth position
|
|
37
|
+
* that is not on an axis — a centre dot — has no opposite at all, and the honest answer
|
|
38
|
+
* then is to change this type rather than to invent one; the compiler will ask.
|
|
39
|
+
*/
|
|
40
|
+
export declare const OPPOSITE_SIDE: Record<Side, Side>;
|
|
41
|
+
/**
|
|
42
|
+
* Place the `i`th of `total` output dots evenly along a side.
|
|
43
|
+
*
|
|
44
|
+
* Evenly *between* the corners rather than across them — `(i + 1) / (total + 1)` — so
|
|
45
|
+
* the first and last dot sit inside the card's edge instead of on it, and a single
|
|
46
|
+
* output lands in the middle.
|
|
47
|
+
*/
|
|
48
|
+
export declare function distributedStyle(side: Side, i: number, total: number): Record<string, string>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Position } from "@vue-flow/core";
|
|
2
|
+
export const SIDE_POSITION = {
|
|
3
|
+
top: Position.Top,
|
|
4
|
+
right: Position.Right,
|
|
5
|
+
bottom: Position.Bottom,
|
|
6
|
+
left: Position.Left
|
|
7
|
+
};
|
|
8
|
+
export const OPPOSITE_SIDE = {
|
|
9
|
+
top: "bottom",
|
|
10
|
+
right: "left",
|
|
11
|
+
bottom: "top",
|
|
12
|
+
left: "right"
|
|
13
|
+
};
|
|
14
|
+
const SIDE_SPREAD_AXIS = {
|
|
15
|
+
top: "left",
|
|
16
|
+
right: "top",
|
|
17
|
+
bottom: "left",
|
|
18
|
+
left: "top"
|
|
19
|
+
};
|
|
20
|
+
export function distributedStyle(side, i, total) {
|
|
21
|
+
return { [SIDE_SPREAD_AXIS[side]]: `${(i + 1) / (total + 1) * 100}%` };
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
2
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
3
|
-
import {
|
|
3
|
+
import { errorReason } from "../utils/apiError.js";
|
|
4
4
|
import { useLoadGate } from "./useLoadGate.js";
|
|
5
5
|
export function useCredentialTypes() {
|
|
6
6
|
const types = useState("integrations.credentialTypes", () => []);
|
|
@@ -16,10 +16,6 @@ export function useCredentialTypes() {
|
|
|
16
16
|
if (Array.isArray(obj?.data)) return obj.data;
|
|
17
17
|
return [];
|
|
18
18
|
}
|
|
19
|
-
function captureError(e, fallback) {
|
|
20
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
21
|
-
return e?.message ?? fallback;
|
|
22
|
-
}
|
|
23
19
|
async function load(force = false) {
|
|
24
20
|
if (!await gate.claim(force)) return types.value;
|
|
25
21
|
error.value = null;
|
|
@@ -27,7 +23,7 @@ export function useCredentialTypes() {
|
|
|
27
23
|
types.value = normalize(await api.get("/credential-types"));
|
|
28
24
|
loaded.value = true;
|
|
29
25
|
} catch (e) {
|
|
30
|
-
error.value =
|
|
26
|
+
error.value = errorReason(e, t("integration.credentials.error.loadTypes"));
|
|
31
27
|
} finally {
|
|
32
28
|
gate.release();
|
|
33
29
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
2
|
-
import {
|
|
2
|
+
import { errorReason } from "../utils/apiError.js";
|
|
3
3
|
import { useLoadGate } from "./useLoadGate.js";
|
|
4
4
|
export function useIntegrationsAudit() {
|
|
5
5
|
const api = useIntegrationsApi();
|
|
@@ -28,7 +28,7 @@ export function useIntegrationsAudit() {
|
|
|
28
28
|
entries.value = append ? [...entries.value, ...data] : data;
|
|
29
29
|
nextCursor.value = res.meta?.next_cursor ?? null;
|
|
30
30
|
} catch (e) {
|
|
31
|
-
error.value = e
|
|
31
|
+
error.value = errorReason(e, "Could not load the audit log.");
|
|
32
32
|
} finally {
|
|
33
33
|
gate.release();
|
|
34
34
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import { IntegrationsApiError } from "../utils/apiError.js";
|
|
4
|
+
import { IntegrationsApiError, errorReason } from "../utils/apiError.js";
|
|
5
5
|
import { useLoadGate } from "./useLoadGate.js";
|
|
6
6
|
export function useIntegrationsCredentials() {
|
|
7
7
|
const credentials = useState("integrations.credentials", () => []);
|
|
@@ -20,10 +20,6 @@ export function useIntegrationsCredentials() {
|
|
|
20
20
|
function unwrap(res) {
|
|
21
21
|
return res?.data ?? res;
|
|
22
22
|
}
|
|
23
|
-
function captureError(e, fallback) {
|
|
24
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
25
|
-
return e?.message ?? fallback;
|
|
26
|
-
}
|
|
27
23
|
async function load(force = false) {
|
|
28
24
|
if (!await gate.claim(force)) return credentials.value;
|
|
29
25
|
error.value = null;
|
|
@@ -31,7 +27,7 @@ export function useIntegrationsCredentials() {
|
|
|
31
27
|
credentials.value = normalize(await api.get("/credentials"));
|
|
32
28
|
loaded.value = true;
|
|
33
29
|
} catch (e) {
|
|
34
|
-
error.value =
|
|
30
|
+
error.value = errorReason(e, t("integration.credentials.error.loadFailed"));
|
|
35
31
|
} finally {
|
|
36
32
|
gate.release();
|
|
37
33
|
}
|
|
@@ -62,7 +58,7 @@ export function useIntegrationsCredentials() {
|
|
|
62
58
|
const res = await api.post("/credentials/test", { credential_type_slug: credentialTypeSlug, config });
|
|
63
59
|
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
64
60
|
} catch (e) {
|
|
65
|
-
return { ok: false, error:
|
|
61
|
+
return { ok: false, error: errorReason(e, t("integration.credentials.error.testFailed")) };
|
|
66
62
|
}
|
|
67
63
|
}
|
|
68
64
|
async function test(id) {
|
|
@@ -70,7 +66,7 @@ export function useIntegrationsCredentials() {
|
|
|
70
66
|
const res = await api.post(`/credentials/${encodeURIComponent(id)}/test`);
|
|
71
67
|
return res?.ok === false ? res : { ok: true, message: res?.message };
|
|
72
68
|
} catch (e) {
|
|
73
|
-
return { ok: false, error:
|
|
69
|
+
return { ok: false, error: errorReason(e, t("integration.credentials.error.testFailed")) };
|
|
74
70
|
}
|
|
75
71
|
}
|
|
76
72
|
async function oauthAuthorizeUrl(id) {
|
|
@@ -24,6 +24,10 @@ export declare function useIntegrationsDeadLetters(): {
|
|
|
24
24
|
globalLoading: any;
|
|
25
25
|
globalLoaded: any;
|
|
26
26
|
globalHasMore: any;
|
|
27
|
+
globalPage: any;
|
|
28
|
+
globalLastPage: import("vue").ComputedRef<number>;
|
|
29
|
+
goGlobalPage: (page: number) => Promise<GlobalDeadLetterRow[]>;
|
|
30
|
+
resetGlobalPage: () => void;
|
|
27
31
|
summary: any;
|
|
28
32
|
pendingCount: import("vue").ComputedRef<any>;
|
|
29
33
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useLoadGate } from "./useLoadGate.js";
|
|
4
|
+
import { runFeedLastPage } from "../utils/runFeedQuery.js";
|
|
4
5
|
const EMPTY_SUMMARY = { total: 0, by_status: {} };
|
|
5
6
|
export function useIntegrationsDeadLetters() {
|
|
6
7
|
const api = useIntegrationsApi();
|
|
@@ -25,12 +26,15 @@ export function useIntegrationsDeadLetters() {
|
|
|
25
26
|
const gate = useLoadGate("integrations.deadletters.global", { loading: globalLoading, loaded: globalLoaded });
|
|
26
27
|
const summary = useState("integrations.deadletters.summary", () => ({ ...EMPTY_SUMMARY }));
|
|
27
28
|
const globalHasMore = useState("integrations.deadletters.global.hasMore", () => false);
|
|
29
|
+
const GLOBAL_PER_PAGE = 25;
|
|
30
|
+
const globalPage = useState("integrations.deadletters.global.page", () => 1);
|
|
31
|
+
const globalLastPage = computed(() => runFeedLastPage(summary.value.total, GLOBAL_PER_PAGE));
|
|
28
32
|
const pendingCount = computed(() => summary.value.by_status.pending ?? 0);
|
|
29
33
|
async function loadGlobal(force = false) {
|
|
30
34
|
if (!await gate.claim(force)) return globalItems.value;
|
|
31
35
|
try {
|
|
32
36
|
const [page, counts] = await Promise.all([
|
|
33
|
-
api.get(
|
|
37
|
+
api.get(`/dead-letters?page=${globalPage.value}&per_page=${GLOBAL_PER_PAGE}`),
|
|
34
38
|
api.get("/dead-letters/summary")
|
|
35
39
|
]);
|
|
36
40
|
globalItems.value = page?.data ?? [];
|
|
@@ -45,6 +49,13 @@ export function useIntegrationsDeadLetters() {
|
|
|
45
49
|
}
|
|
46
50
|
return globalItems.value;
|
|
47
51
|
}
|
|
52
|
+
async function goGlobalPage(page) {
|
|
53
|
+
globalPage.value = Math.min(Math.max(1, page), globalLastPage.value);
|
|
54
|
+
return loadGlobal(true);
|
|
55
|
+
}
|
|
56
|
+
function resetGlobalPage() {
|
|
57
|
+
globalPage.value = 1;
|
|
58
|
+
}
|
|
48
59
|
return {
|
|
49
60
|
list,
|
|
50
61
|
replay,
|
|
@@ -54,6 +65,10 @@ export function useIntegrationsDeadLetters() {
|
|
|
54
65
|
globalLoading,
|
|
55
66
|
globalLoaded,
|
|
56
67
|
globalHasMore,
|
|
68
|
+
globalPage,
|
|
69
|
+
globalLastPage,
|
|
70
|
+
goGlobalPage,
|
|
71
|
+
resetGlobalPage,
|
|
57
72
|
summary,
|
|
58
73
|
pendingCount
|
|
59
74
|
};
|
|
@@ -2,7 +2,7 @@ import { applyNodeCatalog, findManifest } from "../data/integrationNodeCatalog.j
|
|
|
2
2
|
import { resolveLocalized } from "../utils/localize.js";
|
|
3
3
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
4
4
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
5
|
-
import {
|
|
5
|
+
import { errorReason } from "../utils/apiError.js";
|
|
6
6
|
import { useLoadGate } from "./useLoadGate.js";
|
|
7
7
|
const RETRY_COOLDOWN_MS = 3e4;
|
|
8
8
|
export function useIntegrationsNodes() {
|
|
@@ -39,7 +39,7 @@ export function useIntegrationsNodes() {
|
|
|
39
39
|
applyNodeCatalog([]);
|
|
40
40
|
loaded.value = false;
|
|
41
41
|
failedAt.value = Date.now();
|
|
42
|
-
error.value = e
|
|
42
|
+
error.value = errorReason(e, t("integration.nodes.error.loadFailed"));
|
|
43
43
|
} finally {
|
|
44
44
|
gate.release();
|
|
45
45
|
}
|