@revenexx/studio-integrations 0.1.4 → 0.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/README.md +31 -0
- package/dist/module.d.mts +2 -2
- package/dist/module.json +1 -1
- package/dist/runtime/components/Integrations/CredentialCreateDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue +279 -0
- package/dist/runtime/components/Integrations/CredentialCreateDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.d.vue.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue +342 -0
- package/dist/runtime/components/Integrations/CredentialDetailDialog.vue.d.ts +20 -0
- package/dist/runtime/components/Integrations/CredentialLogo.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue +37 -0
- package/dist/runtime/components/Integrations/CredentialLogo.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue +364 -0
- package/dist/runtime/components/Integrations/CredentialRevenexxSource.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.d.vue.ts +15 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue +71 -0
- package/dist/runtime/components/Integrations/CredentialTypePicker.vue.d.ts +15 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.d.vue.ts +5 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue +151 -0
- package/dist/runtime/components/Integrations/DeadLettersPanel.vue.d.ts +5 -0
- package/dist/runtime/components/Integrations/EditorTip.d.vue.ts +17 -0
- package/dist/runtime/components/Integrations/EditorTip.vue +46 -0
- package/dist/runtime/components/Integrations/EditorTip.vue.d.ts +17 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue +64 -0
- package/dist/runtime/components/Integrations/EventTypeCombobox.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/ExpressionInput.d.vue.ts +19 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue +236 -0
- package/dist/runtime/components/Integrations/ExpressionInput.vue.d.ts +19 -0
- package/dist/runtime/components/Integrations/GroupFrameView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue +222 -0
- package/dist/runtime/components/Integrations/GroupFrameView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue +184 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicField.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.d.vue.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue +102 -0
- package/dist/runtime/components/Integrations/NodeConfigDynamicSchema.vue.d.ts +16 -0
- package/dist/runtime/components/Integrations/NodeConfigField.d.vue.ts +23 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue +587 -0
- package/dist/runtime/components/Integrations/NodeConfigField.vue.d.ts +23 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue +80 -0
- package/dist/runtime/components/Integrations/NodeDynamicOutputsResolver.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/NodeInspector.d.vue.ts +22 -3
- package/dist/runtime/components/Integrations/NodeInspector.vue +1074 -294
- package/dist/runtime/components/Integrations/NodeInspector.vue.d.ts +22 -3
- package/dist/runtime/components/Integrations/NodeLogo.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue +38 -0
- package/dist/runtime/components/Integrations/NodeLogo.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/NodePalette.d.vue.ts +22 -1
- package/dist/runtime/components/Integrations/NodePalette.vue +411 -90
- package/dist/runtime/components/Integrations/NodePalette.vue.d.ts +22 -1
- package/dist/runtime/components/Integrations/QueryBuilder.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue +239 -0
- package/dist/runtime/components/Integrations/QueryBuilder.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.d.vue.ts → RunActionsMenu.d.vue.ts} +3 -4
- package/dist/runtime/components/Integrations/RunActionsMenu.vue +86 -0
- package/dist/runtime/components/Integrations/{RunDetailDialog.vue.d.ts → RunActionsMenu.vue.d.ts} +3 -4
- package/dist/runtime/components/Integrations/RunDetailPanel.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue +777 -0
- package/dist/runtime/components/Integrations/RunDetailPanel.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue +175 -0
- package/dist/runtime/components/Integrations/RunPayloadInput.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/RunResponseModal.d.vue.ts +27 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue +84 -0
- package/dist/runtime/components/Integrations/RunResponseModal.vue.d.ts +27 -0
- package/dist/runtime/components/Integrations/RunsTable.d.vue.ts +21 -0
- package/dist/runtime/components/Integrations/RunsTable.vue +114 -0
- package/dist/runtime/components/Integrations/RunsTable.vue.d.ts +21 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.d.vue.ts +11 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue +91 -0
- package/dist/runtime/components/Integrations/SftpAccountImportDialog.vue.d.ts +11 -0
- package/dist/runtime/components/Integrations/StickyNoteView.d.vue.ts +28 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue +208 -0
- package/dist/runtime/components/Integrations/StickyNoteView.vue.d.ts +28 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.d.vue.ts +29 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue +201 -0
- package/dist/runtime/components/Integrations/TemplateFlowPreview.vue.d.ts +29 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.d.vue.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue +138 -0
- package/dist/runtime/components/Integrations/WorkflowContextMenu.vue.d.ts +32 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.d.vue.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue +322 -0
- package/dist/runtime/components/Integrations/WorkflowCreateDialog.vue.d.ts +9 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowDuplicateDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Integrations/WorkflowEdgeView.d.vue.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue +68 -6
- package/dist/runtime/components/Integrations/WorkflowEdgeView.vue.d.ts +12 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.d.vue.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowEditor.vue +1424 -144
- package/dist/runtime/components/Integrations/WorkflowEditor.vue.d.ts +8 -1
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue +417 -0
- package/dist/runtime/components/Integrations/WorkflowHistoryPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue +89 -0
- package/dist/runtime/components/Integrations/WorkflowIconPicker.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.d.vue.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue +35 -0
- package/dist/runtime/components/Integrations/WorkflowIconView.vue.d.ts +10 -0
- package/dist/runtime/components/Integrations/WorkflowNodeView.d.vue.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue +114 -84
- package/dist/runtime/components/Integrations/WorkflowNodeView.vue.d.ts +5 -1
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.d.vue.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue +654 -0
- package/dist/runtime/components/Integrations/WorkflowRunReplayPanel.vue.d.ts +12 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.d.vue.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue +136 -0
- package/dist/runtime/components/Integrations/WorkflowSettingsDialog.vue.d.ts +36 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.d.vue.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue +63 -0
- package/dist/runtime/components/Integrations/WorkflowTagsInput.vue.d.ts +14 -0
- package/dist/runtime/components/Integrations/WorkflowTriggerView.vue +35 -15
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.d.vue.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue +265 -0
- package/dist/runtime/components/Integrations/WorkflowVariablesDialog.vue.d.ts +13 -0
- package/dist/runtime/components/Integrations/WorkflowsTable.d.vue.ts +4 -1
- package/dist/runtime/components/Integrations/WorkflowsTable.vue +122 -39
- package/dist/runtime/components/Integrations/WorkflowsTable.vue.d.ts +4 -1
- package/dist/runtime/components/Integrations/workflowLayout.d.ts +5 -0
- package/dist/runtime/components/Integrations/workflowLayout.js +5 -3
- package/dist/runtime/composables/integrationsApi.d.ts +6 -7
- package/dist/runtime/composables/integrationsApi.js +6 -12
- package/dist/runtime/composables/useCanvasShortcuts.d.ts +37 -0
- package/dist/runtime/composables/useCanvasShortcuts.js +62 -0
- package/dist/runtime/composables/useCredentialTypes.js +2 -1
- package/dist/runtime/composables/useIntegrationsAudit.js +2 -1
- package/dist/runtime/composables/useIntegrationsCredentials.js +2 -1
- package/dist/runtime/composables/useIntegrationsDeadLetters.d.ts +22 -0
- package/dist/runtime/composables/useIntegrationsDeadLetters.js +50 -0
- package/dist/runtime/composables/useIntegrationsNodes.d.ts +4 -0
- package/dist/runtime/composables/useIntegrationsNodes.js +8 -4
- package/dist/runtime/composables/useIntegrationsRevisions.d.ts +40 -0
- package/dist/runtime/composables/useIntegrationsRevisions.js +52 -0
- package/dist/runtime/composables/useIntegrationsRuns.d.ts +21 -1
- package/dist/runtime/composables/useIntegrationsRuns.js +94 -5
- package/dist/runtime/composables/useIntegrationsSecrets.js +2 -1
- package/dist/runtime/composables/useIntegrationsTemplates.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsTemplates.js +34 -5
- package/dist/runtime/composables/useIntegrationsTriggers.js +2 -1
- package/dist/runtime/composables/useIntegrationsWorkflows.d.ts +1 -0
- package/dist/runtime/composables/useIntegrationsWorkflows.js +59 -2
- package/dist/runtime/composables/useMiniMapLayout.d.ts +34 -0
- package/dist/runtime/composables/useMiniMapLayout.js +119 -0
- package/dist/runtime/composables/useNodeConfigResolve.d.ts +43 -0
- package/dist/runtime/composables/useNodeConfigResolve.js +124 -0
- package/dist/runtime/composables/useNodeDrag.d.ts +22 -0
- package/dist/runtime/composables/useNodeDrag.js +10 -0
- package/dist/runtime/composables/usePaletteLayout.d.ts +41 -0
- package/dist/runtime/composables/usePaletteLayout.js +113 -0
- package/dist/runtime/data/expressionEditor.d.ts +22 -0
- package/dist/runtime/data/expressionEditor.js +40 -0
- package/dist/runtime/data/integrationNodeCatalog.js +8 -8
- package/dist/runtime/data/nodeInputData.d.ts +37 -0
- package/dist/runtime/data/nodeInputData.js +62 -0
- package/dist/runtime/data/nodeRunData.d.ts +56 -0
- package/dist/runtime/data/nodeRunData.js +91 -0
- package/dist/runtime/data/payloadForm.d.ts +44 -0
- package/dist/runtime/data/payloadForm.js +126 -0
- package/dist/runtime/data/workflowIconCatalog.d.ts +34 -0
- package/dist/runtime/data/workflowIconCatalog.js +116 -0
- package/dist/runtime/pages/integrations/audit.vue +25 -35
- package/dist/runtime/pages/integrations/credentials.vue +117 -122
- package/dist/runtime/pages/integrations/index.vue +133 -36
- package/dist/runtime/pages/integrations/runs.vue +125 -75
- package/dist/runtime/pages/integrations/schedules.vue +5 -16
- package/dist/runtime/pages/integrations/secrets.vue +1 -3
- package/dist/runtime/pages/integrations/workflows/[id].vue +599 -194
- package/dist/runtime/pages/integrations/workflows/index.vue +10 -8
- package/dist/runtime/types/integrations.d.ts +190 -4
- package/dist/runtime/utils/apiError.d.ts +17 -0
- package/dist/runtime/utils/apiError.js +8 -0
- package/dist/runtime/utils/credentialState.d.ts +28 -0
- package/dist/runtime/utils/credentialState.js +29 -0
- package/dist/runtime/utils/localize.d.ts +3 -1
- package/dist/runtime/utils/localize.js +1 -2
- package/dist/runtime/utils/statusStyle.d.ts +6 -0
- package/dist/runtime/utils/statusStyle.js +15 -0
- package/package.json +20 -3
- package/dist/runtime/components/Integrations/RunDetailDialog.vue +0 -169
- package/dist/runtime/pages/integrations/workflows/new.d.vue.ts +0 -2
- package/dist/runtime/pages/integrations/workflows/new.vue +0 -153
- package/dist/runtime/pages/integrations/workflows/new.vue.d.ts +0 -2
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, markRaw, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
MarkerType,
|
|
5
|
+
PanOnScrollMode,
|
|
6
|
+
VueFlow,
|
|
7
|
+
useVueFlow
|
|
8
|
+
} from "@vue-flow/core";
|
|
9
|
+
import { Background } from "@vue-flow/background";
|
|
10
|
+
import {
|
|
11
|
+
AltArrowLeft as Prev,
|
|
12
|
+
AltArrowRight as Next,
|
|
13
|
+
Play,
|
|
14
|
+
Pause,
|
|
15
|
+
RefreshCircle as Loader2,
|
|
16
|
+
ClockCircle as Clock,
|
|
17
|
+
LinkRound as Webhook
|
|
18
|
+
} from "#solar-icons";
|
|
19
|
+
import { Badge, Button } from "@revenexx/studio";
|
|
20
|
+
import { toast } from "vue-sonner";
|
|
21
|
+
import "@vue-flow/core/dist/style.css";
|
|
22
|
+
import "@vue-flow/core/dist/theme-default.css";
|
|
23
|
+
import WorkflowNodeView from "./WorkflowNodeView.vue";
|
|
24
|
+
import NodeDynamicOutputsResolver from "./NodeDynamicOutputsResolver.vue";
|
|
25
|
+
import WorkflowTriggerView from "./WorkflowTriggerView.vue";
|
|
26
|
+
import StickyNoteView from "./StickyNoteView.vue";
|
|
27
|
+
import GroupFrameView from "./GroupFrameView.vue";
|
|
28
|
+
import WorkflowEdgeView from "./WorkflowEdgeView.vue";
|
|
29
|
+
import { findManifest } from "../../data/integrationNodeCatalog";
|
|
30
|
+
import { runStatusLabel, statusClass } from "../../utils/statusStyle";
|
|
31
|
+
const props = defineProps({
|
|
32
|
+
workflow: { type: Object, required: true },
|
|
33
|
+
initialRunId: { type: [String, null], required: false },
|
|
34
|
+
single: { type: Boolean, required: false }
|
|
35
|
+
});
|
|
36
|
+
const { loadForWorkflow, getSteps } = useIntegrationsRuns();
|
|
37
|
+
const TERMINAL = /* @__PURE__ */ new Set(["completed", "succeeded", "failed", "cancelled", "terminated"]);
|
|
38
|
+
const STEPS_POLL_MS = 4e3;
|
|
39
|
+
const PLAY_INTERVAL_MS = 3e3;
|
|
40
|
+
const runs = ref([]);
|
|
41
|
+
const runsPage = ref(1);
|
|
42
|
+
const runsLastPage = ref(1);
|
|
43
|
+
const runsLoading = ref(false);
|
|
44
|
+
const selectedRunId = ref(null);
|
|
45
|
+
async function loadRuns(reset = false) {
|
|
46
|
+
if (runsLoading.value) return;
|
|
47
|
+
runsLoading.value = true;
|
|
48
|
+
try {
|
|
49
|
+
const res = await loadForWorkflow(props.workflow.id, props.workflow.name, {
|
|
50
|
+
page: reset ? 1 : runsPage.value,
|
|
51
|
+
reconcile: true
|
|
52
|
+
});
|
|
53
|
+
runs.value = reset ? res.runs : [...runs.value, ...res.runs];
|
|
54
|
+
runsPage.value = res.page;
|
|
55
|
+
runsLastPage.value = res.lastPage;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
toast.error(e?.message ?? "Could not load the runs.");
|
|
58
|
+
} finally {
|
|
59
|
+
runsLoading.value = false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function loadMoreRuns() {
|
|
63
|
+
if (runsPage.value >= runsLastPage.value) return;
|
|
64
|
+
runsPage.value += 1;
|
|
65
|
+
void loadRuns();
|
|
66
|
+
}
|
|
67
|
+
const steps = ref([]);
|
|
68
|
+
const stepsSource = ref(null);
|
|
69
|
+
const runStatus = ref(null);
|
|
70
|
+
const stepsLoading = ref(false);
|
|
71
|
+
const stepsError = ref(null);
|
|
72
|
+
let pollTimer = null;
|
|
73
|
+
function stopPoll() {
|
|
74
|
+
if (pollTimer) {
|
|
75
|
+
window.clearTimeout(pollTimer);
|
|
76
|
+
pollTimer = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function loadSteps(runId, isPoll = false) {
|
|
80
|
+
if (!isPoll) {
|
|
81
|
+
stepsLoading.value = true;
|
|
82
|
+
stepsError.value = null;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const res = await getSteps(props.workflow.id, runId, true);
|
|
86
|
+
if (selectedRunId.value !== runId) return;
|
|
87
|
+
steps.value = res.steps ?? [];
|
|
88
|
+
stepsSource.value = res.source ?? null;
|
|
89
|
+
runStatus.value = String(res.run?.status ?? "");
|
|
90
|
+
if (!isPoll) cursor.value = Math.max(0, steps.value.length - 1);
|
|
91
|
+
stopPoll();
|
|
92
|
+
if (runStatus.value && !TERMINAL.has(runStatus.value)) {
|
|
93
|
+
pollTimer = window.setTimeout(() => {
|
|
94
|
+
void loadSteps(runId, true);
|
|
95
|
+
}, STEPS_POLL_MS);
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (selectedRunId.value === runId) {
|
|
99
|
+
stepsError.value = e?.message ?? "Could not load the run steps.";
|
|
100
|
+
}
|
|
101
|
+
} finally {
|
|
102
|
+
if (!isPoll) stepsLoading.value = false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function selectRun(id) {
|
|
106
|
+
if (selectedRunId.value === id) return;
|
|
107
|
+
selectedRunId.value = id;
|
|
108
|
+
playing.value = false;
|
|
109
|
+
steps.value = [];
|
|
110
|
+
stepsError.value = null;
|
|
111
|
+
cursor.value = 0;
|
|
112
|
+
stopPoll();
|
|
113
|
+
void loadSteps(id);
|
|
114
|
+
}
|
|
115
|
+
onBeforeUnmount(stopPoll);
|
|
116
|
+
const cursor = ref(0);
|
|
117
|
+
const playing = ref(false);
|
|
118
|
+
let playTimer = null;
|
|
119
|
+
const safeCursor = computed(() => steps.value.length === 0 ? 0 : Math.min(cursor.value, steps.value.length - 1));
|
|
120
|
+
const activeStep = computed(() => steps.value[safeCursor.value]);
|
|
121
|
+
function seek(i) {
|
|
122
|
+
playing.value = false;
|
|
123
|
+
cursor.value = Math.max(0, Math.min(i, steps.value.length - 1));
|
|
124
|
+
}
|
|
125
|
+
function togglePlay() {
|
|
126
|
+
if (!playing.value && safeCursor.value >= steps.value.length - 1) {
|
|
127
|
+
cursor.value = 0;
|
|
128
|
+
playing.value = true;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
playing.value = !playing.value;
|
|
132
|
+
}
|
|
133
|
+
watch([playing, safeCursor, () => steps.value.length], () => {
|
|
134
|
+
if (playTimer) {
|
|
135
|
+
window.clearTimeout(playTimer);
|
|
136
|
+
playTimer = null;
|
|
137
|
+
}
|
|
138
|
+
if (!playing.value || safeCursor.value >= steps.value.length - 1) return;
|
|
139
|
+
playTimer = window.setTimeout(() => {
|
|
140
|
+
const next = Math.min(safeCursor.value + 1, steps.value.length - 1);
|
|
141
|
+
cursor.value = next;
|
|
142
|
+
if (next >= steps.value.length - 1) playing.value = false;
|
|
143
|
+
}, PLAY_INTERVAL_MS);
|
|
144
|
+
});
|
|
145
|
+
onBeforeUnmount(() => {
|
|
146
|
+
if (playTimer) window.clearTimeout(playTimer);
|
|
147
|
+
});
|
|
148
|
+
const preview = useVueFlow("workflow-run-replay");
|
|
149
|
+
preview.onPaneReady(() => {
|
|
150
|
+
preview.updateNodeInternals(vfNodes.value.map((n) => n.id));
|
|
151
|
+
void preview.fitView({ padding: 0.2, maxZoom: 1 });
|
|
152
|
+
});
|
|
153
|
+
provide("workflow-node-actions", { duplicate: () => {
|
|
154
|
+
}, remove: () => {
|
|
155
|
+
} });
|
|
156
|
+
const dynamicOutputs = ref({});
|
|
157
|
+
const dynamicOutputNodes = computed(() => props.workflow.blob.nodes.filter((n) => {
|
|
158
|
+
const m = findManifest(n.nodeSlug, n.nodeVersion);
|
|
159
|
+
return (m?.outputs ?? []).some((o) => o.resolveOutputs === true);
|
|
160
|
+
}));
|
|
161
|
+
function onResolvedOutputs(nodeId, outputs) {
|
|
162
|
+
dynamicOutputs.value = { ...dynamicOutputs.value, [nodeId]: outputs };
|
|
163
|
+
void nextTick(() => preview.updateNodeInternals([nodeId]));
|
|
164
|
+
}
|
|
165
|
+
const reachedStatus = computed(() => {
|
|
166
|
+
const m = /* @__PURE__ */ new Map();
|
|
167
|
+
for (let i = 0; i <= safeCursor.value && i < steps.value.length; i++) {
|
|
168
|
+
const s = steps.value[i];
|
|
169
|
+
m.set(s.node_id, s.status);
|
|
170
|
+
}
|
|
171
|
+
return m;
|
|
172
|
+
});
|
|
173
|
+
const finalStatus = computed(() => {
|
|
174
|
+
const m = /* @__PURE__ */ new Map();
|
|
175
|
+
for (const s of steps.value) m.set(s.node_id, s.status);
|
|
176
|
+
return m;
|
|
177
|
+
});
|
|
178
|
+
const runNodeIds = computed(() => new Set(steps.value.map((s) => s.node_id)));
|
|
179
|
+
const triggerIds = computed(() => new Set(props.workflow.blob.triggers.map((t) => t.id)));
|
|
180
|
+
function edgeColorByKind(kind) {
|
|
181
|
+
switch (kind) {
|
|
182
|
+
case "success":
|
|
183
|
+
return "#10b981";
|
|
184
|
+
case "error":
|
|
185
|
+
return "#f43f5e";
|
|
186
|
+
case "branch-true":
|
|
187
|
+
return "#10b981";
|
|
188
|
+
case "branch-false":
|
|
189
|
+
return "#f59e0b";
|
|
190
|
+
case "branch":
|
|
191
|
+
return "#8b5cf6";
|
|
192
|
+
default:
|
|
193
|
+
return "#94a3b8";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const GROUP_PAD = 24;
|
|
197
|
+
const GROUP_LABEL_ROOM = 26;
|
|
198
|
+
function previewGroupNodes(blob, nodePos) {
|
|
199
|
+
return (blob.groups ?? []).map((g, i) => {
|
|
200
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
201
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
202
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
203
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
204
|
+
for (const id of g.nodeIds) {
|
|
205
|
+
const pos = nodePos(id);
|
|
206
|
+
minX = Math.min(minX, pos.x);
|
|
207
|
+
minY = Math.min(minY, pos.y);
|
|
208
|
+
maxX = Math.max(maxX, pos.x + 220);
|
|
209
|
+
maxY = Math.max(maxY, pos.y + 64);
|
|
210
|
+
}
|
|
211
|
+
if (!Number.isFinite(minX)) return null;
|
|
212
|
+
return {
|
|
213
|
+
id: g.id,
|
|
214
|
+
type: "group",
|
|
215
|
+
position: { x: Math.round(minX - GROUP_PAD), y: Math.round(minY - GROUP_PAD - GROUP_LABEL_ROOM) },
|
|
216
|
+
zIndex: -50 + i,
|
|
217
|
+
draggable: false,
|
|
218
|
+
selectable: false,
|
|
219
|
+
data: {
|
|
220
|
+
group: { ...g, collapsed: false },
|
|
221
|
+
rect: {
|
|
222
|
+
width: Math.round(maxX - minX + GROUP_PAD * 2),
|
|
223
|
+
height: Math.round(maxY - minY + GROUP_PAD * 2 + GROUP_LABEL_ROOM)
|
|
224
|
+
},
|
|
225
|
+
memberCount: g.nodeIds.length
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}).filter((n) => n !== null);
|
|
229
|
+
}
|
|
230
|
+
function stickyArea(s) {
|
|
231
|
+
return (s.size?.width ?? 240) * (s.size?.height ?? 160);
|
|
232
|
+
}
|
|
233
|
+
function replayNodeClass(id, type) {
|
|
234
|
+
if (type === "sticky") return "replay-dim";
|
|
235
|
+
if (type === "trigger") return steps.value.length ? "replay-node-ok" : "";
|
|
236
|
+
const final = finalStatus.value.get(id);
|
|
237
|
+
if (!final) return "replay-dim";
|
|
238
|
+
const reached = reachedStatus.value.has(id);
|
|
239
|
+
const tone = final === "failed" ? "replay-node-failed" : final === "running" ? "replay-node-running" : "replay-node-ok";
|
|
240
|
+
const active = activeStep.value?.node_id === id ? " replay-node-active" : "";
|
|
241
|
+
return `${tone}${reached ? "" : " replay-unreached"}${active}`;
|
|
242
|
+
}
|
|
243
|
+
const vfNodes = computed(() => {
|
|
244
|
+
const blob = props.workflow.blob;
|
|
245
|
+
const triggerPos = (id, i) => blob.triggerPositions?.[id] ?? { x: -40, y: 60 + i * 140 };
|
|
246
|
+
const posOf = (id) => {
|
|
247
|
+
const wn = blob.nodes.find((n) => n.id === id);
|
|
248
|
+
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
249
|
+
const ti = blob.triggers.findIndex((t) => t.id === id);
|
|
250
|
+
if (ti >= 0) return triggerPos(id, ti);
|
|
251
|
+
return { x: 0, y: 0 };
|
|
252
|
+
};
|
|
253
|
+
const stickies = [...blob.stickies ?? []].sort((a, b) => stickyArea(b) - stickyArea(a));
|
|
254
|
+
return [
|
|
255
|
+
...previewGroupNodes(blob, posOf),
|
|
256
|
+
...stickies.map((s, i) => ({
|
|
257
|
+
id: s.id,
|
|
258
|
+
type: "sticky",
|
|
259
|
+
position: { x: s.position.x, y: s.position.y },
|
|
260
|
+
zIndex: -100 + i,
|
|
261
|
+
draggable: false,
|
|
262
|
+
class: "replay-dim",
|
|
263
|
+
data: { sticky: s, onUpdate: () => {
|
|
264
|
+
}, onRemove: () => {
|
|
265
|
+
} }
|
|
266
|
+
})),
|
|
267
|
+
...blob.triggers.map((t, i) => ({
|
|
268
|
+
id: t.id,
|
|
269
|
+
type: "trigger",
|
|
270
|
+
position: triggerPos(t.id, i),
|
|
271
|
+
draggable: false,
|
|
272
|
+
class: replayNodeClass(t.id, "trigger"),
|
|
273
|
+
data: t
|
|
274
|
+
})),
|
|
275
|
+
...blob.nodes.map((n) => ({
|
|
276
|
+
id: n.id,
|
|
277
|
+
type: "workflow",
|
|
278
|
+
position: n.position ?? { x: 240, y: 80 },
|
|
279
|
+
draggable: false,
|
|
280
|
+
class: replayNodeClass(n.id, "workflow"),
|
|
281
|
+
data: { ...n, __resolvedOutputs: dynamicOutputs.value[n.id] }
|
|
282
|
+
}))
|
|
283
|
+
];
|
|
284
|
+
});
|
|
285
|
+
watch(() => vfNodes.value.map((n) => n.id).join("|"), () => {
|
|
286
|
+
void nextTick(() => preview.updateNodeInternals(vfNodes.value.map((n) => n.id)));
|
|
287
|
+
});
|
|
288
|
+
const vfEdges = computed(() => {
|
|
289
|
+
const blob = props.workflow.blob;
|
|
290
|
+
const nodePos = (id) => {
|
|
291
|
+
const wn = blob.nodes.find((n) => n.id === id);
|
|
292
|
+
if (wn) return { x: wn.position?.x ?? 0, y: wn.position?.y ?? 0 };
|
|
293
|
+
const ti = blob.triggers.findIndex((t) => t.id === id);
|
|
294
|
+
if (ti >= 0) return blob.triggerPositions?.[id] ?? { x: -40, y: 60 + ti * 140 };
|
|
295
|
+
return { x: 0, y: 0 };
|
|
296
|
+
};
|
|
297
|
+
const pickSourceSide = (_sourceId, _targetId) => "bottom";
|
|
298
|
+
const opposite = (s) => s === "top" ? "bottom" : s === "right" ? "left" : s === "bottom" ? "top" : "right";
|
|
299
|
+
const edgeKind = (sourceNodeId, output) => {
|
|
300
|
+
const wn = blob.nodes.find((n) => n.id === sourceNodeId);
|
|
301
|
+
if (!wn) return "default";
|
|
302
|
+
const manifest = findManifest(wn.nodeSlug, wn.nodeVersion);
|
|
303
|
+
if (!manifest) return "default";
|
|
304
|
+
if (output === "true") return "branch-true";
|
|
305
|
+
if (output === "false") return "branch-false";
|
|
306
|
+
if (output === "error") return "error";
|
|
307
|
+
const outs = [...manifest.outputs ?? [], ...dynamicOutputs.value[sourceNodeId] ?? []];
|
|
308
|
+
const portDef = outs.find((o) => o.name === output);
|
|
309
|
+
if (portDef) {
|
|
310
|
+
if (portDef.kind === "error") return "error";
|
|
311
|
+
if (portDef.kind === "branch") return "branch";
|
|
312
|
+
return "success";
|
|
313
|
+
}
|
|
314
|
+
for (const o of manifest.outputs ?? []) {
|
|
315
|
+
if (o.sourceFromConfig) {
|
|
316
|
+
if (o.kind === "error") return "error";
|
|
317
|
+
if (o.kind === "branch") return "branch";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return "default";
|
|
321
|
+
};
|
|
322
|
+
return blob.edges.map((e) => {
|
|
323
|
+
const kind = edgeKind(e.from.nodeId, e.from.output);
|
|
324
|
+
const wn = blob.nodes.find((n) => n.id === e.from.nodeId);
|
|
325
|
+
const manifest = wn ? findManifest(wn.nodeSlug, wn.nodeVersion) : null;
|
|
326
|
+
const isBranchPort = (manifest?.outputs ?? []).some((o) => o.sourceFromConfig) || ["true", "false", "error"].includes(e.from.output);
|
|
327
|
+
const srcSide = pickSourceSide(e.from.nodeId, e.to.nodeId);
|
|
328
|
+
const sourceRan = triggerIds.value.has(e.from.nodeId) ? steps.value.length > 0 : runNodeIds.value.has(e.from.nodeId);
|
|
329
|
+
const traversed = sourceRan && runNodeIds.value.has(e.to.nodeId);
|
|
330
|
+
const targetReached = reachedStatus.value.has(e.to.nodeId);
|
|
331
|
+
return {
|
|
332
|
+
id: e.id,
|
|
333
|
+
source: e.from.nodeId,
|
|
334
|
+
target: e.to.nodeId,
|
|
335
|
+
sourceHandle: `${e.from.output}__${srcSide}`,
|
|
336
|
+
targetHandle: `__in__${opposite(srcSide)}`,
|
|
337
|
+
type: "flow",
|
|
338
|
+
selectable: false,
|
|
339
|
+
animated: traversed && !targetReached,
|
|
340
|
+
data: {
|
|
341
|
+
kind,
|
|
342
|
+
label: isBranchPort ? e.from.output : void 0,
|
|
343
|
+
...traversed ? { replay: { ahead: !targetReached } } : {}
|
|
344
|
+
},
|
|
345
|
+
markerEnd: {
|
|
346
|
+
type: MarkerType.ArrowClosed,
|
|
347
|
+
width: 16,
|
|
348
|
+
height: 16,
|
|
349
|
+
color: traversed ? "#3b82f6" : edgeColorByKind(kind)
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
watch([activeStep, safeCursor], () => {
|
|
355
|
+
const id = activeStep.value?.node_id;
|
|
356
|
+
if (!id) return;
|
|
357
|
+
void nextTick(() => {
|
|
358
|
+
const node = preview.findNode(id);
|
|
359
|
+
if (!node) return;
|
|
360
|
+
const w = node.dimensions?.width ?? 220;
|
|
361
|
+
const h = node.dimensions?.height ?? 60;
|
|
362
|
+
preview.setCenter(node.position.x + w / 2, node.position.y + h / 2, { zoom: 1, duration: 400 });
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
function onNodeClick(nodeId) {
|
|
366
|
+
const idx = steps.value.findIndex((s) => s.node_id === nodeId);
|
|
367
|
+
if (idx < 0) return;
|
|
368
|
+
playing.value = false;
|
|
369
|
+
cursor.value = idx;
|
|
370
|
+
}
|
|
371
|
+
const nodeTypes = {
|
|
372
|
+
workflow: markRaw(WorkflowNodeView),
|
|
373
|
+
trigger: markRaw(WorkflowTriggerView),
|
|
374
|
+
sticky: markRaw(StickyNoteView),
|
|
375
|
+
group: markRaw(GroupFrameView)
|
|
376
|
+
};
|
|
377
|
+
const edgeTypes = {
|
|
378
|
+
flow: markRaw(WorkflowEdgeView)
|
|
379
|
+
};
|
|
380
|
+
preview.onNodeClick((evt) => {
|
|
381
|
+
if (evt.node.type === "workflow") onNodeClick(evt.node.id);
|
|
382
|
+
});
|
|
383
|
+
function formatDateTime(iso) {
|
|
384
|
+
if (!iso) return "\u2014";
|
|
385
|
+
try {
|
|
386
|
+
return new Date(iso).toLocaleString();
|
|
387
|
+
} catch {
|
|
388
|
+
return String(iso);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function formatTime(iso) {
|
|
392
|
+
if (!iso) return "\u2014";
|
|
393
|
+
try {
|
|
394
|
+
return new Date(iso).toLocaleTimeString();
|
|
395
|
+
} catch {
|
|
396
|
+
return String(iso);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function formatDuration(ms) {
|
|
400
|
+
if (ms == null) return "\u2014";
|
|
401
|
+
if (ms < 1e3) return `${ms} ms`;
|
|
402
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)} s`;
|
|
403
|
+
return `${Math.floor(ms / 6e4)}m ${Math.round(ms % 6e4 / 1e3)}s`;
|
|
404
|
+
}
|
|
405
|
+
function pretty(v) {
|
|
406
|
+
try {
|
|
407
|
+
return JSON.stringify(v, null, 2);
|
|
408
|
+
} catch {
|
|
409
|
+
return String(v);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function triggerIcon(type) {
|
|
413
|
+
return type === "schedule" ? Clock : type === "webhook" ? Webhook : Play;
|
|
414
|
+
}
|
|
415
|
+
const activeStepTitle = computed(() => {
|
|
416
|
+
const s = activeStep.value;
|
|
417
|
+
if (!s) return "";
|
|
418
|
+
return `${s.node_slug || s.node_id}${s.attempt > 0 ? ` (#${s.attempt + 1})` : ""}`;
|
|
419
|
+
});
|
|
420
|
+
onMounted(async () => {
|
|
421
|
+
if (props.single && props.initialRunId) {
|
|
422
|
+
selectRun(props.initialRunId);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
await loadRuns(true);
|
|
426
|
+
const initial = props.initialRunId && runs.value.some((r) => r.id === props.initialRunId) ? props.initialRunId : runs.value[0]?.id;
|
|
427
|
+
if (initial) selectRun(initial);
|
|
428
|
+
});
|
|
429
|
+
watch(() => props.initialRunId, (id) => {
|
|
430
|
+
if (!id) return;
|
|
431
|
+
if (props.single) {
|
|
432
|
+
selectRun(id);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (runs.value.some((r) => r.id === id)) selectRun(id);
|
|
436
|
+
});
|
|
437
|
+
</script>
|
|
438
|
+
|
|
439
|
+
<template>
|
|
440
|
+
<div class="flex h-full min-h-0 flex-1">
|
|
441
|
+
<!-- Canvas + transport -->
|
|
442
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
443
|
+
<div class="relative min-h-0 flex-1 bg-[#f7f6fb]">
|
|
444
|
+
<VueFlow
|
|
445
|
+
id="workflow-run-replay"
|
|
446
|
+
:nodes="vfNodes"
|
|
447
|
+
:edges="vfEdges"
|
|
448
|
+
:node-types="nodeTypes"
|
|
449
|
+
:edge-types="edgeTypes"
|
|
450
|
+
:default-edge-options="{ type: 'flow' }"
|
|
451
|
+
:nodes-draggable="false"
|
|
452
|
+
:nodes-connectable="false"
|
|
453
|
+
:edges-updatable="false"
|
|
454
|
+
:pan-on-scroll-mode="PanOnScrollMode.Free"
|
|
455
|
+
:delete-key-code="null"
|
|
456
|
+
:min-zoom="0.25"
|
|
457
|
+
:max-zoom="2"
|
|
458
|
+
>
|
|
459
|
+
<Background pattern-color="#cbd5e1" :gap="20" :size="1.2" />
|
|
460
|
+
</VueFlow>
|
|
461
|
+
|
|
462
|
+
<!-- Headless dynamic-output resolvers — render nothing. -->
|
|
463
|
+
<NodeDynamicOutputsResolver
|
|
464
|
+
v-for="n in dynamicOutputNodes"
|
|
465
|
+
:key="`dyn-out-${n.id}`"
|
|
466
|
+
:node-id="n.id"
|
|
467
|
+
:slug="n.nodeSlug"
|
|
468
|
+
:version="n.nodeVersion"
|
|
469
|
+
:config="n.config ?? {}"
|
|
470
|
+
:static-outputs="findManifest(n.nodeSlug, n.nodeVersion)?.outputs ?? []"
|
|
471
|
+
@resolved="onResolvedOutputs"
|
|
472
|
+
/>
|
|
473
|
+
|
|
474
|
+
<!-- Which run is on the canvas -->
|
|
475
|
+
<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">
|
|
476
|
+
<Play :size="13" class="text-muted-foreground" />
|
|
477
|
+
<span class="font-medium">{{ selectedRunId ? `Run #${selectedRunId}` : "Pick a run" }}</span>
|
|
478
|
+
<Badge v-if="runStatus" variant="outline" class="border text-xs font-medium uppercase" :class="statusClass(runStatus)">
|
|
479
|
+
{{ runStatusLabel(runStatus) }}
|
|
480
|
+
</Badge>
|
|
481
|
+
<Badge v-if="stepsSource" variant="outline" class="border text-xs font-medium uppercase text-muted-foreground">
|
|
482
|
+
{{ stepsSource === "temporal" ? "Live" : "Snapshot" }}
|
|
483
|
+
</Badge>
|
|
484
|
+
<Loader2 v-if="stepsLoading" :size="13" class="animate-spin text-muted-foreground" />
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<div v-if="stepsError" class="absolute inset-x-3 top-14 z-10 rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
488
|
+
{{ stepsError }}
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
492
|
+
<!-- Transport bar -->
|
|
493
|
+
<div class="flex shrink-0 items-center gap-3 border-t border-black/[0.06] bg-white px-4 py-2.5">
|
|
494
|
+
<template v-if="steps.length">
|
|
495
|
+
<div class="flex items-center gap-1">
|
|
496
|
+
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor === 0" @click="seek(safeCursor - 1)">
|
|
497
|
+
<Prev :size="13" />
|
|
498
|
+
Prev
|
|
499
|
+
</Button>
|
|
500
|
+
<Button size="sm" class="h-7" @click="togglePlay">
|
|
501
|
+
<component :is="playing ? Pause : Play" :size="13" class="mr-1" />
|
|
502
|
+
{{ playing ? "Pause" : "Play" }}
|
|
503
|
+
</Button>
|
|
504
|
+
<Button variant="ghost" size="sm" class="h-7" :disabled="safeCursor >= steps.length - 1" @click="seek(safeCursor + 1)">
|
|
505
|
+
Next
|
|
506
|
+
<Next :size="13" />
|
|
507
|
+
</Button>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<input
|
|
511
|
+
type="range"
|
|
512
|
+
:min="0"
|
|
513
|
+
:max="steps.length - 1"
|
|
514
|
+
:value="safeCursor"
|
|
515
|
+
class="h-1.5 flex-1 cursor-pointer accent-primary"
|
|
516
|
+
aria-label="Replay step"
|
|
517
|
+
@input="seek(Number($event.target.value))"
|
|
518
|
+
>
|
|
519
|
+
|
|
520
|
+
<span class="shrink-0 text-sm tabular-nums text-muted-foreground">
|
|
521
|
+
Step {{ safeCursor + 1 }}/{{ steps.length }}
|
|
522
|
+
<span class="font-mono">· {{ activeStepTitle }}</span>
|
|
523
|
+
</span>
|
|
524
|
+
</template>
|
|
525
|
+
<p v-else-if="!stepsLoading" class="text-sm text-muted-foreground">
|
|
526
|
+
{{ selectedRunId ? "No steps recorded for this run." : "Pick a run on the right to replay it." }}
|
|
527
|
+
</p>
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<!-- Debug panel — the ACTIVE step, live with the cursor: status, timings,
|
|
532
|
+
branch, error and payloads, so no lightbox is needed. -->
|
|
533
|
+
<aside class="flex w-[32rem] shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
534
|
+
<div class="flex items-center gap-2 border-b border-black/[0.06] px-3 py-2">
|
|
535
|
+
<div class="min-w-0 flex-1">
|
|
536
|
+
<div class="truncate font-mono text-sm font-medium">{{ activeStepTitle || "Debug" }}</div>
|
|
537
|
+
<div class="text-xs tabular-nums text-muted-foreground">
|
|
538
|
+
<template v-if="steps.length">Step {{ safeCursor + 1 }} / {{ steps.length }}</template>
|
|
539
|
+
<template v-else>No step selected</template>
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
<Badge v-if="activeStep" variant="outline" class="shrink-0 border text-xs font-medium uppercase" :class="statusClass(activeStep.status)">
|
|
543
|
+
{{ runStatusLabel(activeStep.status) }}
|
|
544
|
+
</Badge>
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<div v-if="activeStep" class="min-h-0 flex-1 space-y-4 overflow-y-auto p-3">
|
|
548
|
+
<div class="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
|
|
549
|
+
<div>
|
|
550
|
+
<div class="text-mono text-muted-foreground">Attempt</div>
|
|
551
|
+
<div class="tabular-nums">{{ activeStep.attempt + 1 }}</div>
|
|
552
|
+
</div>
|
|
553
|
+
<div>
|
|
554
|
+
<div class="text-mono text-muted-foreground">Duration</div>
|
|
555
|
+
<div class="tabular-nums">{{ formatDuration(activeStep.duration_ms) }}</div>
|
|
556
|
+
</div>
|
|
557
|
+
<div>
|
|
558
|
+
<div class="text-mono text-muted-foreground">Started</div>
|
|
559
|
+
<div class="tabular-nums">{{ formatTime(activeStep.started_at) }}</div>
|
|
560
|
+
</div>
|
|
561
|
+
<div>
|
|
562
|
+
<div class="text-mono text-muted-foreground">Completed</div>
|
|
563
|
+
<div class="tabular-nums">{{ formatTime(activeStep.completed_at) }}</div>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
|
|
567
|
+
<div v-if="activeStep.branch" class="text-sm">
|
|
568
|
+
<div class="text-mono text-muted-foreground mb-1">Branch</div>
|
|
569
|
+
<code class="rounded bg-black/[0.05] px-1.5 py-0.5 font-mono">{{ activeStep.branch }}</code>
|
|
570
|
+
</div>
|
|
571
|
+
|
|
572
|
+
<div v-if="activeStep.error" class="text-sm">
|
|
573
|
+
<div class="text-mono text-muted-foreground mb-1">Error</div>
|
|
574
|
+
<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>
|
|
575
|
+
</div>
|
|
576
|
+
|
|
577
|
+
<div v-if="activeStep.input !== void 0 && activeStep.input !== null" class="text-sm">
|
|
578
|
+
<div class="text-mono text-muted-foreground mb-1">Input</div>
|
|
579
|
+
<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>
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
<div v-if="activeStep.config !== void 0 && activeStep.config !== null" class="text-sm">
|
|
583
|
+
<div class="text-mono text-muted-foreground mb-1">Config</div>
|
|
584
|
+
<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>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
<div v-if="activeStep.output !== void 0 && activeStep.output !== null" class="text-sm">
|
|
588
|
+
<div class="text-mono text-muted-foreground mb-1">Output</div>
|
|
589
|
+
<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>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
<p v-else class="p-4 text-sm text-muted-foreground">
|
|
593
|
+
The active step's status, timings and payloads land here while the replay walks the run.
|
|
594
|
+
</p>
|
|
595
|
+
</aside>
|
|
596
|
+
|
|
597
|
+
<!-- Run list — hidden in single mode (the run detail replays ONE run). -->
|
|
598
|
+
<aside v-if="!single" class="flex w-80 shrink-0 flex-col border-l border-black/[0.06] bg-white">
|
|
599
|
+
<div class="border-b border-black/[0.06] px-3 py-2">
|
|
600
|
+
<div class="text-sm font-semibold">Runs</div>
|
|
601
|
+
<p class="text-sm text-muted-foreground">
|
|
602
|
+
Click through the executions to replay them on the canvas.
|
|
603
|
+
</p>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
<div class="flex-1 space-y-1 overflow-y-auto p-2">
|
|
607
|
+
<button
|
|
608
|
+
v-for="r in runs"
|
|
609
|
+
:key="r.id"
|
|
610
|
+
type="button"
|
|
611
|
+
class="w-full rounded-lg border p-2.5 text-left transition"
|
|
612
|
+
:class="selectedRunId === r.id ? 'border-primary/40 bg-primary/[0.04]' : 'border-transparent hover:border-black/[0.08] hover:bg-muted/30'"
|
|
613
|
+
@click="selectRun(r.id)"
|
|
614
|
+
>
|
|
615
|
+
<div class="flex items-center justify-between gap-2">
|
|
616
|
+
<span class="flex min-w-0 items-center gap-1.5 text-sm font-medium">
|
|
617
|
+
<component :is="triggerIcon(r.triggerType)" :size="12" class="shrink-0 text-muted-foreground" />
|
|
618
|
+
<span class="truncate">#{{ r.id }}</span>
|
|
619
|
+
</span>
|
|
620
|
+
<Badge variant="outline" class="shrink-0 border text-xs font-medium uppercase" :class="statusClass(r.status)">
|
|
621
|
+
{{ r.status }}
|
|
622
|
+
</Badge>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="mt-0.5 flex items-center justify-between gap-2 text-sm text-muted-foreground">
|
|
625
|
+
<span class="tabular-nums">{{ formatDateTime(r.startedAt) }}</span>
|
|
626
|
+
<span class="tabular-nums">{{ formatDuration(r.durationMs) }}</span>
|
|
627
|
+
</div>
|
|
628
|
+
</button>
|
|
629
|
+
|
|
630
|
+
<p v-if="!runs.length && !runsLoading" class="px-2 py-6 text-center text-sm text-muted-foreground">
|
|
631
|
+
No runs yet — trigger one from the editor's Run button.
|
|
632
|
+
</p>
|
|
633
|
+
|
|
634
|
+
<div v-if="runsLoading" class="flex justify-center py-3">
|
|
635
|
+
<Loader2 :size="15" class="animate-spin text-muted-foreground" />
|
|
636
|
+
</div>
|
|
637
|
+
<Button
|
|
638
|
+
v-else-if="runsPage < runsLastPage"
|
|
639
|
+
variant="ghost"
|
|
640
|
+
size="sm"
|
|
641
|
+
class="w-full text-sm"
|
|
642
|
+
@click="loadMoreRuns"
|
|
643
|
+
>
|
|
644
|
+
Load older runs
|
|
645
|
+
</Button>
|
|
646
|
+
</div>
|
|
647
|
+
</aside>
|
|
648
|
+
|
|
649
|
+
</div>
|
|
650
|
+
</template>
|
|
651
|
+
|
|
652
|
+
<style>
|
|
653
|
+
.vue-flow__node.replay-dim{opacity:.35}.vue-flow__node.replay-unreached{opacity:.45}.vue-flow__node.replay-node-ok{filter:drop-shadow(0 0 5px rgba(16,185,129,.45))}.vue-flow__node.replay-node-failed{filter:drop-shadow(0 0 5px rgba(244,63,94,.5))}.vue-flow__node.replay-node-running{filter:drop-shadow(0 0 5px rgba(14,165,233,.5))}.vue-flow__node.replay-node-active{filter:drop-shadow(0 0 10px rgba(59,130,246,.65))}
|
|
654
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import '@vue-flow/core/dist/style.css';
|
|
2
|
+
import '@vue-flow/core/dist/theme-default.css';
|
|
3
|
+
import type { Workflow } from '../../types/integrations.js';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
workflow: Workflow;
|
|
6
|
+
/** Preselect a run (deep-link from the Runs table). */
|
|
7
|
+
initialRunId?: string | null;
|
|
8
|
+
/** Replay ONLY the initial run: no run list, no browsing (run detail view). */
|
|
9
|
+
single?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
12
|
+
export default _default;
|