@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
|
@@ -1,12 +1,36 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed, onMounted, ref, watch } from "vue";
|
|
3
|
-
import { ArrowLeft,
|
|
4
|
-
import {
|
|
2
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
3
|
+
import { AltArrowLeft as ArrowLeft, AltArrowRight as Chevron, Copy as CopyIcon, Pause as PauseIcon, RefreshCircle as Loader2, Play, Refresh as RetryIcon, Diskette as Save } from "#solar-icons";
|
|
4
|
+
import {
|
|
5
|
+
Badge,
|
|
6
|
+
Button,
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogHeader,
|
|
11
|
+
DialogTitle,
|
|
12
|
+
Select,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectTrigger,
|
|
16
|
+
SelectValue,
|
|
17
|
+
Textarea
|
|
18
|
+
} from "@revenexx/studio";
|
|
5
19
|
import WorkflowEditor from "../../../components/Integrations/WorkflowEditor.vue";
|
|
20
|
+
import WorkflowHistoryPanel from "../../../components/Integrations/WorkflowHistoryPanel.vue";
|
|
21
|
+
import WorkflowRunReplayPanel from "../../../components/Integrations/WorkflowRunReplayPanel.vue";
|
|
22
|
+
import WorkflowSettingsDialog from "../../../components/Integrations/WorkflowSettingsDialog.vue";
|
|
23
|
+
import WorkflowDuplicateDialog from "../../../components/Integrations/WorkflowDuplicateDialog.vue";
|
|
24
|
+
import RunsTable from "../../../components/Integrations/RunsTable.vue";
|
|
25
|
+
import RunDetailPanel from "../../../components/Integrations/RunDetailPanel.vue";
|
|
26
|
+
import { toast } from "vue-sonner";
|
|
27
|
+
import { IntegrationsApiError } from "../../../utils/apiError";
|
|
28
|
+
import { payloadFieldsFromBlob } from "../../../data/payloadForm";
|
|
29
|
+
import WorkflowIconView from "../../../components/Integrations/WorkflowIconView.vue";
|
|
6
30
|
const route = useRoute();
|
|
7
31
|
const router = useRouter();
|
|
8
32
|
const { workflows, get, update, toggleActive } = useIntegrationsWorkflows();
|
|
9
|
-
const {
|
|
33
|
+
const { autoEnable } = useIntegrationsFocus();
|
|
10
34
|
autoEnable();
|
|
11
35
|
const id = computed(() => Number(route.params.id));
|
|
12
36
|
const workflow = computed(() => workflows.value.find((w) => w.id === id.value) ?? null);
|
|
@@ -14,11 +38,9 @@ useHead({ title: () => `${workflow.value?.name ?? "Workflow"} \xB7 Revenexx Cock
|
|
|
14
38
|
const tab = ref("editor");
|
|
15
39
|
const tabs = [
|
|
16
40
|
{ key: "editor", label: "Editor" },
|
|
41
|
+
{ key: "history", label: "History" },
|
|
17
42
|
{ key: "runs", label: "Runs" },
|
|
18
|
-
{ key: "
|
|
19
|
-
{ key: "variables", label: "Variables" },
|
|
20
|
-
{ key: "settings", label: "Settings" },
|
|
21
|
-
{ key: "history", label: "History" }
|
|
43
|
+
{ key: "dead-letters", label: "Missed runs" }
|
|
22
44
|
];
|
|
23
45
|
const dirtyBlob = ref(null);
|
|
24
46
|
const isDirty = computed(() => dirtyBlob.value !== null);
|
|
@@ -32,50 +54,360 @@ const reauthCredentials = computed(() => {
|
|
|
32
54
|
const blob = JSON.stringify(dirtyBlob.value ?? workflow.value?.blob ?? {});
|
|
33
55
|
return needsReauth.value.filter((c) => blob.includes(c.id));
|
|
34
56
|
});
|
|
57
|
+
const buildBlocked = computed(() => {
|
|
58
|
+
const s = workflow.value?.buildStatus;
|
|
59
|
+
return s === "pending" || s === "building" || s === "failed";
|
|
60
|
+
});
|
|
61
|
+
const buildWorking = computed(() => workflow.value?.buildStatus === "building" || workflow.value?.buildStatus === "pending");
|
|
62
|
+
const buildNotice = computed(() => {
|
|
63
|
+
switch (workflow.value?.buildStatus) {
|
|
64
|
+
case "building":
|
|
65
|
+
return { tone: "info", text: "The workflow bundle is still building \u2014 runs are unavailable until it is ready." };
|
|
66
|
+
case "pending":
|
|
67
|
+
return { tone: "info", text: "The workflow bundle build is queued \u2014 runs are unavailable until it is ready." };
|
|
68
|
+
case "failed":
|
|
69
|
+
return {
|
|
70
|
+
tone: "error",
|
|
71
|
+
text: `The workflow bundle failed to build${workflow.value?.buildError ? `: ${workflow.value.buildError}` : "."} Save the workflow again to retry the build.`
|
|
72
|
+
};
|
|
73
|
+
default:
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
35
77
|
watch(id, async () => {
|
|
36
78
|
const w = await get(id.value);
|
|
37
79
|
if (!w) await router.push("/integrations/workflows");
|
|
38
80
|
dirtyBlob.value = null;
|
|
81
|
+
if (tab.value === "runs") void loadRuns(1);
|
|
39
82
|
}, { immediate: true });
|
|
40
83
|
function onChange(blob) {
|
|
41
84
|
dirtyBlob.value = blob;
|
|
42
85
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
86
|
+
const editorResetKey = ref(0);
|
|
87
|
+
function discardChanges() {
|
|
88
|
+
dirtyBlob.value = null;
|
|
89
|
+
editorResetKey.value++;
|
|
90
|
+
}
|
|
91
|
+
const duplicateOpen = ref(false);
|
|
92
|
+
const duplicateTarget = computed(() => workflow.value ? { id: workflow.value.id, name: workflow.value.name } : null);
|
|
93
|
+
async function onDuplicated(copy) {
|
|
94
|
+
toast.success(`Duplicated as \u201C${copy.name}\u201D`);
|
|
95
|
+
await router.push(`/integrations/workflows/${copy.id}`);
|
|
96
|
+
}
|
|
97
|
+
async function onRevisionRestored() {
|
|
98
|
+
if (!workflow.value) return;
|
|
99
|
+
await get(workflow.value.id);
|
|
100
|
+
dirtyBlob.value = null;
|
|
101
|
+
}
|
|
102
|
+
const { trigger: triggerRun, loadForWorkflow, fetchRun, retryRun } = useIntegrationsRuns();
|
|
103
|
+
const running = ref(false);
|
|
104
|
+
const payloadText = ref("{}");
|
|
105
|
+
const payloadError = ref(null);
|
|
106
|
+
const primaryMode = computed(() => workflow.value?.execution_mode === "sync_only" ? "sync" : "async");
|
|
107
|
+
const syncOpen = ref(false);
|
|
108
|
+
const syncLoading = ref(false);
|
|
109
|
+
const syncResponse = ref(null);
|
|
110
|
+
const syncError = ref(null);
|
|
111
|
+
const runModalMode = ref("sync");
|
|
112
|
+
const runModalRunId = ref(null);
|
|
113
|
+
function extractRunId(res) {
|
|
114
|
+
if (res && typeof res === "object" && "id" in res) {
|
|
115
|
+
const id2 = res.id;
|
|
116
|
+
if (id2 !== null && id2 !== void 0 && id2 !== "") return String(id2);
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function viewRunFromModal(id2) {
|
|
121
|
+
syncOpen.value = false;
|
|
122
|
+
void router.push({ query: { ...route.query, run: id2 } });
|
|
123
|
+
}
|
|
124
|
+
function syncErrorMessage(e) {
|
|
125
|
+
if (e instanceof IntegrationsApiError) {
|
|
126
|
+
if (e.status === 504) return "Sync run timed out";
|
|
127
|
+
if (e.status === 422 && e.body?.status === "failed") return "Sync run failed";
|
|
128
|
+
return e.message || "Sync run failed";
|
|
129
|
+
}
|
|
130
|
+
return e?.message || "Sync run failed";
|
|
131
|
+
}
|
|
132
|
+
async function run(mode) {
|
|
45
133
|
if (!workflow.value) return;
|
|
46
|
-
|
|
134
|
+
let payload;
|
|
135
|
+
try {
|
|
136
|
+
payload = JSON.parse(payloadText.value || "{}");
|
|
137
|
+
} catch {
|
|
138
|
+
payloadError.value = "Trigger payload is not valid JSON.";
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
payloadError.value = null;
|
|
142
|
+
runModalMode.value = mode;
|
|
143
|
+
runModalRunId.value = null;
|
|
144
|
+
syncOpen.value = true;
|
|
145
|
+
syncLoading.value = true;
|
|
146
|
+
syncResponse.value = null;
|
|
147
|
+
syncError.value = null;
|
|
148
|
+
if (mode === "sync") {
|
|
149
|
+
try {
|
|
150
|
+
syncResponse.value = await triggerRun(workflow.value.id, payload, "sync");
|
|
151
|
+
runModalRunId.value = extractRunId(syncResponse.value);
|
|
152
|
+
} catch (e) {
|
|
153
|
+
syncError.value = syncErrorMessage(e);
|
|
154
|
+
if (e instanceof IntegrationsApiError) {
|
|
155
|
+
runModalRunId.value = extractRunId(e.body?.run);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
syncLoading.value = false;
|
|
159
|
+
void loadRuns(1);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
running.value = true;
|
|
164
|
+
try {
|
|
165
|
+
const res = await triggerRun(workflow.value.id, payload, "async");
|
|
166
|
+
runModalRunId.value = extractRunId(res);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
syncError.value = e.message || "Could not start the run.";
|
|
169
|
+
} finally {
|
|
170
|
+
running.value = false;
|
|
171
|
+
syncLoading.value = false;
|
|
172
|
+
void loadRuns(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const payloadDriven = computed(() => payloadFieldsFromBlob(dirtyBlob.value ?? workflow.value?.blob ?? { nodes: [] }).length > 0);
|
|
176
|
+
async function runFromEditor() {
|
|
177
|
+
if (!workflow.value) return;
|
|
178
|
+
if (payloadDriven.value) {
|
|
179
|
+
runPayloadOpen.value = true;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
await run(primaryMode.value);
|
|
183
|
+
}
|
|
184
|
+
const runPayloadOpen = ref(false);
|
|
185
|
+
const definedEvents = useDefinedEvents();
|
|
186
|
+
const eventLog = useIntegrationsEvents();
|
|
187
|
+
const prefillBusy = ref(null);
|
|
188
|
+
const eventTriggerTopic = computed(() => {
|
|
189
|
+
const trigger = (workflow.value?.blob.triggers ?? []).find((t) => t.type === "event");
|
|
190
|
+
const topic = trigger?.config?.event_type;
|
|
191
|
+
return typeof topic === "string" && topic.trim() !== "" ? topic.trim() : null;
|
|
192
|
+
});
|
|
193
|
+
watch(runPayloadOpen, (open) => {
|
|
194
|
+
if (open && eventTriggerTopic.value) void definedEvents.load();
|
|
195
|
+
});
|
|
196
|
+
const catalogSample = computed(() => {
|
|
197
|
+
if (!eventTriggerTopic.value) return null;
|
|
198
|
+
return definedEvents.emits.value.find((e) => e.topic === eventTriggerTopic.value)?.sample ?? null;
|
|
199
|
+
});
|
|
200
|
+
function envelope(topic, data, extra = {}) {
|
|
201
|
+
return JSON.stringify({
|
|
202
|
+
topic,
|
|
203
|
+
id: extra.id ?? "evt_sample00000000000000000000",
|
|
204
|
+
tenant_id: extra.tenant_id ?? "",
|
|
205
|
+
data,
|
|
206
|
+
metadata: extra.metadata ?? {},
|
|
207
|
+
time: extra.time ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
208
|
+
}, null, 2);
|
|
209
|
+
}
|
|
210
|
+
function insertSampleEvent() {
|
|
211
|
+
const topic = eventTriggerTopic.value;
|
|
212
|
+
if (!topic || !catalogSample.value) return;
|
|
213
|
+
payloadText.value = envelope(topic, catalogSample.value);
|
|
214
|
+
payloadError.value = null;
|
|
215
|
+
}
|
|
216
|
+
async function insertLatestEvent() {
|
|
217
|
+
const topic = eventTriggerTopic.value;
|
|
218
|
+
if (!topic || prefillBusy.value) return;
|
|
219
|
+
prefillBusy.value = "latest";
|
|
220
|
+
try {
|
|
221
|
+
await eventLog.loadEvents();
|
|
222
|
+
const match = eventLog.events.value.filter((e) => e.topic === topic).sort((a, b) => (b.time ?? "").localeCompare(a.time ?? ""))[0];
|
|
223
|
+
if (!match) {
|
|
224
|
+
toast.info(`No received ${topic} event in the log yet.`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
payloadText.value = envelope(topic, match.data, {
|
|
228
|
+
id: match.id,
|
|
229
|
+
tenant_id: match.tenantId ?? "",
|
|
230
|
+
metadata: match.metadata,
|
|
231
|
+
time: match.time
|
|
232
|
+
});
|
|
233
|
+
payloadError.value = null;
|
|
234
|
+
} finally {
|
|
235
|
+
prefillBusy.value = null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function runFromPayloadDialog(mode) {
|
|
239
|
+
await run(mode);
|
|
240
|
+
if (payloadError.value) return;
|
|
241
|
+
runPayloadOpen.value = false;
|
|
242
|
+
}
|
|
243
|
+
const RUNS_PER_PAGE = 25;
|
|
244
|
+
const wfRuns = ref([]);
|
|
245
|
+
const runsLoading = ref(false);
|
|
246
|
+
const runsError = ref(null);
|
|
247
|
+
const runsPage = ref(1);
|
|
248
|
+
const runsTotal = ref(0);
|
|
249
|
+
const runsLastPage = ref(1);
|
|
250
|
+
let runsPoll = null;
|
|
251
|
+
function stopRunsPoll() {
|
|
252
|
+
if (runsPoll) {
|
|
253
|
+
clearInterval(runsPoll);
|
|
254
|
+
runsPoll = null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function startRunsPoll() {
|
|
258
|
+
stopRunsPoll();
|
|
259
|
+
runsPoll = setInterval(() => {
|
|
260
|
+
if (runsPage.value === 1) void loadRuns(1, { silent: true });
|
|
261
|
+
}, 3e3);
|
|
262
|
+
}
|
|
263
|
+
onUnmounted(stopRunsPoll);
|
|
264
|
+
async function loadRuns(page = runsPage.value, opts = {}) {
|
|
265
|
+
if (!workflow.value) return;
|
|
266
|
+
if (!opts.silent) runsLoading.value = true;
|
|
267
|
+
runsError.value = null;
|
|
268
|
+
try {
|
|
269
|
+
const res = await loadForWorkflow(workflow.value.id, workflow.value.name, {
|
|
270
|
+
reconcile: page === 1,
|
|
271
|
+
page,
|
|
272
|
+
perPage: RUNS_PER_PAGE
|
|
273
|
+
});
|
|
274
|
+
wfRuns.value = res.runs;
|
|
275
|
+
runsTotal.value = res.total;
|
|
276
|
+
runsLastPage.value = res.lastPage;
|
|
277
|
+
runsPage.value = res.page;
|
|
278
|
+
} catch (e) {
|
|
279
|
+
runsError.value = e.message || "Could not load runs.";
|
|
280
|
+
} finally {
|
|
281
|
+
if (!opts.silent) runsLoading.value = false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function goRunsPage(p) {
|
|
285
|
+
const next = Math.min(Math.max(1, p), runsLastPage.value);
|
|
286
|
+
if (next !== runsPage.value) void loadRuns(next);
|
|
287
|
+
}
|
|
288
|
+
watch(tab, (t) => {
|
|
289
|
+
if (t === "runs") {
|
|
290
|
+
void loadRuns(1);
|
|
291
|
+
startRunsPoll();
|
|
292
|
+
} else stopRunsPoll();
|
|
293
|
+
});
|
|
294
|
+
const settingsOpen = ref(false);
|
|
295
|
+
const openRunId = computed(() => route.query.run != null ? String(route.query.run) : null);
|
|
296
|
+
const runView = ref("details");
|
|
297
|
+
const runViews = [
|
|
298
|
+
{ key: "details", label: "Details" },
|
|
299
|
+
{ key: "timeline", label: "Timeline" },
|
|
300
|
+
{ key: "replay", label: "Replay" }
|
|
301
|
+
];
|
|
302
|
+
const openRun = ref(null);
|
|
303
|
+
const openRunError = ref(null);
|
|
304
|
+
async function loadOpenRun() {
|
|
305
|
+
if (!openRunId.value || !workflow.value) return;
|
|
306
|
+
openRunError.value = null;
|
|
307
|
+
try {
|
|
308
|
+
openRun.value = await fetchRun(workflow.value.id, openRunId.value, workflow.value.name);
|
|
309
|
+
} catch (e) {
|
|
310
|
+
openRunError.value = e?.message ?? "Could not load the run.";
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
watch(openRunId, () => {
|
|
314
|
+
runView.value = route.query.view === "replay" ? "replay" : "details";
|
|
315
|
+
}, { immediate: true });
|
|
316
|
+
watch([openRunId, () => workflow.value?.id], () => {
|
|
317
|
+
openRun.value = null;
|
|
318
|
+
openRunError.value = null;
|
|
319
|
+
if (openRunId.value && workflow.value) void loadOpenRun();
|
|
320
|
+
}, { immediate: true });
|
|
321
|
+
function closeRun(target) {
|
|
322
|
+
tab.value = target;
|
|
323
|
+
void router.replace({ query: { ...route.query, run: void 0, view: void 0 } });
|
|
324
|
+
}
|
|
325
|
+
const canRetryOpenRun = computed(() => !!openRun.value && ["succeeded", "failed", "cancelled"].includes(openRun.value.status));
|
|
326
|
+
const retryBusy = ref(false);
|
|
327
|
+
async function retryOpenRun() {
|
|
328
|
+
if (!openRun.value || retryBusy.value) return;
|
|
329
|
+
retryBusy.value = true;
|
|
330
|
+
try {
|
|
331
|
+
const res = await retryRun(openRun.value.workflowId, openRun.value.id);
|
|
332
|
+
const newId = res && typeof res === "object" && "id" in res ? String(res.id) : null;
|
|
333
|
+
toast.success(newId && newId !== openRun.value.id ? `Started run #${newId}` : "Retry requested");
|
|
334
|
+
void loadRuns(1, { silent: true });
|
|
335
|
+
} catch (e) {
|
|
336
|
+
toast.error(e?.message ?? "Could not retry the run.");
|
|
337
|
+
} finally {
|
|
338
|
+
retryBusy.value = false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const EXECUTION_MODES = [
|
|
342
|
+
{ value: "async_only", label: "Async only", hint: "Runs are always queued; the trigger returns immediately." },
|
|
343
|
+
{ value: "sync_only", label: "Sync only", hint: "Runs block until they finish and return the result inline." },
|
|
344
|
+
{ value: "caller_decides", label: "Caller decides", hint: "The caller chooses sync or async per run." }
|
|
345
|
+
];
|
|
346
|
+
const executionModeDraft = ref("async_only");
|
|
347
|
+
watch(workflow, (w) => {
|
|
348
|
+
if (w) executionModeDraft.value = w.execution_mode ?? "async_only";
|
|
349
|
+
}, { immediate: true });
|
|
350
|
+
const savingMode = ref(false);
|
|
351
|
+
function modeLabel(m) {
|
|
352
|
+
return m === "sync_only" ? "Sync" : m === "caller_decides" ? "Caller decides" : "Async";
|
|
353
|
+
}
|
|
354
|
+
async function saveExecutionMode(mode) {
|
|
355
|
+
if (!workflow.value || mode === workflow.value.execution_mode) return;
|
|
356
|
+
const prev = workflow.value.execution_mode ?? "async_only";
|
|
357
|
+
executionModeDraft.value = mode;
|
|
358
|
+
savingMode.value = true;
|
|
47
359
|
saveError.value = null;
|
|
48
|
-
trg.active = !trg.active;
|
|
49
360
|
try {
|
|
50
361
|
await update(workflow.value.id, {
|
|
51
362
|
name: workflow.value.name,
|
|
52
363
|
description: workflow.value.description,
|
|
53
364
|
blob: workflow.value.blob,
|
|
54
365
|
active: workflow.value.active,
|
|
55
|
-
executionMode:
|
|
366
|
+
executionMode: mode
|
|
56
367
|
});
|
|
368
|
+
await get(workflow.value.id);
|
|
57
369
|
} catch (e) {
|
|
58
|
-
|
|
59
|
-
saveError.value = e.message || "Could not update the
|
|
370
|
+
executionModeDraft.value = prev;
|
|
371
|
+
saveError.value = e.message || "Could not update the execution mode.";
|
|
60
372
|
} finally {
|
|
61
|
-
|
|
373
|
+
savingMode.value = false;
|
|
62
374
|
}
|
|
63
375
|
}
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
376
|
+
const savingMeta = ref(false);
|
|
377
|
+
const tagSuggestions = computed(() => {
|
|
378
|
+
const all = /* @__PURE__ */ new Set();
|
|
379
|
+
for (const wf of workflows.value) for (const t of wf.blob.tags ?? []) all.add(t);
|
|
380
|
+
return [...all].sort();
|
|
381
|
+
});
|
|
382
|
+
async function saveWorkflowMeta(meta) {
|
|
69
383
|
if (!workflow.value) return;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
runResult.value = null;
|
|
384
|
+
savingMeta.value = true;
|
|
385
|
+
saveError.value = null;
|
|
73
386
|
try {
|
|
74
|
-
|
|
387
|
+
const blob = JSON.parse(JSON.stringify(dirtyBlob.value ?? workflow.value.blob));
|
|
388
|
+
blob.name = meta.name;
|
|
389
|
+
if (meta.description) blob.description = meta.description;
|
|
390
|
+
else delete blob.description;
|
|
391
|
+
if (meta.tags.length) blob.tags = [...meta.tags];
|
|
392
|
+
else delete blob.tags;
|
|
393
|
+
if (meta.icon) blob.icon = meta.icon;
|
|
394
|
+
else delete blob.icon;
|
|
395
|
+
await update(workflow.value.id, {
|
|
396
|
+
name: meta.name,
|
|
397
|
+
description: meta.description || null,
|
|
398
|
+
blob,
|
|
399
|
+
active: workflow.value.active,
|
|
400
|
+
executionMode: workflow.value.execution_mode
|
|
401
|
+
});
|
|
402
|
+
await get(workflow.value.id);
|
|
403
|
+
dirtyBlob.value = null;
|
|
404
|
+
toast.success("Appearance saved");
|
|
75
405
|
} catch (e) {
|
|
76
|
-
|
|
406
|
+
const msg = e.message || "Could not save the appearance.";
|
|
407
|
+
saveError.value = msg;
|
|
408
|
+
toast.error(msg);
|
|
77
409
|
} finally {
|
|
78
|
-
|
|
410
|
+
savingMeta.value = false;
|
|
79
411
|
}
|
|
80
412
|
}
|
|
81
413
|
async function save() {
|
|
@@ -113,40 +445,85 @@ function formatDateTime(iso) {
|
|
|
113
445
|
remaining height below the page header. -->
|
|
114
446
|
<div class="flex h-full flex-col">
|
|
115
447
|
<template v-if="workflow">
|
|
116
|
-
<PageHeader
|
|
117
|
-
|
|
118
|
-
|
|
448
|
+
<PageHeader :title="workflow.name">
|
|
449
|
+
<template #title>
|
|
450
|
+
<span class="inline-flex min-w-0 items-center gap-1.5">
|
|
451
|
+
<NuxtLink to="/integrations/workflows" class="font-normal text-muted-foreground/80 transition-colors hover:text-primary">Workflows</NuxtLink>
|
|
452
|
+
<Chevron :size="15" weight="Outline" class="text-muted-foreground/50" />
|
|
453
|
+
<!-- Run open: the breadcrumb extends — name and Runs become crumbs
|
|
454
|
+
back into the workflow surface. -->
|
|
455
|
+
<template v-if="openRunId">
|
|
456
|
+
<button type="button" class="truncate font-normal text-muted-foreground/80 transition-colors hover:text-primary" @click="closeRun('editor')">{{ workflow.name }}</button>
|
|
457
|
+
<Chevron :size="15" weight="Outline" class="text-muted-foreground/50" />
|
|
458
|
+
<button type="button" class="font-normal text-muted-foreground/80 transition-colors hover:text-primary" @click="closeRun('runs')">Runs</button>
|
|
459
|
+
<Chevron :size="15" weight="Outline" class="text-muted-foreground/50" />
|
|
460
|
+
<!-- The Temporal run id IS the run's identity; the internal
|
|
461
|
+
number only fills in until the run is handed off. -->
|
|
462
|
+
<span class="truncate font-mono">{{ openRun?.temporalRunId ?? `Run #${openRunId}` }}</span>
|
|
463
|
+
</template>
|
|
464
|
+
<template v-else>
|
|
465
|
+
<span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-slate-100 text-slate-500">
|
|
466
|
+
<WorkflowIconView :icon="workflow.blob.icon" :size="16" />
|
|
467
|
+
</span>
|
|
468
|
+
<span class="truncate">{{ workflow.name }}</span>
|
|
469
|
+
<Badge variant="outline" class="ml-1.5 border text-xs font-medium uppercase text-muted-foreground" :title="`Execution mode: ${modeLabel(workflow.execution_mode)}`">
|
|
470
|
+
{{ modeLabel(workflow.execution_mode) }}
|
|
471
|
+
</Badge>
|
|
472
|
+
</template>
|
|
473
|
+
</span>
|
|
474
|
+
</template>
|
|
119
475
|
<template #actions>
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
variant="
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<PanelLeftOpen :size="14" :class="focused ? '' : 'rotate-180'" />
|
|
129
|
-
</Button>
|
|
130
|
-
<Button variant="outline" class="h-7 text-sm" @click="toggleActive(workflow.id)">
|
|
131
|
-
<component :is="workflow.active ? Pause : Play" :size="12" />
|
|
132
|
-
{{ workflow.active ? "Pause" : "Activate" }}
|
|
133
|
-
</Button>
|
|
134
|
-
<Button class="h-7 text-sm" :disabled="!isDirty || saving" @click="save">
|
|
135
|
-
<Loader2 v-if="saving" :size="12" class="animate-spin" />
|
|
136
|
-
<Save v-else :size="12" />
|
|
137
|
-
{{ saving ? "Saving\u2026" : "Save" }}
|
|
138
|
-
</Button>
|
|
139
|
-
<NuxtLink to="/integrations/workflows">
|
|
140
|
-
<Button variant="ghost" class="h-7 text-sm">
|
|
476
|
+
<!-- Run open: run-scoped actions replace the workflow Save. -->
|
|
477
|
+
<template v-if="openRunId">
|
|
478
|
+
<Button v-if="canRetryOpenRun" variant="outline" class="h-7 text-sm" :disabled="retryBusy" @click="retryOpenRun">
|
|
479
|
+
<Loader2 v-if="retryBusy" :size="12" class="animate-spin" />
|
|
480
|
+
<RetryIcon v-else :size="12" />
|
|
481
|
+
Retry
|
|
482
|
+
</Button>
|
|
483
|
+
<Button class="h-7 text-sm" @click="closeRun('editor')">
|
|
141
484
|
<ArrowLeft :size="12" />
|
|
142
|
-
|
|
485
|
+
Back to Workflow
|
|
143
486
|
</Button>
|
|
144
|
-
</
|
|
487
|
+
</template>
|
|
488
|
+
<template v-else>
|
|
489
|
+
<span
|
|
490
|
+
v-if="buildWorking"
|
|
491
|
+
class="inline-flex h-7 items-center gap-1.5 rounded-md border border-sky-500/25 bg-sky-500/10 px-2.5 text-sm text-sky-700"
|
|
492
|
+
:title="buildNotice?.text"
|
|
493
|
+
>
|
|
494
|
+
<Loader2 :size="12" class="animate-spin" />
|
|
495
|
+
{{ workflow.buildStatus === "building" ? "Building bundle\u2026" : "Build queued\u2026" }}
|
|
496
|
+
</span>
|
|
497
|
+
<span v-if="isDirty" class="text-sm text-amber-600">Unsaved changes</span>
|
|
498
|
+
<Button variant="outline" class="h-7 text-sm" :disabled="saving" title="Duplicate this workflow" @click="duplicateOpen = true">
|
|
499
|
+
<CopyIcon :size="12" />
|
|
500
|
+
Duplicate
|
|
501
|
+
</Button>
|
|
502
|
+
<Button variant="outline" class="h-7 text-sm" :disabled="!isDirty || saving" title="Discard unsaved changes" @click="discardChanges">
|
|
503
|
+
Cancel
|
|
504
|
+
</Button>
|
|
505
|
+
<Button class="h-7 text-sm" :disabled="!isDirty || saving" @click="save">
|
|
506
|
+
<Loader2 v-if="saving" :size="12" class="animate-spin" />
|
|
507
|
+
<Save v-else :size="12" />
|
|
508
|
+
{{ saving ? "Saving\u2026" : "Save" }}
|
|
509
|
+
</Button>
|
|
510
|
+
<Button
|
|
511
|
+
:variant="workflow.active ? 'outline' : 'default'"
|
|
512
|
+
class="h-7 text-sm"
|
|
513
|
+
:class="workflow.active ? 'text-amber-700 hover:text-amber-800' : 'bg-emerald-600 text-white hover:bg-emerald-700'"
|
|
514
|
+
:title="workflow.active ? 'Pause this workflow' : 'Activate this workflow'"
|
|
515
|
+
@click="toggleActive(workflow.id)"
|
|
516
|
+
>
|
|
517
|
+
<component :is="workflow.active ? PauseIcon : Play" :size="12" />
|
|
518
|
+
{{ workflow.active ? "Pause" : "Activate" }}
|
|
519
|
+
</Button>
|
|
520
|
+
</template>
|
|
145
521
|
</template>
|
|
146
522
|
|
|
147
523
|
</PageHeader>
|
|
148
524
|
|
|
149
|
-
<TabBar v-model="tab" :tabs="tabs" class="shrink-0 px-4" />
|
|
525
|
+
<TabBar v-if="!openRunId" v-model="tab" :tabs="tabs" class="shrink-0 px-4" />
|
|
526
|
+
<TabBar v-else v-model="runView" :tabs="runViews" class="shrink-0 px-4" />
|
|
150
527
|
|
|
151
528
|
<div v-if="saveError" class="border-b border-rose-500/20 bg-rose-500/5 px-4 py-2 text-sm text-rose-700">
|
|
152
529
|
{{ saveError }}
|
|
@@ -161,166 +538,194 @@ function formatDateTime(iso) {
|
|
|
161
538
|
</NuxtLink>.
|
|
162
539
|
</div>
|
|
163
540
|
|
|
164
|
-
<!--
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<div class="rounded-lg border border-black/[0.06] bg-white p-4 flex items-center justify-between gap-3">
|
|
173
|
-
<div>
|
|
174
|
-
<h3 class="text-sm font-semibold">
|
|
175
|
-
Run this workflow
|
|
176
|
-
</h3>
|
|
177
|
-
<p class="mt-0.5 text-sm text-muted-foreground">
|
|
178
|
-
Triggers a manual run via the API. See all executions on the Runs page.
|
|
179
|
-
</p>
|
|
180
|
-
</div>
|
|
181
|
-
<Button :disabled="running" class="h-8 text-sm" @click="runNow">
|
|
182
|
-
<Loader2 v-if="running" :size="14" class="animate-spin" />
|
|
183
|
-
<Play v-else :size="14" />
|
|
184
|
-
{{ running ? "Starting\u2026" : "Run now" }}
|
|
185
|
-
</Button>
|
|
186
|
-
</div>
|
|
187
|
-
<div v-if="runError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
188
|
-
{{ runError }}
|
|
189
|
-
</div>
|
|
190
|
-
<pre v-if="runResult" class="overflow-auto rounded-lg border border-black/[0.06] bg-slate-50 p-3 text-xs">{{ runResult }}</pre>
|
|
541
|
+
<!-- Only the FAILED build state banners (its message matters); the
|
|
542
|
+
working states live as the header chip — and inside a run the
|
|
543
|
+
workflow's build state is noise, so nothing shows at all. -->
|
|
544
|
+
<div
|
|
545
|
+
v-if="!openRunId && buildNotice && buildNotice.tone === 'error'"
|
|
546
|
+
class="border-b border-rose-500/20 bg-rose-500/5 px-4 py-2 text-sm text-rose-700"
|
|
547
|
+
>
|
|
548
|
+
{{ buildNotice.text }}
|
|
191
549
|
</div>
|
|
192
550
|
|
|
193
|
-
<!--
|
|
194
|
-
<
|
|
195
|
-
<div
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
<component
|
|
201
|
-
:is="trg.type === 'schedule' ? Clock : trg.type === 'webhook' ? Webhook : Play"
|
|
202
|
-
:size="18"
|
|
203
|
-
class="text-emerald-600"
|
|
204
|
-
/>
|
|
205
|
-
<div class="flex-1 min-w-0">
|
|
206
|
-
<div class="text-sm font-medium">
|
|
207
|
-
{{ trg.type === "schedule" ? "Schedule" : trg.type === "webhook" ? "Webhook" : "Manual" }}
|
|
551
|
+
<!-- Nested run detail — overview/timeline panel or Replay canvas. -->
|
|
552
|
+
<template v-if="openRunId">
|
|
553
|
+
<div v-if="runView !== 'replay'" class="flex-1 overflow-y-auto p-4">
|
|
554
|
+
<div class="w-full">
|
|
555
|
+
<RunDetailPanel v-if="openRun" :run="openRun" :view="runView" @changed="loadOpenRun" />
|
|
556
|
+
<div v-else-if="openRunError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
557
|
+
{{ openRunError }}
|
|
208
558
|
</div>
|
|
209
|
-
<div class="text-sm text-muted-foreground
|
|
210
|
-
<
|
|
211
|
-
cron: {{ trg.config.cron }} ({{ trg.config.timezone ?? "UTC" }})
|
|
212
|
-
</template>
|
|
213
|
-
<template v-else-if="trg.type === 'webhook'">
|
|
214
|
-
{{ trg.config.method }} {{ trg.config.path }}
|
|
215
|
-
</template>
|
|
216
|
-
<template v-else>on demand</template>
|
|
559
|
+
<div v-else class="flex items-center gap-2 p-12 text-sm text-muted-foreground">
|
|
560
|
+
<Loader2 :size="16" class="animate-spin" /> Loading run…
|
|
217
561
|
</div>
|
|
218
562
|
</div>
|
|
219
|
-
<div class="flex shrink-0 items-center gap-2">
|
|
220
|
-
<span class="text-xs" :class="trg.active ? 'text-emerald-600' : 'text-muted-foreground'">
|
|
221
|
-
{{ trg.active ? "Active" : "Inactive" }}
|
|
222
|
-
</span>
|
|
223
|
-
<button
|
|
224
|
-
type="button"
|
|
225
|
-
:disabled="togglingTrigger === trg.id"
|
|
226
|
-
class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors disabled:opacity-50"
|
|
227
|
-
:class="trg.active ? 'bg-emerald-500' : 'bg-slate-300'"
|
|
228
|
-
:aria-pressed="trg.active"
|
|
229
|
-
:title="trg.active ? 'Deactivate trigger' : 'Activate trigger'"
|
|
230
|
-
@click="toggleTrigger(trg)"
|
|
231
|
-
>
|
|
232
|
-
<span
|
|
233
|
-
class="inline-block size-4 transform rounded-full bg-white shadow transition-transform"
|
|
234
|
-
:class="trg.active ? 'translate-x-[18px]' : 'translate-x-0.5'"
|
|
235
|
-
/>
|
|
236
|
-
</button>
|
|
237
|
-
</div>
|
|
238
563
|
</div>
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
</
|
|
564
|
+
<div v-else class="min-h-0 flex-1">
|
|
565
|
+
<WorkflowRunReplayPanel :workflow="workflow" :initial-run-id="openRunId" single />
|
|
566
|
+
</div>
|
|
567
|
+
</template>
|
|
568
|
+
|
|
569
|
+
<!-- Editor tab — fills the remaining height inside the panel via
|
|
570
|
+
the flex wrapper in the studio layout. -->
|
|
571
|
+
<div v-else-if="tab === 'editor'" class="flex-1 min-h-0">
|
|
572
|
+
<WorkflowEditor
|
|
573
|
+
:key="editorResetKey"
|
|
574
|
+
:workflow="workflow"
|
|
575
|
+
:run-busy="running || syncLoading"
|
|
576
|
+
@change="onChange"
|
|
577
|
+
@run="runFromEditor"
|
|
578
|
+
@toggle-active="toggleActive(workflow.id)"
|
|
579
|
+
@open-settings="settingsOpen = true"
|
|
580
|
+
/>
|
|
242
581
|
</div>
|
|
243
582
|
|
|
244
|
-
<!--
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
583
|
+
<!-- Runs — this workflow's full execution history. Manual runs start
|
|
584
|
+
from the editor's Run split-button (payload via its lightbox). -->
|
|
585
|
+
<div v-else-if="tab === 'runs'" class="p-4 space-y-4 overflow-y-auto">
|
|
586
|
+
<div v-if="runsError" class="rounded-md border border-rose-500/20 bg-rose-500/5 px-3 py-2 text-sm text-rose-700">
|
|
587
|
+
{{ runsError }}
|
|
248
588
|
</div>
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
<
|
|
264
|
-
|
|
265
|
-
</
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
</
|
|
589
|
+
|
|
590
|
+
<RunsTable
|
|
591
|
+
:runs="wfRuns"
|
|
592
|
+
:loading="runsLoading"
|
|
593
|
+
:pagination="false"
|
|
594
|
+
:show-workflow="false"
|
|
595
|
+
empty-text="No runs yet — hit Run in the editor."
|
|
596
|
+
@changed="() => loadRuns()"
|
|
597
|
+
/>
|
|
598
|
+
|
|
599
|
+
<!-- Server pagination — the endpoint pages, so the whole history is reachable. -->
|
|
600
|
+
<div v-if="runsLastPage > 1 || runsTotal > wfRuns.length" class="flex items-center justify-between text-sm text-muted-foreground">
|
|
601
|
+
<span class="tabular-nums">Page {{ runsPage }} / {{ runsLastPage }} · {{ runsTotal }} run{{ runsTotal === 1 ? "" : "s" }}</span>
|
|
602
|
+
<div class="flex gap-2">
|
|
603
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsPage <= 1 || runsLoading" @click="goRunsPage(runsPage - 1)">
|
|
604
|
+
← Newer
|
|
605
|
+
</Button>
|
|
606
|
+
<Button variant="outline" size="sm" class="h-7 text-sm" :disabled="runsPage >= runsLastPage || runsLoading" @click="goRunsPage(runsPage + 1)">
|
|
607
|
+
Older →
|
|
608
|
+
</Button>
|
|
269
609
|
</div>
|
|
270
610
|
</div>
|
|
271
611
|
</div>
|
|
272
612
|
|
|
273
|
-
<!--
|
|
274
|
-
<div v-else-if="tab === '
|
|
275
|
-
<
|
|
276
|
-
<div class="text-mono text-muted-foreground">
|
|
277
|
-
Metadata
|
|
278
|
-
</div>
|
|
279
|
-
<div class="grid grid-cols-[120px_1fr] gap-y-2 gap-x-3 text-sm">
|
|
280
|
-
<div class="text-muted-foreground">
|
|
281
|
-
ID
|
|
282
|
-
</div><div class="font-mono">
|
|
283
|
-
{{ workflow.id }}
|
|
284
|
-
</div>
|
|
285
|
-
<div class="text-muted-foreground">
|
|
286
|
-
Definition
|
|
287
|
-
</div><div class="font-mono">
|
|
288
|
-
{{ workflow.blob_definition_version }}
|
|
289
|
-
</div>
|
|
290
|
-
<div class="text-muted-foreground">
|
|
291
|
-
Revision
|
|
292
|
-
</div><div class="font-mono">
|
|
293
|
-
r{{ workflow.revision }}
|
|
294
|
-
</div>
|
|
295
|
-
<div class="text-muted-foreground">
|
|
296
|
-
Created
|
|
297
|
-
</div><div>{{ formatDateTime(workflow.created_at) }}</div>
|
|
298
|
-
<div class="text-muted-foreground">
|
|
299
|
-
Updated
|
|
300
|
-
</div><div>{{ formatDateTime(workflow.updated_at) }}</div>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
613
|
+
<!-- Dead letters — triggers that never produced a run -->
|
|
614
|
+
<div v-else-if="tab === 'dead-letters'" class="p-6 overflow-y-auto">
|
|
615
|
+
<IntegrationsDeadLettersPanel :workflow-id="workflow.id" />
|
|
303
616
|
</div>
|
|
304
617
|
|
|
305
|
-
<!-- History -->
|
|
306
|
-
<div v-else-if="tab === 'history'" class="
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
<HistoryIcon :size="22" />
|
|
310
|
-
</div>
|
|
311
|
-
<h3 class="text-sm font-semibold">
|
|
312
|
-
{{ workflow.revision }} revision{{ workflow.revision === 1 ? "" : "s" }}
|
|
313
|
-
</h3>
|
|
314
|
-
<p class="mt-1 text-sm text-muted-foreground max-w-md mx-auto">
|
|
315
|
-
Every save that changes the blob bumps the revision and snapshots the previous one. Once history endpoints land you'll be able to diff and roll back from here.
|
|
316
|
-
</p>
|
|
317
|
-
</div>
|
|
318
|
-
</div>
|
|
618
|
+
<!-- History — n8n-style: read-only canvas preview + revision list. -->
|
|
619
|
+
<div v-else-if="tab === 'history'" class="flex-1 min-h-0">
|
|
620
|
+
<WorkflowHistoryPanel :workflow="workflow" @restored="onRevisionRestored" />
|
|
621
|
+
</div>
|
|
319
622
|
</template>
|
|
320
623
|
|
|
321
624
|
<div v-else class="p-12 text-center text-sm text-muted-foreground">
|
|
322
625
|
Loading…
|
|
323
626
|
</div>
|
|
324
627
|
</div>
|
|
628
|
+
|
|
629
|
+
<!-- Run-with-payload lightbox — opened by the editor Run split-button. -->
|
|
630
|
+
<Dialog v-model:open="runPayloadOpen">
|
|
631
|
+
<DialogContent class="sm:max-w-lg">
|
|
632
|
+
<DialogHeader>
|
|
633
|
+
<DialogTitle>Run with payload</DialogTitle>
|
|
634
|
+
<DialogDescription class="text-sm">
|
|
635
|
+
Starts a manual run with this trigger payload. The result lands on the Runs tab.
|
|
636
|
+
</DialogDescription>
|
|
637
|
+
</DialogHeader>
|
|
638
|
+
<div v-if="eventTriggerTopic" class="flex flex-wrap items-center gap-1.5">
|
|
639
|
+
<span class="text-xs text-muted-foreground">Prefill <code class="font-mono">{{ eventTriggerTopic }}</code>:</span>
|
|
640
|
+
<Button
|
|
641
|
+
variant="outline"
|
|
642
|
+
class="h-7 text-xs"
|
|
643
|
+
:disabled="!catalogSample"
|
|
644
|
+
:title="catalogSample ? 'Insert the catalog sample payload' : 'The event catalog has no sample for this topic.'"
|
|
645
|
+
@click="insertSampleEvent"
|
|
646
|
+
>
|
|
647
|
+
Sample event
|
|
648
|
+
</Button>
|
|
649
|
+
<Button
|
|
650
|
+
variant="outline"
|
|
651
|
+
class="h-7 text-xs"
|
|
652
|
+
:disabled="prefillBusy !== null"
|
|
653
|
+
title="Insert the latest received event of this topic"
|
|
654
|
+
@click="insertLatestEvent"
|
|
655
|
+
>
|
|
656
|
+
<Loader2 v-if="prefillBusy === 'latest'" :size="12" class="animate-spin" />
|
|
657
|
+
Latest real event
|
|
658
|
+
</Button>
|
|
659
|
+
</div>
|
|
660
|
+
<div class="space-y-1">
|
|
661
|
+
<IntegrationsRunPayloadInput v-if="workflow" v-model="payloadText" :blob="workflow.blob" />
|
|
662
|
+
<p v-if="payloadError" class="text-xs text-rose-600">
|
|
663
|
+
{{ payloadError }}
|
|
664
|
+
</p>
|
|
665
|
+
</div>
|
|
666
|
+
<div v-if="workflow" class="flex items-center justify-end gap-2">
|
|
667
|
+
<template v-if="workflow.execution_mode === 'caller_decides'">
|
|
668
|
+
<Button
|
|
669
|
+
variant="outline"
|
|
670
|
+
:disabled="running || syncLoading || buildBlocked"
|
|
671
|
+
:title="buildBlocked ? 'The workflow bundle is not ready to run yet.' : void 0"
|
|
672
|
+
class="h-8 text-sm"
|
|
673
|
+
@click="runFromPayloadDialog('sync')"
|
|
674
|
+
>
|
|
675
|
+
<Loader2 v-if="syncLoading" :size="14" class="animate-spin" />
|
|
676
|
+
<Play v-else :size="14" />
|
|
677
|
+
Run (sync)
|
|
678
|
+
</Button>
|
|
679
|
+
<Button
|
|
680
|
+
:disabled="running || syncLoading || buildBlocked"
|
|
681
|
+
:title="buildBlocked ? 'The workflow bundle is not ready to run yet.' : void 0"
|
|
682
|
+
class="h-8 text-sm"
|
|
683
|
+
@click="runFromPayloadDialog('async')"
|
|
684
|
+
>
|
|
685
|
+
<Loader2 v-if="running" :size="14" class="animate-spin" />
|
|
686
|
+
<Play v-else :size="14" />
|
|
687
|
+
Run (async)
|
|
688
|
+
</Button>
|
|
689
|
+
</template>
|
|
690
|
+
<Button
|
|
691
|
+
v-else
|
|
692
|
+
:disabled="running || syncLoading || buildBlocked"
|
|
693
|
+
:title="buildBlocked ? 'The workflow bundle is not ready to run yet.' : void 0"
|
|
694
|
+
class="h-8 text-sm"
|
|
695
|
+
@click="runFromPayloadDialog(primaryMode)"
|
|
696
|
+
>
|
|
697
|
+
<Loader2 v-if="running || syncLoading" :size="14" class="animate-spin" />
|
|
698
|
+
<Play v-else :size="14" />
|
|
699
|
+
Run ({{ primaryMode }})
|
|
700
|
+
</Button>
|
|
701
|
+
</div>
|
|
702
|
+
</DialogContent>
|
|
703
|
+
</Dialog>
|
|
704
|
+
|
|
705
|
+
<IntegrationsRunResponseModal
|
|
706
|
+
:open="syncOpen"
|
|
707
|
+
:loading="syncLoading"
|
|
708
|
+
:mode="runModalMode"
|
|
709
|
+
:response="syncResponse"
|
|
710
|
+
:error="syncError"
|
|
711
|
+
:run-id="runModalRunId"
|
|
712
|
+
@update:open="(v) => syncOpen = v"
|
|
713
|
+
@view-run="viewRunFromModal"
|
|
714
|
+
/>
|
|
715
|
+
|
|
716
|
+
<WorkflowSettingsDialog
|
|
717
|
+
v-if="workflow"
|
|
718
|
+
v-model:open="settingsOpen"
|
|
719
|
+
:workflow="workflow"
|
|
720
|
+
:modes="EXECUTION_MODES"
|
|
721
|
+
:mode-draft="executionModeDraft"
|
|
722
|
+
:saving-mode="savingMode"
|
|
723
|
+
:saving-meta="savingMeta"
|
|
724
|
+
:tag-suggestions="tagSuggestions"
|
|
725
|
+
@change-mode="(m) => saveExecutionMode(m)"
|
|
726
|
+
@save-meta="saveWorkflowMeta"
|
|
727
|
+
/>
|
|
728
|
+
|
|
729
|
+
<WorkflowDuplicateDialog v-model:open="duplicateOpen" :workflow="duplicateTarget" @duplicated="onDuplicated" />
|
|
325
730
|
</IntegrationsLayout>
|
|
326
731
|
</template>
|