@revenexx/studio-integrations 0.6.0 → 0.7.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 +227 -0
- package/README.md +31 -0
- package/dist/module.d.mts +17 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +29 -28
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +38 -37
- 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 +16 -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.vue +15 -14
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +9 -8
- package/dist/runtime/components/Integrations/NodeConfigField.vue +43 -42
- package/dist/runtime/components/Integrations/NodeInspector.vue +225 -216
- package/dist/runtime/components/Integrations/NodeOutputSchema.d.vue.ts +1 -1
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue +3 -2
- package/dist/runtime/components/Integrations/NodeOutputSchema.vue.d.ts +1 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +30 -29
- 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 +37 -23
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +14 -4
- package/dist/runtime/components/Integrations/TemplateCard.vue +16 -6
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +10 -9
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +34 -33
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +36 -35
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +8 -7
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +6 -4
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +82 -79
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +24 -23
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +7 -6
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +19 -20
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +52 -39
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +20 -19
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +9 -8
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +12 -9
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +27 -26
- 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.js +6 -4
- 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 +3 -1
- 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/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.js +4 -2
- package/dist/runtime/composables/useIntegrationsWorkflows.js +5 -3
- package/dist/runtime/composables/useNodeConfigResolve.js +2 -1
- package/dist/runtime/data/integrationNodeCatalog.d.ts +41 -0
- package/dist/runtime/data/integrationNodeCatalog.js +14 -0
- package/dist/runtime/data/integrationTemplates.d.ts +9 -0
- package/dist/runtime/data/integrationTemplates.js +7 -1
- package/dist/runtime/data/payloadForm.js +2 -1
- package/dist/runtime/data/workflowIconCatalog.js +2 -1
- package/dist/runtime/i18n/messages/de.json +1207 -0
- package/dist/runtime/i18n/messages/en.json +1207 -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 +70 -52
- package/dist/runtime/pages/integrations/index.vue +50 -56
- 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 +77 -68
- package/dist/runtime/pages/integrations/templates/index.vue +35 -27
- package/dist/runtime/pages/integrations/workflows/[id].vue +164 -99
- 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/utils/credentialState.d.ts +24 -1
- package/dist/runtime/utils/credentialState.js +26 -11
- package/dist/runtime/utils/localize.d.ts +18 -5
- package/dist/runtime/utils/localize.js +32 -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/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
|
@@ -24,6 +24,7 @@ const props = defineProps({
|
|
|
24
24
|
});
|
|
25
25
|
const emit = defineEmits(["restored"]);
|
|
26
26
|
const revisionsApi = useIntegrationsRevisions();
|
|
27
|
+
const t = useIntegrationsT();
|
|
27
28
|
const entries = ref([]);
|
|
28
29
|
const page = ref(1);
|
|
29
30
|
const lastPage = ref(1);
|
|
@@ -110,7 +111,7 @@ function buildPreview(blob) {
|
|
|
110
111
|
const nodePos = (id) => {
|
|
111
112
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
112
113
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
113
|
-
const ti = blob.triggers.findIndex((
|
|
114
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
114
115
|
if (ti >= 0) return triggerPos(id, ti);
|
|
115
116
|
return { x: 0, y: 0 };
|
|
116
117
|
};
|
|
@@ -149,16 +150,16 @@ function buildPreview(blob) {
|
|
|
149
150
|
position: { x: s.position.x, y: s.position.y },
|
|
150
151
|
zIndex: -100 + i,
|
|
151
152
|
draggable: false,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
// No callbacks: that absence is what makes the note read-only — it then
|
|
154
|
+
// draws no toolbar, no resize handle and does not open for typing (PO-284).
|
|
155
|
+
data: { sticky: s }
|
|
155
156
|
})),
|
|
156
|
-
...blob.triggers.map((
|
|
157
|
-
id:
|
|
157
|
+
...blob.triggers.map((t2, i) => ({
|
|
158
|
+
id: t2.id,
|
|
158
159
|
type: "trigger",
|
|
159
|
-
position: triggerPos(
|
|
160
|
+
position: triggerPos(t2.id, i),
|
|
160
161
|
draggable: false,
|
|
161
|
-
data:
|
|
162
|
+
data: t2
|
|
162
163
|
})),
|
|
163
164
|
...blob.nodes.map((n) => ({
|
|
164
165
|
id: n.id,
|
|
@@ -218,23 +219,23 @@ async function select(revision) {
|
|
|
218
219
|
buildPreview(detail.blob);
|
|
219
220
|
}
|
|
220
221
|
} catch (e) {
|
|
221
|
-
toast.error(e?.message ?? "
|
|
222
|
+
toast.error(e?.message ?? t("integration.history.toast.loadRevisionFailed"));
|
|
222
223
|
} finally {
|
|
223
224
|
detailLoading.value = false;
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
async function restoreRevision(rev) {
|
|
227
|
-
if (!confirm(
|
|
228
|
+
if (!confirm(t("integration.history.confirm.restore", { revision: rev.revision }))) return;
|
|
228
229
|
restoring.value = rev.revision;
|
|
229
230
|
try {
|
|
230
231
|
await revisionsApi.restore(props.workflow.id, rev.revision);
|
|
231
|
-
toast.success(
|
|
232
|
+
toast.success(t("integration.history.toast.restored", { revision: rev.revision }));
|
|
232
233
|
detailCache.clear();
|
|
233
234
|
emit("restored");
|
|
234
235
|
await loadList(true);
|
|
235
236
|
void select(null);
|
|
236
237
|
} catch (e) {
|
|
237
|
-
toast.error(e?.message ?? "
|
|
238
|
+
toast.error(e?.message ?? t("integration.history.toast.restoreFailed"));
|
|
238
239
|
} finally {
|
|
239
240
|
restoring.value = null;
|
|
240
241
|
}
|
|
@@ -259,7 +260,7 @@ const nodeTypes = {
|
|
|
259
260
|
const edgeTypes = {
|
|
260
261
|
flow: markRaw(WorkflowEdgeView)
|
|
261
262
|
};
|
|
262
|
-
const previewTitle = computed(() => selected.value === null ?
|
|
263
|
+
const previewTitle = computed(() => selected.value === null ? t("integration.history.preview.title.current", { revision: props.workflow.revision }) : t("integration.history.preview.title.revision", { revision: selected.value }));
|
|
263
264
|
onMounted(() => {
|
|
264
265
|
void loadList(true);
|
|
265
266
|
void select(null);
|
|
@@ -311,9 +312,9 @@ onMounted(() => {
|
|
|
311
312
|
<!-- Revision list -->
|
|
312
313
|
<aside class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
313
314
|
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
314
|
-
<div class="text-sm font-semibold">
|
|
315
|
+
<div class="text-sm font-semibold">{{ t("integration.history.heading.title") }}</div>
|
|
315
316
|
<p class="text-sm text-muted-foreground">
|
|
316
|
-
|
|
317
|
+
{{ t("integration.history.heading.description") }}
|
|
317
318
|
</p>
|
|
318
319
|
</div>
|
|
319
320
|
|
|
@@ -327,17 +328,17 @@ onMounted(() => {
|
|
|
327
328
|
>
|
|
328
329
|
<div class="flex items-center justify-between gap-2">
|
|
329
330
|
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(workflow.updated_at) }}</span>
|
|
330
|
-
<Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">
|
|
331
|
+
<Badge variant="outline" class="text-xs font-medium uppercase border-emerald-500/30 text-emerald-700">{{ t("integration.history.badge.current") }}</Badge>
|
|
331
332
|
<Badge
|
|
332
333
|
variant="outline"
|
|
333
334
|
class="text-xs font-medium uppercase"
|
|
334
335
|
:class="workflow.active ? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-700' : 'border-black/[0.08] text-muted-foreground'"
|
|
335
|
-
:title="workflow.active ? '
|
|
336
|
+
:title="workflow.active ? t('integration.history.badge.activeHint') : t('integration.history.badge.pausedHint')"
|
|
336
337
|
>
|
|
337
|
-
{{ workflow.active ? "
|
|
338
|
+
{{ workflow.active ? t("integration.history.badge.active") : t("integration.history.badge.paused") }}
|
|
338
339
|
</Badge>
|
|
339
340
|
</div>
|
|
340
|
-
<div class="mt-0.5 text-sm text-muted-foreground">
|
|
341
|
+
<div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: workflow.revision }) }}</div>
|
|
341
342
|
</button>
|
|
342
343
|
|
|
343
344
|
<!-- Archived revisions -->
|
|
@@ -351,7 +352,7 @@ onMounted(() => {
|
|
|
351
352
|
<div class="flex items-center justify-between gap-2">
|
|
352
353
|
<span class="text-sm font-medium tabular-nums">{{ formatDateTime(rev.archived_at) }}</span>
|
|
353
354
|
</div>
|
|
354
|
-
<div class="mt-0.5 text-sm text-muted-foreground">
|
|
355
|
+
<div class="mt-0.5 text-sm text-muted-foreground">{{ t("integration.history.list.revisionLabel", { revision: rev.revision }) }}</div>
|
|
355
356
|
</button>
|
|
356
357
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 opacity-0 transition group-hover:opacity-100">
|
|
357
358
|
<Button
|
|
@@ -362,13 +363,13 @@ onMounted(() => {
|
|
|
362
363
|
@click.stop="restoreRevision(rev)"
|
|
363
364
|
>
|
|
364
365
|
<Loader2 v-if="restoring === rev.revision" :size="11" class="mr-1 animate-spin" />
|
|
365
|
-
|
|
366
|
+
{{ t("integration.history.action.restore") }}
|
|
366
367
|
</Button>
|
|
367
368
|
</div>
|
|
368
369
|
</div>
|
|
369
370
|
|
|
370
371
|
<p v-if="!entries.length && !listLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
371
|
-
|
|
372
|
+
{{ t("integration.history.state.empty") }}
|
|
372
373
|
</p>
|
|
373
374
|
<p v-if="revisionsApi.error.value" class="px-2 py-2 text-sm text-rose-600">
|
|
374
375
|
{{ revisionsApi.error.value }}
|
|
@@ -393,7 +394,7 @@ onMounted(() => {
|
|
|
393
394
|
class="w-full text-sm"
|
|
394
395
|
@click="loadMore"
|
|
395
396
|
>
|
|
396
|
-
|
|
397
|
+
{{ t("integration.history.action.loadMore") }}
|
|
397
398
|
</Button>
|
|
398
399
|
</div>
|
|
399
400
|
</aside>
|
|
@@ -8,6 +8,7 @@ const model = defineModel({ type: [Object, null], ...{ default: null } });
|
|
|
8
8
|
const props = defineProps({
|
|
9
9
|
triggerLabelledby: { type: String, required: false }
|
|
10
10
|
});
|
|
11
|
+
const t = useIntegrationsT();
|
|
11
12
|
const valueId = useId();
|
|
12
13
|
const triggerLabelledby = computed(
|
|
13
14
|
() => props.triggerLabelledby ? `${props.triggerLabelledby} ${valueId}` : void 0
|
|
@@ -26,10 +27,10 @@ function isImage(id) {
|
|
|
26
27
|
return (imageIdFromSrc(model.value.src) ?? model.value.src) === id;
|
|
27
28
|
}
|
|
28
29
|
const selectedLabel = computed(() => {
|
|
29
|
-
if (!model.value) return "
|
|
30
|
-
if (model.value.kind === "solar") return model.value.name ?? "
|
|
30
|
+
if (!model.value) return t("integration.workflows.iconPicker.label.default");
|
|
31
|
+
if (model.value.kind === "solar") return model.value.name ?? t("integration.workflows.iconPicker.label.iconFallback");
|
|
31
32
|
const id = imageIdFromSrc(model.value.src) ?? model.value.src;
|
|
32
|
-
return logos.value.find((l) => l.id === id)?.label ?? "
|
|
33
|
+
return logos.value.find((l) => l.id === id)?.label ?? t("integration.workflows.iconPicker.label.imageFallback");
|
|
33
34
|
});
|
|
34
35
|
</script>
|
|
35
36
|
|
|
@@ -57,8 +58,8 @@ const selectedLabel = computed(() => {
|
|
|
57
58
|
type="button"
|
|
58
59
|
class="flex size-8 items-center justify-center rounded-md border text-sm text-muted-foreground transition hover:bg-muted/50"
|
|
59
60
|
:class="!model ? 'border-emerald-500/40 bg-emerald-500/10' : 'border-transparent'"
|
|
60
|
-
title="
|
|
61
|
-
aria-label="
|
|
61
|
+
:title="t('integration.workflows.iconPicker.action.none')"
|
|
62
|
+
:aria-label="t('integration.workflows.iconPicker.action.none')"
|
|
62
63
|
@click="pick(null)"
|
|
63
64
|
>
|
|
64
65
|
—
|
|
@@ -77,7 +78,7 @@ const selectedLabel = computed(() => {
|
|
|
77
78
|
</div>
|
|
78
79
|
|
|
79
80
|
<template v-if="logos.length">
|
|
80
|
-
<div class="text-mono text-muted-foreground">
|
|
81
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.workflows.iconPicker.section.integrations") }}</div>
|
|
81
82
|
<div class="flex flex-wrap gap-1">
|
|
82
83
|
<button
|
|
83
84
|
v-for="l in logos"
|
|
@@ -3,6 +3,7 @@ import { computed, inject, nextTick, ref, watch } from "vue";
|
|
|
3
3
|
import { Handle, Position, useVueFlow } from "@vue-flow/core";
|
|
4
4
|
import { NodeToolbar } from "@vue-flow/node-toolbar";
|
|
5
5
|
import * as Lucide from "lucide-vue-next";
|
|
6
|
+
import { Copy, TrashBinMinimalistic as TrashBin } from "#solar-icons";
|
|
6
7
|
import { NODE_CATEGORIES, findManifest } from "../../data/integrationNodeCatalog";
|
|
7
8
|
import { isTerminalManifest } from "../../data/nodeInputData";
|
|
8
9
|
import { resolveDisplayName, resolveLocalized } from "../../utils/localize";
|
|
@@ -13,6 +14,7 @@ const props = defineProps({
|
|
|
13
14
|
});
|
|
14
15
|
defineOptions({ inheritAttrs: false });
|
|
15
16
|
defineEmits(["updateNodeInternals"]);
|
|
17
|
+
const t = useIntegrationsT();
|
|
16
18
|
const manifest = computed(() => findManifest(props.data.nodeSlug, props.data.nodeVersion));
|
|
17
19
|
const category = computed(() => NODE_CATEGORIES.find((c) => c.key === manifest.value?.category));
|
|
18
20
|
const title = computed(
|
|
@@ -20,10 +22,7 @@ const title = computed(
|
|
|
20
22
|
);
|
|
21
23
|
const inputRequired = computed(() => manifest.value?.input?.required !== false);
|
|
22
24
|
const invalidFields = computed(() => props.data.__invalidFields ?? []);
|
|
23
|
-
const invalidLabel = computed(() => {
|
|
24
|
-
const n = invalidFields.value.length;
|
|
25
|
-
return `Config incomplete \u2014 ${n} setting${n === 1 ? "" : "s"} still need${n === 1 ? "s" : ""} fixing: ${invalidFields.value.join(", ")}`;
|
|
26
|
-
});
|
|
25
|
+
const invalidLabel = computed(() => t("integration.canvas.node.badge.invalidConfig", invalidFields.value.length, { fields: invalidFields.value.join(", ") }));
|
|
27
26
|
const notes = computed(() => (props.data.notes ?? "").trim());
|
|
28
27
|
const noteOpen = ref(false);
|
|
29
28
|
const noteReady = ref(false);
|
|
@@ -62,12 +61,12 @@ const outputs = computed(() => {
|
|
|
62
61
|
const resolved = props.data.__resolvedOutputs;
|
|
63
62
|
if (Array.isArray(resolved) && resolved.length) {
|
|
64
63
|
const ports2 = portsFromOutputs(resolved);
|
|
65
|
-
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: "
|
|
64
|
+
if (!ports2.length) ports2.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
66
65
|
return ports2;
|
|
67
66
|
}
|
|
68
|
-
if (!m) return [{ name: "default", kind: "default", label: "
|
|
67
|
+
if (!m) return [{ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") }];
|
|
69
68
|
const ports = portsFromOutputs(m.outputs ?? []);
|
|
70
|
-
if (!ports.length) ports.push({ name: "default", kind: "default", label: "
|
|
69
|
+
if (!ports.length) ports.push({ name: "default", kind: "default", label: t("integration.canvas.node.port.outputFallback") });
|
|
71
70
|
return ports;
|
|
72
71
|
});
|
|
73
72
|
function portSemantic(p) {
|
|
@@ -183,21 +182,21 @@ function remove() {
|
|
|
183
182
|
<button
|
|
184
183
|
type="button"
|
|
185
184
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-foreground/80 hover:bg-muted/50"
|
|
186
|
-
title="
|
|
187
|
-
aria-label="
|
|
185
|
+
:title="t('integration.canvas.node.action.duplicate')"
|
|
186
|
+
:aria-label="t('integration.canvas.node.action.duplicateAria')"
|
|
188
187
|
@click="duplicate"
|
|
189
188
|
>
|
|
190
|
-
<
|
|
189
|
+
<Copy :size="11" />
|
|
191
190
|
</button>
|
|
192
191
|
<div class="h-4 w-px bg-black/10" />
|
|
193
192
|
<button
|
|
194
193
|
type="button"
|
|
195
194
|
class="flex h-6 items-center gap-1 rounded px-1.5 text-sm text-rose-600 hover:bg-rose-50"
|
|
196
|
-
title="
|
|
197
|
-
aria-label="
|
|
195
|
+
:title="t('integration.common.action.delete')"
|
|
196
|
+
:aria-label="t('integration.canvas.node.action.deleteAria')"
|
|
198
197
|
@click="remove"
|
|
199
198
|
>
|
|
200
|
-
<
|
|
199
|
+
<TrashBin :size="11" />
|
|
201
200
|
</button>
|
|
202
201
|
</NodeToolbar>
|
|
203
202
|
|
|
@@ -219,7 +218,7 @@ function remove() {
|
|
|
219
218
|
>
|
|
220
219
|
<div ref="notePanel" role="tooltip">
|
|
221
220
|
<div class="mb-1 text-[8px] font-semibold uppercase tracking-[0.08em] text-muted-foreground/60">
|
|
222
|
-
|
|
221
|
+
{{ t("integration.canvas.node.note.heading") }}
|
|
223
222
|
</div>
|
|
224
223
|
<p class="whitespace-pre-wrap break-words text-[11px] leading-snug text-foreground/80">{{ notes }}</p>
|
|
225
224
|
</div>
|
|
@@ -234,7 +233,7 @@ function remove() {
|
|
|
234
233
|
:key="`in-${side}`"
|
|
235
234
|
type="target"
|
|
236
235
|
:position="sideToPosition(side)"
|
|
237
|
-
title="
|
|
236
|
+
:title="t('integration.canvas.node.handle.input')"
|
|
238
237
|
class="!size-3 !rounded-full !border-2 !border-white !bg-slate-500 !shadow-sm transition-all hover:!size-3.5 hover:!bg-slate-700"
|
|
239
238
|
/>
|
|
240
239
|
</template>
|
|
@@ -288,8 +287,8 @@ function remove() {
|
|
|
288
287
|
v-if="canConfigure"
|
|
289
288
|
type="button"
|
|
290
289
|
class="nodrag absolute right-1.5 top-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/50 transition-colors hover:bg-muted hover:text-foreground"
|
|
291
|
-
title="
|
|
292
|
-
aria-label="
|
|
290
|
+
:title="t('integration.canvas.node.action.configure')"
|
|
291
|
+
:aria-label="t('integration.canvas.node.action.configure')"
|
|
293
292
|
@click.stop="configure"
|
|
294
293
|
@mousedown.stop
|
|
295
294
|
@dblclick.stop
|
|
@@ -305,7 +304,7 @@ function remove() {
|
|
|
305
304
|
type="button"
|
|
306
305
|
class="nodrag absolute right-1.5 z-10 flex size-5 items-center justify-center rounded text-muted-foreground/45 transition-colors hover:bg-muted hover:text-foreground"
|
|
307
306
|
:class="showPortLabels ? 'bottom-4' : 'bottom-1.5'"
|
|
308
|
-
aria-label="
|
|
307
|
+
:aria-label="t('integration.canvas.node.note.heading')"
|
|
309
308
|
:aria-describedby="notePanelId"
|
|
310
309
|
@mouseenter="openNote"
|
|
311
310
|
@mouseleave="releaseNote"
|
|
@@ -332,7 +331,7 @@ function remove() {
|
|
|
332
331
|
class="pointer-events-none absolute bottom-1 left-1/2 flex -translate-x-1/2 select-none items-center gap-1 whitespace-nowrap text-[9px] font-semibold uppercase tracking-wide text-muted-foreground"
|
|
333
332
|
>
|
|
334
333
|
<Lucide.LoaderCircle :size="9" class="animate-spin" />
|
|
335
|
-
|
|
334
|
+
{{ t("integration.canvas.node.port.resolving") }}
|
|
336
335
|
</span>
|
|
337
336
|
<template v-else>
|
|
338
337
|
<span
|
|
@@ -364,7 +363,7 @@ function remove() {
|
|
|
364
363
|
<div v-if="data.nodeVersion" class="mt-0.5 flex items-center gap-1.5 text-sm text-muted-foreground">
|
|
365
364
|
<span
|
|
366
365
|
class="shrink-0 text-[10px] font-medium leading-none tabular-nums text-muted-foreground/55"
|
|
367
|
-
:title="
|
|
366
|
+
:title="t('integration.canvas.node.meta.versionTitle', { version: data.nodeVersion })"
|
|
368
367
|
>v{{ data.nodeVersion }}</span>
|
|
369
368
|
</div>
|
|
370
369
|
</div>
|
|
@@ -26,6 +26,7 @@ import StickyNoteView from "./StickyNoteView.vue";
|
|
|
26
26
|
import GroupFrameView from "./GroupFrameView.vue";
|
|
27
27
|
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
28
28
|
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
29
|
+
import { SAFETY_POLL_MS } from "../../utils/pollBudget";
|
|
29
30
|
import { runStatusLabel, statusClass } from "../../utils/statusStyle";
|
|
30
31
|
const props = defineProps({
|
|
31
32
|
workflow: { type: Object, required: true },
|
|
@@ -33,9 +34,14 @@ const props = defineProps({
|
|
|
33
34
|
single: { type: Boolean, required: false }
|
|
34
35
|
});
|
|
35
36
|
const { loadForWorkflow, getSteps } = useIntegrationsRuns();
|
|
37
|
+
const t = useIntegrationsT();
|
|
36
38
|
const TERMINAL = /* @__PURE__ */ new Set(["completed", "succeeded", "failed", "cancelled", "terminated"]);
|
|
37
39
|
const STEPS_POLL_MS = 4e3;
|
|
38
40
|
const PLAY_INTERVAL_MS = 3e3;
|
|
41
|
+
const { delivering: realtimeDelivering, runTick, lastRunEvent } = useIntegrationsRealtime();
|
|
42
|
+
function stepsPollDelay() {
|
|
43
|
+
return realtimeDelivering.value ? SAFETY_POLL_MS : STEPS_POLL_MS;
|
|
44
|
+
}
|
|
39
45
|
const runs = ref([]);
|
|
40
46
|
const runsPage = ref(1);
|
|
41
47
|
const runsLastPage = ref(1);
|
|
@@ -53,7 +59,7 @@ async function loadRuns(reset = false) {
|
|
|
53
59
|
runsPage.value = res.page;
|
|
54
60
|
runsLastPage.value = res.lastPage;
|
|
55
61
|
} catch (e) {
|
|
56
|
-
toast.error(e?.message ?? "
|
|
62
|
+
toast.error(e?.message ?? t("integration.replay.toast.loadRunsFailed"));
|
|
57
63
|
} finally {
|
|
58
64
|
runsLoading.value = false;
|
|
59
65
|
}
|
|
@@ -91,16 +97,23 @@ async function loadSteps(runId, isPoll = false) {
|
|
|
91
97
|
if (runStatus.value && !TERMINAL.has(runStatus.value)) {
|
|
92
98
|
pollTimer = window.setTimeout(() => {
|
|
93
99
|
void loadSteps(runId, true);
|
|
94
|
-
},
|
|
100
|
+
}, stepsPollDelay());
|
|
95
101
|
}
|
|
96
102
|
} catch (e) {
|
|
97
103
|
if (selectedRunId.value === runId) {
|
|
98
|
-
stepsError.value = e?.message ?? "
|
|
104
|
+
stepsError.value = e?.message ?? t("integration.replay.error.loadStepsFailed");
|
|
99
105
|
}
|
|
100
106
|
} finally {
|
|
101
107
|
if (!isPoll) stepsLoading.value = false;
|
|
102
108
|
}
|
|
103
109
|
}
|
|
110
|
+
watch(runTick, () => {
|
|
111
|
+
const runId = selectedRunId.value;
|
|
112
|
+
if (!runId) return;
|
|
113
|
+
const event = lastRunEvent.value;
|
|
114
|
+
if (event && event.topicId !== runId) return;
|
|
115
|
+
void loadSteps(runId, true);
|
|
116
|
+
});
|
|
104
117
|
function selectRun(id) {
|
|
105
118
|
if (selectedRunId.value === id) return;
|
|
106
119
|
selectedRunId.value = id;
|
|
@@ -173,7 +186,7 @@ const finalStatus = computed(() => {
|
|
|
173
186
|
return m;
|
|
174
187
|
});
|
|
175
188
|
const runNodeIds = computed(() => new Set(steps.value.map((s) => s.node_id)));
|
|
176
|
-
const triggerIds = computed(() => new Set(props.workflow.blob.triggers.map((
|
|
189
|
+
const triggerIds = computed(() => new Set(props.workflow.blob.triggers.map((t2) => t2.id)));
|
|
177
190
|
function edgeColorByKind(kind) {
|
|
178
191
|
return kind === "error" ? "#cbd5e1" : "#94a3b8";
|
|
179
192
|
}
|
|
@@ -230,7 +243,7 @@ const vfNodes = computed(() => {
|
|
|
230
243
|
const posOf = (id) => {
|
|
231
244
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
232
245
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
233
|
-
const ti = blob.triggers.findIndex((
|
|
246
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
234
247
|
if (ti >= 0) return triggerPos(id, ti);
|
|
235
248
|
return { x: 0, y: 0 };
|
|
236
249
|
};
|
|
@@ -244,17 +257,17 @@ const vfNodes = computed(() => {
|
|
|
244
257
|
zIndex: -100 + i,
|
|
245
258
|
draggable: false,
|
|
246
259
|
class: "replay-dim",
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
// No callbacks: that absence is what makes the note read-only — it then
|
|
261
|
+
// draws no toolbar, no resize handle and does not open for typing (PO-284).
|
|
262
|
+
data: { sticky: s }
|
|
250
263
|
})),
|
|
251
|
-
...blob.triggers.map((
|
|
252
|
-
id:
|
|
264
|
+
...blob.triggers.map((t2, i) => ({
|
|
265
|
+
id: t2.id,
|
|
253
266
|
type: "trigger",
|
|
254
|
-
position: triggerPos(
|
|
267
|
+
position: triggerPos(t2.id, i),
|
|
255
268
|
draggable: false,
|
|
256
|
-
class: replayNodeClass(
|
|
257
|
-
data:
|
|
269
|
+
class: replayNodeClass(t2.id, "trigger"),
|
|
270
|
+
data: t2
|
|
258
271
|
})),
|
|
259
272
|
...blob.nodes.map((n) => ({
|
|
260
273
|
id: n.id,
|
|
@@ -274,7 +287,7 @@ const vfEdges = computed(() => {
|
|
|
274
287
|
const nodePos = (id) => {
|
|
275
288
|
const wn = blob.nodes.find((n) => n.id === id);
|
|
276
289
|
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
277
|
-
const ti = blob.triggers.findIndex((
|
|
290
|
+
const ti = blob.triggers.findIndex((t2) => t2.id === id);
|
|
278
291
|
if (ti >= 0) return blob.triggerPositions?.[id] ?? { x: -40, y: 60 + ti * 140 };
|
|
279
292
|
return { x: 0, y: 0 };
|
|
280
293
|
};
|
|
@@ -458,12 +471,12 @@ watch(() => props.initialRunId, (id) => {
|
|
|
458
471
|
<!-- Which run is on the canvas -->
|
|
459
472
|
<div class="absolute top-3 left-3 z-10 flex items-center gap-2 rounded-lg border border-black/[0.08] bg-white/95 px-2.5 py-1.5 text-sm shadow-sm backdrop-blur-sm">
|
|
460
473
|
<Play :size="13" class="text-muted-foreground" />
|
|
461
|
-
<span class="font-medium">{{ selectedRunId ?
|
|
474
|
+
<span class="font-medium">{{ selectedRunId ? t("integration.replay.canvas.runLabel", { id: selectedRunId }) : t("integration.replay.canvas.pickRun") }}</span>
|
|
462
475
|
<Badge v-if="runStatus" variant="outline" class="border text-xs font-medium uppercase" :class="statusClass(runStatus)">
|
|
463
476
|
{{ runStatusLabel(runStatus) }}
|
|
464
477
|
</Badge>
|
|
465
478
|
<Badge v-if="stepsSource" variant="outline" class="border text-xs font-medium uppercase text-muted-foreground">
|
|
466
|
-
{{ stepsSource === "temporal" ? "
|
|
479
|
+
{{ stepsSource === "temporal" ? t("integration.replay.badge.live") : t("integration.replay.badge.snapshot") }}
|
|
467
480
|
</Badge>
|
|
468
481
|
<Loader2 v-if="stepsLoading" :size="13" class="animate-spin text-muted-foreground" />
|
|
469
482
|
</div>
|
|
@@ -479,14 +492,14 @@ watch(() => props.initialRunId, (id) => {
|
|
|
479
492
|
<div class="flex items-center gap-1">
|
|
480
493
|
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor === 0" @click="seek(safeCursor - 1)">
|
|
481
494
|
<Prev :size="13" />
|
|
482
|
-
|
|
495
|
+
{{ t("integration.replay.transport.action.prev") }}
|
|
483
496
|
</Button>
|
|
484
497
|
<Button size="sm" class="h-7" @click="togglePlay">
|
|
485
498
|
<component :is="playing ? Pause : Play" :size="13" class="mr-1" />
|
|
486
|
-
{{ playing ? "
|
|
499
|
+
{{ playing ? t("integration.replay.transport.action.pause") : t("integration.replay.transport.action.play") }}
|
|
487
500
|
</Button>
|
|
488
501
|
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor >= steps.length - 1" @click="seek(safeCursor + 1)">
|
|
489
|
-
|
|
502
|
+
{{ t("integration.replay.transport.action.next") }}
|
|
490
503
|
<Next :size="13" />
|
|
491
504
|
</Button>
|
|
492
505
|
</div>
|
|
@@ -497,17 +510,17 @@ watch(() => props.initialRunId, (id) => {
|
|
|
497
510
|
:max="steps.length - 1"
|
|
498
511
|
:value="safeCursor"
|
|
499
512
|
class="h-1.5 flex-1 cursor-pointer accent-primary"
|
|
500
|
-
aria-label="
|
|
513
|
+
:aria-label="t('integration.replay.transport.scrubberAria')"
|
|
501
514
|
@input="seek(Number($event.target.value))"
|
|
502
515
|
>
|
|
503
516
|
|
|
504
517
|
<span class="shrink-0 text-sm tabular-nums text-muted-foreground">
|
|
505
|
-
|
|
518
|
+
{{ t("integration.replay.transport.stepCounter", { current: safeCursor + 1, total: steps.length }) }}
|
|
506
519
|
<span class="font-mono">· {{ activeStepTitle }}</span>
|
|
507
520
|
</span>
|
|
508
521
|
</template>
|
|
509
522
|
<p v-else-if="!stepsLoading" class="text-sm text-muted-foreground">
|
|
510
|
-
{{ selectedRunId ? "
|
|
523
|
+
{{ selectedRunId ? t("integration.replay.state.noSteps") : t("integration.replay.state.pickRunHint") }}
|
|
511
524
|
</p>
|
|
512
525
|
</div>
|
|
513
526
|
</div>
|
|
@@ -517,10 +530,10 @@ watch(() => props.initialRunId, (id) => {
|
|
|
517
530
|
<aside class="flex w-[32rem] shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
518
531
|
<div class="flex items-center gap-2 border-b border-black/[0.06] px-3 py-2">
|
|
519
532
|
<div class="min-w-0 flex-1">
|
|
520
|
-
<div class="truncate font-mono text-sm font-medium">{{ activeStepTitle || "
|
|
533
|
+
<div class="truncate font-mono text-sm font-medium">{{ activeStepTitle || t("integration.replay.debug.titleFallback") }}</div>
|
|
521
534
|
<div class="text-xs tabular-nums text-muted-foreground">
|
|
522
|
-
<template v-if="steps.length">
|
|
523
|
-
<template v-else>
|
|
535
|
+
<template v-if="steps.length">{{ t("integration.replay.debug.stepCounter", { current: safeCursor + 1, total: steps.length }) }}</template>
|
|
536
|
+
<template v-else>{{ t("integration.replay.debug.noStepSelected") }}</template>
|
|
524
537
|
</div>
|
|
525
538
|
</div>
|
|
526
539
|
<Badge v-if="activeStep" variant="outline" class="shrink-0 border text-xs font-medium uppercase" :class="statusClass(activeStep.status)">
|
|
@@ -531,59 +544,59 @@ watch(() => props.initialRunId, (id) => {
|
|
|
531
544
|
<div v-if="activeStep" class="min-h-0 flex-1 space-y-4 overflow-y-auto p-3">
|
|
532
545
|
<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
|
|
533
546
|
<div>
|
|
534
|
-
<div class="text-mono text-muted-foreground">
|
|
547
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.attempt") }}</div>
|
|
535
548
|
<div class="tabular-nums">{{ activeStep.attempt + 1 }}</div>
|
|
536
549
|
</div>
|
|
537
550
|
<div>
|
|
538
|
-
<div class="text-mono text-muted-foreground">
|
|
551
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.duration") }}</div>
|
|
539
552
|
<div class="tabular-nums">{{ formatDuration(activeStep.duration_ms) }}</div>
|
|
540
553
|
</div>
|
|
541
554
|
<div>
|
|
542
|
-
<div class="text-mono text-muted-foreground">
|
|
555
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.started") }}</div>
|
|
543
556
|
<div class="tabular-nums">{{ formatTime(activeStep.started_at) }}</div>
|
|
544
557
|
</div>
|
|
545
558
|
<div>
|
|
546
|
-
<div class="text-mono text-muted-foreground">
|
|
559
|
+
<div class="text-mono text-muted-foreground">{{ t("integration.replay.debug.field.completed") }}</div>
|
|
547
560
|
<div class="tabular-nums">{{ formatTime(activeStep.completed_at) }}</div>
|
|
548
561
|
</div>
|
|
549
562
|
</div>
|
|
550
563
|
|
|
551
564
|
<div v-if="activeStep.branch" class="text-sm">
|
|
552
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
565
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.branch") }}</div>
|
|
553
566
|
<code class="rounded bg-black/[0.05] px-1.5 py-0.5 font-mono">{{ activeStep.branch }}</code>
|
|
554
567
|
</div>
|
|
555
568
|
|
|
556
569
|
<div v-if="activeStep.error" class="text-sm">
|
|
557
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
570
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.error") }}</div>
|
|
558
571
|
<pre class="max-h-40 overflow-auto rounded-md border border-rose-500/20 bg-rose-500/5 p-2 font-mono text-sm text-rose-700">{{ activeStep.error }}</pre>
|
|
559
572
|
</div>
|
|
560
573
|
|
|
561
574
|
<div v-if="activeStep.input !== void 0 && activeStep.input !== null" class="text-sm">
|
|
562
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
575
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.input") }}</div>
|
|
563
576
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.input) }}</pre>
|
|
564
577
|
</div>
|
|
565
578
|
|
|
566
579
|
<div v-if="activeStep.config !== void 0 && activeStep.config !== null" class="text-sm">
|
|
567
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
580
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.config") }}</div>
|
|
568
581
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.config) }}</pre>
|
|
569
582
|
</div>
|
|
570
583
|
|
|
571
584
|
<div v-if="activeStep.output !== void 0 && activeStep.output !== null" class="text-sm">
|
|
572
|
-
<div class="text-mono text-muted-foreground mb-1">
|
|
585
|
+
<div class="text-mono text-muted-foreground mb-1">{{ t("integration.replay.debug.field.output") }}</div>
|
|
573
586
|
<pre class="max-h-56 overflow-auto rounded-md border border-black/[0.06] bg-muted/20 p-2 font-mono text-sm">{{ pretty(activeStep.output) }}</pre>
|
|
574
587
|
</div>
|
|
575
588
|
</div>
|
|
576
589
|
<p v-else class="p-4 text-sm text-muted-foreground">
|
|
577
|
-
|
|
590
|
+
{{ t("integration.replay.debug.emptyHint") }}
|
|
578
591
|
</p>
|
|
579
592
|
</aside>
|
|
580
593
|
|
|
581
594
|
<!-- Run list — hidden in single mode (the run detail replays ONE run). -->
|
|
582
595
|
<aside v-if="!single" class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
583
596
|
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
584
|
-
<div class="text-sm font-semibold">
|
|
597
|
+
<div class="text-sm font-semibold">{{ t("integration.replay.runsList.heading") }}</div>
|
|
585
598
|
<p class="text-sm text-muted-foreground">
|
|
586
|
-
|
|
599
|
+
{{ t("integration.replay.runsList.description") }}
|
|
587
600
|
</p>
|
|
588
601
|
</div>
|
|
589
602
|
|
|
@@ -612,7 +625,7 @@ watch(() => props.initialRunId, (id) => {
|
|
|
612
625
|
</button>
|
|
613
626
|
|
|
614
627
|
<p v-if="!runs.length && !runsLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
615
|
-
|
|
628
|
+
{{ t("integration.replay.runsList.empty") }}
|
|
616
629
|
</p>
|
|
617
630
|
|
|
618
631
|
<div v-if="runsLoading" class="flex justify-center py-3">
|
|
@@ -625,7 +638,7 @@ watch(() => props.initialRunId, (id) => {
|
|
|
625
638
|
class="w-full text-sm"
|
|
626
639
|
@click="loadMoreRuns"
|
|
627
640
|
>
|
|
628
|
-
|
|
641
|
+
{{ t("integration.replay.runsList.loadMore") }}
|
|
629
642
|
</Button>
|
|
630
643
|
</div>
|
|
631
644
|
</aside>
|