@nvent-addon/app 0.5.5 → 0.5.7

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/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nventapp",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "configKey": "nventapp",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
@@ -453,12 +453,16 @@ function isEmitEvent(type) {
453
453
  }
454
454
  function hasMetadata(eventData) {
455
455
  if (!eventData || typeof eventData !== "object") return false;
456
- const keys = Object.keys(eventData).filter((k) => k !== "message" && k !== "level" && k !== "msg");
456
+ const autoInjectedKeys = ["message", "level", "msg", "stepName", "stepId", "stepRunId", "attempt", "flowName"];
457
+ const keys = Object.keys(eventData).filter((k) => !autoInjectedKeys.includes(k));
457
458
  return keys.length > 0;
458
459
  }
459
460
  function prettyMetadata(eventData) {
460
461
  if (!eventData || typeof eventData !== "object") return "";
461
- const { message, level, msg, ...metadata } = eventData;
462
+ const { message, level, msg, stepName, stepId, stepRunId, attempt, flowName, ...metadata } = eventData;
463
+ if (Object.keys(metadata).length === 1 && "value" in metadata) {
464
+ return pretty(metadata.value);
465
+ }
462
466
  return pretty(metadata);
463
467
  }
464
468
  </script>
@@ -42,7 +42,7 @@ interface FlowMeta {
42
42
  };
43
43
  }
44
44
  interface StepStatus {
45
- status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout' | 'canceled';
45
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout' | 'canceled' | 'stalled';
46
46
  attempt?: number;
47
47
  error?: string;
48
48
  scheduledTriggerAt?: string;
@@ -112,7 +112,7 @@ const nodes = computed(() => {
112
112
  if (f.entry) {
113
113
  const entryState = states[f.entry.step];
114
114
  const status = mapStatusToNodeStatus(entryState?.status);
115
- const entryStepTimeout = f.entry.stepTimeout;
115
+ const entryStepTimeout = f.analyzed?.steps?.[f.entry.step]?.stepTimeout;
116
116
  out.push({
117
117
  id: `entry:${f.entry.step}`,
118
118
  position: { x: -nodeWidth / 2, y },
@@ -306,6 +306,7 @@ function mapStatusToNodeStatus(status) {
306
306
  return "done";
307
307
  case "failed":
308
308
  case "timeout":
309
+ case "stalled":
309
310
  return "error";
310
311
  case "canceled":
311
312
  return "canceled";
@@ -42,7 +42,7 @@ interface FlowMeta {
42
42
  };
43
43
  }
44
44
  interface StepStatus {
45
- status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout' | 'canceled';
45
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout' | 'canceled' | 'stalled';
46
46
  attempt?: number;
47
47
  error?: string;
48
48
  scheduledTriggerAt?: string;
@@ -13,9 +13,11 @@ type __VLS_Props = {
13
13
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
14
  "select-step": (stepKey: string | null) => any;
15
15
  "cancel-flow": () => any;
16
+ "restart-flow": () => any;
16
17
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
18
  "onSelect-step"?: ((stepKey: string | null) => any) | undefined;
18
19
  "onCancel-flow"?: (() => any) | undefined;
20
+ "onRestart-flow"?: (() => any) | undefined;
19
21
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
22
  declare const _default: typeof __VLS_export;
21
23
  export default _default;
@@ -21,15 +21,27 @@
21
21
  </div>
22
22
 
23
23
  <!-- Cancel Button (only show for running/awaiting flows) -->
24
- <UButton
25
- v-if="runStatus === 'running' || runStatus === 'awaiting'"
26
- color="neutral"
27
- variant="ghost"
28
- icon="i-lucide-x-circle"
29
- size="xs"
30
- label="Cancel"
31
- @click="handleCancelFlow"
32
- />
24
+ <div class="flex items-center gap-2">
25
+ <UButton
26
+ v-if="runStatus === 'running' || runStatus === 'awaiting'"
27
+ color="neutral"
28
+ variant="ghost"
29
+ icon="i-lucide-x-circle"
30
+ size="xs"
31
+ label="Cancel"
32
+ @click="handleCancelFlow"
33
+ />
34
+ <!-- Restart Button (show for terminal states) -->
35
+ <UButton
36
+ v-if="runStatus === 'failed' || runStatus === 'stalled' || runStatus === 'canceled' || runStatus === 'completed'"
37
+ color="primary"
38
+ variant="ghost"
39
+ icon="i-lucide-rotate-ccw"
40
+ size="xs"
41
+ label="Restart"
42
+ @click="handleRestartFlow"
43
+ />
44
+ </div>
33
45
  </div>
34
46
 
35
47
  <!-- Bottom Row: Compact Stats Grid -->
@@ -120,10 +132,13 @@ const props = defineProps({
120
132
  flowDef: { type: null, required: false },
121
133
  stallTimeout: { type: Number, required: false }
122
134
  });
123
- const emit = defineEmits(["select-step", "cancel-flow"]);
135
+ const emit = defineEmits(["select-step", "cancel-flow", "restart-flow"]);
124
136
  const handleCancelFlow = () => {
125
137
  emit("cancel-flow");
126
138
  };
139
+ const handleRestartFlow = () => {
140
+ emit("restart-flow");
141
+ };
127
142
  const selectedStep = ref("all-steps");
128
143
  const firstStepKey = computed(() => props.steps?.length > 0 ? props.steps[0]?.key : void 0);
129
144
  watch(firstStepKey, (newKey, oldKey) => {
@@ -13,9 +13,11 @@ type __VLS_Props = {
13
13
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
14
  "select-step": (stepKey: string | null) => any;
15
15
  "cancel-flow": () => any;
16
+ "restart-flow": () => any;
16
17
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
17
18
  "onSelect-step"?: ((stepKey: string | null) => any) | undefined;
18
19
  "onCancel-flow"?: (() => any) | undefined;
20
+ "onRestart-flow"?: (() => any) | undefined;
19
21
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
20
22
  declare const _default: typeof __VLS_export;
21
23
  export default _default;
@@ -438,6 +438,8 @@ const getStepStatusBg = (status) => {
438
438
  return "bg-emerald-50 dark:bg-emerald-900/20";
439
439
  case "failed":
440
440
  return "bg-red-50 dark:bg-red-900/20";
441
+ case "stalled":
442
+ return "bg-amber-50 dark:bg-amber-900/20";
441
443
  case "running":
442
444
  return "bg-blue-50 dark:bg-blue-900/20";
443
445
  default:
@@ -450,6 +452,8 @@ const getStepStatusIcon = (status) => {
450
452
  return "i-lucide-check-circle";
451
453
  case "failed":
452
454
  return "i-lucide-x-circle";
455
+ case "stalled":
456
+ return "i-lucide-alert-triangle";
453
457
  case "running":
454
458
  return "i-lucide-loader-circle";
455
459
  default:
@@ -462,6 +466,8 @@ const getStepStatusIconColor = (status) => {
462
466
  return "text-emerald-600 dark:text-emerald-400";
463
467
  case "failed":
464
468
  return "text-red-600 dark:text-red-400";
469
+ case "stalled":
470
+ return "text-amber-600 dark:text-amber-400";
465
471
  case "running":
466
472
  return "text-blue-600 dark:text-blue-400 animate-spin";
467
473
  default:
@@ -474,6 +480,8 @@ const getStepStatusTextColor = (status) => {
474
480
  return "text-emerald-600 dark:text-emerald-400";
475
481
  case "failed":
476
482
  return "text-red-600 dark:text-red-400";
483
+ case "stalled":
484
+ return "text-amber-600 dark:text-amber-400";
477
485
  case "running":
478
486
  return "text-blue-600 dark:text-blue-400";
479
487
  default:
@@ -21,7 +21,7 @@ export declare function useFlowRunTimeline(flowId: Ref<string>, runId: Ref<strin
21
21
  isStalled: import("vue").ComputedRef<boolean>;
22
22
  isAwaiting: import("vue").ComputedRef<boolean>;
23
23
  stepList: import("vue").ComputedRef<{
24
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
24
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
25
25
  attempt: number;
26
26
  startedAt?: string;
27
27
  completedAt?: string;
@@ -33,7 +33,7 @@ export declare function useFlowRunTimeline(flowId: Ref<string>, runId: Ref<strin
33
33
  key: string;
34
34
  }[]>;
35
35
  runningSteps: import("vue").ComputedRef<{
36
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
36
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
37
37
  attempt: number;
38
38
  startedAt?: string;
39
39
  completedAt?: string;
@@ -45,7 +45,7 @@ export declare function useFlowRunTimeline(flowId: Ref<string>, runId: Ref<strin
45
45
  key: string;
46
46
  }[]>;
47
47
  waitingSteps: import("vue").ComputedRef<{
48
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
48
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
49
49
  attempt: number;
50
50
  startedAt?: string;
51
51
  completedAt?: string;
@@ -57,7 +57,7 @@ export declare function useFlowRunTimeline(flowId: Ref<string>, runId: Ref<strin
57
57
  key: string;
58
58
  }[]>;
59
59
  failedSteps: import("vue").ComputedRef<{
60
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
60
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
61
61
  attempt: number;
62
62
  startedAt?: string;
63
63
  completedAt?: string;
@@ -69,7 +69,7 @@ export declare function useFlowRunTimeline(flowId: Ref<string>, runId: Ref<strin
69
69
  key: string;
70
70
  }[]>;
71
71
  completedSteps: import("vue").ComputedRef<{
72
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
72
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
73
73
  attempt: number;
74
74
  startedAt?: string;
75
75
  completedAt?: string;
@@ -13,7 +13,7 @@ export interface FlowState {
13
13
  meta?: Record<string, any>;
14
14
  }
15
15
  export interface StepState {
16
- status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout';
16
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout' | 'stalled';
17
17
  attempt: number;
18
18
  startedAt?: string;
19
19
  completedAt?: string;
@@ -72,7 +72,7 @@ export declare function useFlowState(initialEvents?: EventRecord[]): {
72
72
  isStalled: import("vue").ComputedRef<boolean>;
73
73
  isAwaiting: import("vue").ComputedRef<boolean>;
74
74
  stepList: import("vue").ComputedRef<{
75
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
75
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
76
76
  attempt: number;
77
77
  startedAt?: string;
78
78
  completedAt?: string;
@@ -84,7 +84,7 @@ export declare function useFlowState(initialEvents?: EventRecord[]): {
84
84
  key: string;
85
85
  }[]>;
86
86
  runningSteps: import("vue").ComputedRef<{
87
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
87
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
88
88
  attempt: number;
89
89
  startedAt?: string;
90
90
  completedAt?: string;
@@ -96,7 +96,7 @@ export declare function useFlowState(initialEvents?: EventRecord[]): {
96
96
  key: string;
97
97
  }[]>;
98
98
  waitingSteps: import("vue").ComputedRef<{
99
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
99
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
100
100
  attempt: number;
101
101
  startedAt?: string;
102
102
  completedAt?: string;
@@ -108,7 +108,7 @@ export declare function useFlowState(initialEvents?: EventRecord[]): {
108
108
  key: string;
109
109
  }[]>;
110
110
  failedSteps: import("vue").ComputedRef<{
111
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
111
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
112
112
  attempt: number;
113
113
  startedAt?: string;
114
114
  completedAt?: string;
@@ -120,7 +120,7 @@ export declare function useFlowState(initialEvents?: EventRecord[]): {
120
120
  key: string;
121
121
  }[]>;
122
122
  completedSteps: import("vue").ComputedRef<{
123
- status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
123
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout" | "stalled";
124
124
  attempt: number;
125
125
  startedAt?: string;
126
126
  completedAt?: string;
@@ -94,6 +94,16 @@ export function reduceFlowState(events) {
94
94
  state.steps[stepKey].error = e.data?.error;
95
95
  break;
96
96
  }
97
+ case "step.stalled": {
98
+ if (!stepKey) break;
99
+ if (!state.steps[stepKey]) {
100
+ state.steps[stepKey] = { status: "stalled", attempt: 1 };
101
+ }
102
+ state.steps[stepKey].status = "stalled";
103
+ state.steps[stepKey].error = e.data?.reason || "Step execution interrupted";
104
+ state.steps[stepKey].completedAt = e.ts;
105
+ break;
106
+ }
97
107
  case "step.await.time":
98
108
  case "step.await.event":
99
109
  case "step.await.trigger": {
@@ -199,22 +209,8 @@ export function reduceFlowState(events) {
199
209
  (s) => s.status === "running" || s.status === "retrying"
200
210
  );
201
211
  const hasWaitingSteps = Object.values(state.steps).some((s) => s.status === "waiting");
202
- const hasFailedSteps = Object.values(state.steps).some((s) => s.status === "failed");
203
- const allStepsTerminal = Object.values(state.steps).every(
204
- (s) => s.status === "completed" || s.status === "failed" || s.status === "timeout"
205
- );
206
212
  if (hasWaitingSteps && !hasActiveRunningSteps) {
207
213
  state.status = "awaiting";
208
- } else if (allStepsTerminal) {
209
- if (hasFailedSteps) {
210
- state.status = "failed";
211
- } else {
212
- state.status = "completed";
213
- }
214
- const latestCompletion = Object.values(state.steps).map((s) => s.completedAt).filter(Boolean).sort().pop();
215
- if (latestCompletion) {
216
- state.completedAt = latestCompletion;
217
- }
218
214
  }
219
215
  }
220
216
  return state;
@@ -297,6 +297,7 @@
297
297
  :stall-timeout="runSnapshot.stallTimeout"
298
298
  @select-step="handleSelectStep"
299
299
  @cancel-flow="handleCancelFlow"
300
+ @restart-flow="handleRestartFlow"
300
301
  />
301
302
  </div>
302
303
  </div>
@@ -424,7 +425,7 @@ const selectedRunId = computed({
424
425
  const goBack = () => {
425
426
  componentRouter.push("/flows");
426
427
  };
427
- const mainTab = ref("diagram");
428
+ const mainTab = ref(route.query.run ? "timeline" : "diagram");
428
429
  const mainTabs = computed(() => [
429
430
  { label: "Diagram", value: "diagram", icon: "i-lucide-git-branch" },
430
431
  {
@@ -541,8 +542,8 @@ const runSnapshot = computed(() => {
541
542
  completedAt: state.completedAt,
542
543
  logsCount: state.logs.length,
543
544
  lastLogLevel: state.logs.length > 0 ? state.logs[state.logs.length - 1]?.level : void 0,
544
- stallTimeout: flowMeta?.analyzed?.stallTimeout
545
- // Get from analyzed flows (static)
545
+ // Use stallTimeout from event data if available, otherwise fall back to static flow definition
546
+ stallTimeout: state.meta?.stallTimeout || flowMeta?.analyzed?.stallTimeout
546
547
  };
547
548
  });
548
549
  const selectedStepKey = ref(null);
@@ -566,9 +567,6 @@ const enhancedStepList = computed(() => {
566
567
  const flowMeta = selectedFlowMeta.value;
567
568
  if (!flowMeta?.analyzed?.steps) return steps;
568
569
  const stepTimeoutMap = /* @__PURE__ */ new Map();
569
- if (flowMeta.entry?.stepTimeout !== void 0) {
570
- stepTimeoutMap.set(flowMeta.entry.step, flowMeta.entry.stepTimeout);
571
- }
572
570
  for (const [stepName, analyzedStep] of Object.entries(flowMeta.analyzed.steps)) {
573
571
  if (analyzedStep.stepTimeout !== void 0) {
574
572
  stepTimeoutMap.set(stepName, analyzedStep.stepTimeout);
@@ -596,6 +594,21 @@ const handleCancelFlow = async () => {
596
594
  console.error("Failed to cancel flow:", error);
597
595
  }
598
596
  };
597
+ const handleRestartFlow = async () => {
598
+ if (!selectedFlow.value || !selectedRunId.value) return;
599
+ try {
600
+ const result = await $fetch(`/api/_flows/${selectedFlow.value}/runs/${selectedRunId.value}/restart`, {
601
+ method: "POST"
602
+ });
603
+ if (result?.newRunId) {
604
+ selectedRunId.value = result.newRunId;
605
+ mainTab.value = "timeline";
606
+ }
607
+ await refreshRuns();
608
+ } catch (error) {
609
+ console.error("Failed to restart flow:", error);
610
+ }
611
+ };
599
612
  const diagramStepStates = computed(() => {
600
613
  if (!selectedRunId.value) return void 0;
601
614
  return flowState.state.value.steps;
@@ -273,19 +273,17 @@ function updateFlowStats(data) {
273
273
  running: metadata["stats.running"] || 0,
274
274
  awaiting: metadata["stats.awaiting"] || 0
275
275
  };
276
- flows.value[flowIndex] = {
277
- ...flows.value[flowIndex],
278
- stats: {
279
- total: stats.total || 0,
280
- success: stats.success || 0,
281
- failure: stats.failure || 0,
282
- cancel: stats.cancel || 0,
283
- running: stats.running || 0,
284
- awaiting: stats.awaiting || 0
285
- },
286
- lastRunAt: metadata.lastRunAt,
287
- lastCompletedAt: metadata.lastCompletedAt
276
+ const flow = flows.value[flowIndex];
277
+ flow.stats = {
278
+ total: stats.total || 0,
279
+ success: stats.success || 0,
280
+ failure: stats.failure || 0,
281
+ cancel: stats.cancel || 0,
282
+ running: stats.running || 0,
283
+ awaiting: stats.awaiting || 0
288
284
  };
285
+ flow.lastRunAt = metadata.lastRunAt;
286
+ flow.lastCompletedAt = metadata.lastCompletedAt;
289
287
  }
290
288
  onMounted(async () => {
291
289
  await fetchAnalyzedFlows();
@@ -314,7 +314,7 @@ const hasChanges = computed(() => {
314
314
  const saveError = ref(null);
315
315
  const saveSuccess = ref(false);
316
316
  const onSubmit = async (event) => {
317
- if (!trigger.value) return;
317
+ if (!trigger.value || isSaving.value) return;
318
318
  isSaving.value = true;
319
319
  saveError.value = null;
320
320
  saveSuccess.value = false;
@@ -351,6 +351,7 @@ function updateTriggerStats(data) {
351
351
  }
352
352
  onMounted(async () => {
353
353
  await fetchTriggers();
354
+ loading.value = false;
354
355
  if (import.meta.client) {
355
356
  triggerWs.subscribeStats(
356
357
  {
@@ -363,9 +364,6 @@ onMounted(async () => {
363
364
  },
364
365
  {
365
366
  autoReconnect: true,
366
- onOpen: () => {
367
- loading.value = false;
368
- },
369
367
  onError: (err) => {
370
368
  console.error("[Trigger Stats] Error:", err);
371
369
  }
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { defineEventHandler, getRouterParam, createError, useFlow } from "#imports";
2
+ export default defineEventHandler(async (event) => {
3
+ const flowName = getRouterParam(event, "name");
4
+ const runId = getRouterParam(event, "runId");
5
+ if (!flowName || !runId) {
6
+ throw createError({
7
+ statusCode: 400,
8
+ statusMessage: "Flow name and run ID are required"
9
+ });
10
+ }
11
+ const flowEngine = useFlow();
12
+ try {
13
+ const result = await flowEngine.restartFlow(flowName, runId);
14
+ return result;
15
+ } catch (error) {
16
+ throw createError({
17
+ statusCode: 500,
18
+ statusMessage: `Failed to restart flow: ${error.message}`
19
+ });
20
+ }
21
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nvent-addon/app",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "nvent app module for Nuxt.js",
5
5
  "repository": "DevJoghurt/nvent",
6
6
  "license": "MIT",