@revenexx/studio-integrations 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +483 -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/NodeConfigField.vue +23 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +2 -2
- package/dist/runtime/components/Integrations/NodeInspector.vue +25 -32
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +2 -2
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +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/StickyNoteView.vue +1 -1
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +18 -3
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue +70 -0
- package/dist/runtime/components/Integrations/WorkflowDeleteDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +2 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +4 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +39 -28
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +17 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +17 -21
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +8 -11
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +6 -7
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +7 -7
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +10 -48
- package/dist/runtime/components/Integrations/canvasSides.d.ts +48 -0
- package/dist/runtime/components/Integrations/canvasSides.js +22 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -6
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -2
- package/dist/runtime/composables/useIntegrationsCredentials.js +4 -8
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +16 -1
- package/dist/runtime/composables/useIntegrationsNodes.js +2 -2
- package/dist/runtime/composables/useIntegrationsRevisions.js +2 -6
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +4 -15
- package/dist/runtime/composables/useIntegrationsRuns.js +49 -34
- package/dist/runtime/composables/useIntegrationsSchedules.js +2 -2
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -6
- package/dist/runtime/composables/useIntegrationsTemplates.js +2 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +4 -7
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -2
- package/dist/runtime/composables/useOAuthConnect.d.ts +42 -0
- package/dist/runtime/composables/useOAuthConnect.js +33 -0
- package/dist/runtime/data/expressionEditor.d.ts +17 -5
- package/dist/runtime/data/expressionEditor.js +11 -5
- package/dist/runtime/data/handleId.d.ts +61 -1
- package/dist/runtime/data/handleId.js +8 -1
- package/dist/runtime/data/payloadForm.js +3 -2
- package/dist/runtime/i18n/messages/de.json +26 -8
- package/dist/runtime/i18n/messages/en.json +26 -8
- package/dist/runtime/pages/integrations/audit.vue +7 -6
- package/dist/runtime/pages/integrations/credentials.vue +9 -21
- package/dist/runtime/pages/integrations/index.vue +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 +155 -20
- package/dist/runtime/types/integrations.d.ts +5 -0
- package/dist/runtime/utils/apiError.d.ts +21 -1
- package/dist/runtime/utils/apiError.js +1 -1
- package/dist/runtime/utils/clipboard.d.ts +33 -0
- package/dist/runtime/utils/clipboard.js +11 -0
- package/dist/runtime/utils/deepClone.d.ts +41 -0
- package/dist/runtime/utils/deepClone.js +3 -0
- package/dist/runtime/utils/formatDuration.d.ts +44 -0
- package/dist/runtime/utils/formatDuration.js +13 -0
- package/dist/runtime/utils/nodeDisplayName.d.ts +22 -0
- package/dist/runtime/utils/nodeDisplayName.js +6 -0
- package/dist/runtime/utils/notify.d.ts +1 -1
- package/dist/runtime/utils/runEnvelope.d.ts +31 -0
- package/dist/runtime/utils/runEnvelope.js +12 -0
- package/dist/runtime/utils/runFeedQuery.d.ts +135 -0
- package/dist/runtime/utils/runFeedQuery.js +66 -0
- package/dist/runtime/utils/runOutputs.d.ts +33 -0
- package/dist/runtime/utils/runOutputs.js +10 -0
- package/dist/runtime/utils/validationFindings.d.ts +26 -3
- package/dist/runtime/utils/validationFindings.js +18 -3
- package/package.json +1 -1
|
@@ -5,8 +5,12 @@ import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
6
|
import { Copy, DangerTriangle as AlertTriangle, RefreshCircle as Loader2, Repeat, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
7
7
|
import { NODE_CATEGORIES, findManifest, isVersionExecutable } from "../../data/integrationNodeCatalog";
|
|
8
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
8
9
|
import { isTerminalManifest } from "../../data/nodeInputData";
|
|
9
|
-
import {
|
|
10
|
+
import { resolveLocalized } from "../../utils/localize";
|
|
11
|
+
import { SIDE_POSITION, distributedStyle } from "./canvasSides";
|
|
12
|
+
import { fieldDisplayLabel } from "../../utils/validationFindings";
|
|
13
|
+
import { nodeDisplayName } from "../../utils/nodeDisplayName";
|
|
10
14
|
const props = defineProps({
|
|
11
15
|
id: { type: String, required: true },
|
|
12
16
|
data: { type: Object, required: true },
|
|
@@ -17,11 +21,11 @@ defineEmits(["updateNodeInternals"]);
|
|
|
17
21
|
const t = useIntegrationsT();
|
|
18
22
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
19
23
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
20
|
-
const title = computed(
|
|
21
|
-
() => resolveDisplayName(props.data.name) || resolveLocalized(manifest.value?.name) || props.data.nodeSlug
|
|
22
|
-
);
|
|
24
|
+
const title = computed(() => nodeDisplayName(props.data));
|
|
23
25
|
const invalidFields = computed(() => props.data.__invalidFields ?? []);
|
|
24
|
-
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, {
|
|
26
|
+
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, {
|
|
27
|
+
fields: invalidFields.value.map((key) => fieldDisplayLabel(props.data, key)).join(", ")
|
|
28
|
+
}));
|
|
25
29
|
const insideLoops = computed(() => props.data.__loop?.inside ?? []);
|
|
26
30
|
const repeatsLabel = computed(() => t("integration.canvas.node.badge.repeats", insideLoops.value.length, { iterators: insideLoops.value.join(", ") }));
|
|
27
31
|
const bodySize = computed(() => props.data.__loop?.bodySize);
|
|
@@ -111,14 +115,6 @@ function portTone(p) {
|
|
|
111
115
|
const showPortLabels = computed(() => resolvePending.value || outputs.value.length > 1 || outputs.value.some((p) => p.kind !== "default"));
|
|
112
116
|
const IN_SIDES = ["top"];
|
|
113
117
|
const OUT_SIDES = ["bottom"];
|
|
114
|
-
function sideToPosition(s) {
|
|
115
|
-
return s === "top" ? Position.Top : s === "right" ? Position.Right : s === "bottom" ? Position.Bottom : Position.Left;
|
|
116
|
-
}
|
|
117
|
-
function distributedStyle(side, i, total) {
|
|
118
|
-
const pct = `${(i + 1) / (total + 1) * 100}%`;
|
|
119
|
-
if (side === "top" || side === "bottom") return { left: pct };
|
|
120
|
-
return { top: pct };
|
|
121
|
-
}
|
|
122
118
|
const flow = useVueFlow();
|
|
123
119
|
const hovered = ref(false);
|
|
124
120
|
let hoverTimer = null;
|
|
@@ -192,22 +188,22 @@ function remove() {
|
|
|
192
188
|
<NodeToolbar v-if="nodeActions" :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">
|
|
193
189
|
<button
|
|
194
190
|
type="button"
|
|
195
|
-
class="flex
|
|
191
|
+
class="flex size-6 items-center justify-center rounded text-sm text-foreground/80 hover:bg-muted/50"
|
|
196
192
|
:title="t('integration.canvas.node.action.duplicate')"
|
|
197
193
|
:aria-label="t('integration.canvas.node.action.duplicateAria')"
|
|
198
194
|
@click="duplicate"
|
|
199
195
|
>
|
|
200
|
-
<Copy :size="
|
|
196
|
+
<Copy :size="13" />
|
|
201
197
|
</button>
|
|
202
198
|
<div class="h-4 w-px bg-black/10" />
|
|
203
199
|
<button
|
|
204
200
|
type="button"
|
|
205
|
-
class="flex
|
|
201
|
+
class="flex size-6 items-center justify-center rounded text-sm text-rose-600 hover:bg-rose-50"
|
|
206
202
|
:title="t('integration.common.action.delete')"
|
|
207
203
|
:aria-label="t('integration.canvas.node.action.deleteAria')"
|
|
208
204
|
@click="remove"
|
|
209
205
|
>
|
|
210
|
-
<TrashBin :size="
|
|
206
|
+
<TrashBin :size="13" />
|
|
211
207
|
</button>
|
|
212
208
|
</NodeToolbar>
|
|
213
209
|
|
|
@@ -239,10 +235,10 @@ function remove() {
|
|
|
239
235
|
dots feed the same `__in` port. Clearly visible, ring + shadow. -->
|
|
240
236
|
<Handle
|
|
241
237
|
v-for="side in IN_SIDES"
|
|
242
|
-
:id="
|
|
238
|
+
:id="inputHandleId(side)"
|
|
243
239
|
:key="`in-${side}`"
|
|
244
240
|
type="target"
|
|
245
|
-
:position="
|
|
241
|
+
:position="SIDE_POSITION[side]"
|
|
246
242
|
:title="t('integration.canvas.node.handle.input')"
|
|
247
243
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-slate-700"
|
|
248
244
|
/>
|
|
@@ -252,10 +248,10 @@ function remove() {
|
|
|
252
248
|
<template v-for="side in OUT_SIDES" :key="`side-${side}`">
|
|
253
249
|
<Handle
|
|
254
250
|
v-for="(p, i) in outputs"
|
|
255
|
-
:id="
|
|
251
|
+
:id="outputHandleId(p.name, side)"
|
|
256
252
|
:key="`${p.name}-${side}`"
|
|
257
253
|
type="source"
|
|
258
|
-
:position="
|
|
254
|
+
:position="SIDE_POSITION[side]"
|
|
259
255
|
:title="handleTitle(p)"
|
|
260
256
|
:style="distributedStyle(side, i, outputs.length)"
|
|
261
257
|
class="!size-3 !rounded-full !border-2 !border-white !shadow-sm transition-all hover:!size-3.5"
|
|
@@ -24,10 +24,14 @@ import StickyNoteView from "./StickyNoteView.vue";
|
|
|
24
24
|
import GroupFrameView from "./GroupFrameView.vue";
|
|
25
25
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
26
26
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
27
|
+
import { OPPOSITE_SIDE } from "./canvasSides";
|
|
28
|
+
import { inputHandleId, outputHandleId } from "../../data/handleId";
|
|
27
29
|
import { nodeLoopData } from "../../data/loopBody";
|
|
28
30
|
import { triggerGlyph } from "../../data/triggerGlyphs";
|
|
31
|
+
import { formatDuration } from "../../utils/formatDuration";
|
|
29
32
|
import { SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
30
33
|
import { runStatusClass, runStatusLabel } from "../../utils/statusStyle";
|
|
34
|
+
import { errorReason } from "../../utils/apiError";
|
|
31
35
|
const props = defineProps({
|
|
32
36
|
workflow: { type: Object, required: true },
|
|
33
37
|
initialRunId: { type: [String, null], required: false },
|
|
@@ -59,7 +63,7 @@ async function loadRuns(reset = false) {
|
|
|
59
63
|
runsPage.value = res.page;
|
|
60
64
|
runsLastPage.value = res.lastPage;
|
|
61
65
|
} catch (e) {
|
|
62
|
-
toast.error(e
|
|
66
|
+
toast.error(errorReason(e, t("integration.replay.toast.loadRunsFailed")));
|
|
63
67
|
} finally {
|
|
64
68
|
runsLoading.value = false;
|
|
65
69
|
}
|
|
@@ -101,7 +105,7 @@ async function loadSteps(runId, isPoll = false) {
|
|
|
101
105
|
}
|
|
102
106
|
} catch (e) {
|
|
103
107
|
if (selectedRunId.value === runId) {
|
|
104
|
-
stepsError.value = e
|
|
108
|
+
stepsError.value = errorReason(e, t("integration.replay.error.loadStepsFailed"));
|
|
105
109
|
}
|
|
106
110
|
} finally {
|
|
107
111
|
if (!isPoll) stepsLoading.value = false;
|
|
@@ -293,7 +297,6 @@ const vfEdges = computed(() => {
|
|
|
293
297
|
return { x: 0, y: 0 };
|
|
294
298
|
};
|
|
295
299
|
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
296
|
-
const opposite = (s) => s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
297
300
|
const edgeKind = (sourceNodeId, output) => {
|
|
298
301
|
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
299
302
|
if (!wn) return "default";
|
|
@@ -331,8 +334,8 @@ const vfEdges = computed(() => {
|
|
|
331
334
|
id: e.id,
|
|
332
335
|
source: e.from.nodeId,
|
|
333
336
|
target: e.to.nodeId,
|
|
334
|
-
sourceHandle:
|
|
335
|
-
targetHandle:
|
|
337
|
+
sourceHandle: outputHandleId(e.from.output, srcSide),
|
|
338
|
+
targetHandle: inputHandleId(OPPOSITE_SIDE[srcSide]),
|
|
336
339
|
type: "flow",
|
|
337
340
|
selectable: false,
|
|
338
341
|
animated: traversed && !targetReached,
|
|
@@ -395,12 +398,6 @@ function formatTime(iso) {
|
|
|
395
398
|
return String(iso);
|
|
396
399
|
}
|
|
397
400
|
}
|
|
398
|
-
function formatDuration(ms) {
|
|
399
|
-
if (ms == null) return "\u2014";
|
|
400
|
-
if (ms < 1e3) return `${ms} ms`;
|
|
401
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)} s`;
|
|
402
|
-
return `${Math.floor(ms / 6e4)}m ${Math.round(ms % 6e4 / 1e3)}s`;
|
|
403
|
-
}
|
|
404
401
|
function pretty(v) {
|
|
405
402
|
try {
|
|
406
403
|
return JSON.stringify(v, null, 2);
|
|
@@ -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
|
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
2
|
import { useIntegrationsApi } from "./integrationsApi.js";
|
|
3
3
|
import { useIntegrationsT } from "./useIntegrationsT.js";
|
|
4
|
-
import {
|
|
4
|
+
import { errorReason } from "../utils/apiError.js";
|
|
5
5
|
export function useIntegrationsRevisions() {
|
|
6
6
|
const api = useIntegrationsApi();
|
|
7
7
|
const t = useIntegrationsT();
|
|
8
8
|
const loading = ref(false);
|
|
9
9
|
const error = ref(null);
|
|
10
|
-
function captureError(e, fallback) {
|
|
11
|
-
if (e instanceof IntegrationsApiError) return e.message;
|
|
12
|
-
return e?.message ?? fallback;
|
|
13
|
-
}
|
|
14
10
|
async function list(workflowId, page = 1, perPage = 25) {
|
|
15
11
|
loading.value = true;
|
|
16
12
|
error.value = null;
|
|
@@ -18,7 +14,7 @@ export function useIntegrationsRevisions() {
|
|
|
18
14
|
const res = await api.get(`/workflows/${workflowId}/revisions?page=${page}&per_page=${perPage}`);
|
|
19
15
|
return { revisions: res.data ?? [], meta: res.meta };
|
|
20
16
|
} catch (e) {
|
|
21
|
-
error.value =
|
|
17
|
+
error.value = errorReason(e, t("integration.revisions.error.loadFailed"));
|
|
22
18
|
throw e;
|
|
23
19
|
} finally {
|
|
24
20
|
loading.value = false;
|
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
import type { RunStatus, RunStepRow, WorkflowRun } from '../types/integrations.js';
|
|
2
|
+
import type { RunFeedQuery } from '../utils/runFeedQuery.js';
|
|
3
|
+
export type { RunFeedQuery };
|
|
2
4
|
interface RunSummary {
|
|
3
5
|
total: number;
|
|
4
6
|
in_flight: number;
|
|
5
7
|
by_status: Record<string, number>;
|
|
6
8
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Which page of the tenant's list to read.
|
|
9
|
-
*
|
|
10
|
-
* Only the page: `/runs` also narrows by status, trigger kind, workflow, window
|
|
11
|
-
* and text, and none of that is driven from here yet. The shared DataTable
|
|
12
|
-
* narrows and searches what it holds, and it cannot report a filter selection
|
|
13
|
-
* back to the page under `serverMode` — so a narrowing sent to the platform
|
|
14
|
-
* would have nothing to set it. That is the remaining half of PO-231, and the
|
|
15
|
-
* parameters are waiting on the endpoint for it.
|
|
16
|
-
*/
|
|
17
|
-
export interface RunFeedQuery {
|
|
18
|
-
page: number;
|
|
19
|
-
perPage: number;
|
|
20
|
-
}
|
|
21
9
|
export declare function useIntegrationsRuns(): {
|
|
22
10
|
runs: any;
|
|
23
11
|
loading: any;
|
|
@@ -29,6 +17,8 @@ export declare function useIntegrationsRuns(): {
|
|
|
29
17
|
summary: any;
|
|
30
18
|
hasMore: any;
|
|
31
19
|
query: any;
|
|
20
|
+
setQuery: (patch: Partial<RunFeedQuery>) => Promise<WorkflowRun[]>;
|
|
21
|
+
narrowedTotal: any;
|
|
32
22
|
load: (force?: boolean) => Promise<WorkflowRun[]>;
|
|
33
23
|
loadSummary: () => Promise<RunSummary>;
|
|
34
24
|
loadForWorkflow: (workflowId: number, workflowName: string, opts?: {
|
|
@@ -77,4 +67,3 @@ export declare function useIntegrationsRuns(): {
|
|
|
77
67
|
}>;
|
|
78
68
|
getStackTrace: (workflowId: number | string, runId: number | string) => Promise<string>;
|
|
79
69
|
};
|
|
80
|
-
export {};
|