@nvent-addon/app 0.4.5

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.
Files changed (99) hide show
  1. package/dist/module.d.mts +6 -0
  2. package/dist/module.json +9 -0
  3. package/dist/module.mjs +42 -0
  4. package/dist/runtime/app/assets/vueflow.css +1 -0
  5. package/dist/runtime/app/components/ConfirmDialog.d.vue.ts +33 -0
  6. package/dist/runtime/app/components/ConfirmDialog.vue +121 -0
  7. package/dist/runtime/app/components/ConfirmDialog.vue.d.ts +33 -0
  8. package/dist/runtime/app/components/FlowDiagram.d.vue.ts +65 -0
  9. package/dist/runtime/app/components/FlowDiagram.vue +341 -0
  10. package/dist/runtime/app/components/FlowDiagram.vue.d.ts +65 -0
  11. package/dist/runtime/app/components/FlowNodeCard.d.vue.ts +29 -0
  12. package/dist/runtime/app/components/FlowNodeCard.vue +158 -0
  13. package/dist/runtime/app/components/FlowNodeCard.vue.d.ts +29 -0
  14. package/dist/runtime/app/components/FlowRunOverview.d.vue.ts +17 -0
  15. package/dist/runtime/app/components/FlowRunOverview.vue +188 -0
  16. package/dist/runtime/app/components/FlowRunOverview.vue.d.ts +17 -0
  17. package/dist/runtime/app/components/FlowRunStatusBadge.d.vue.ts +18 -0
  18. package/dist/runtime/app/components/FlowRunStatusBadge.vue +74 -0
  19. package/dist/runtime/app/components/FlowRunStatusBadge.vue.d.ts +18 -0
  20. package/dist/runtime/app/components/FlowRunTimeline.d.vue.ts +12 -0
  21. package/dist/runtime/app/components/FlowRunTimeline.vue +127 -0
  22. package/dist/runtime/app/components/FlowRunTimeline.vue.d.ts +12 -0
  23. package/dist/runtime/app/components/FlowScheduleDialog.d.vue.ts +16 -0
  24. package/dist/runtime/app/components/FlowScheduleDialog.vue +226 -0
  25. package/dist/runtime/app/components/FlowScheduleDialog.vue.d.ts +16 -0
  26. package/dist/runtime/app/components/FlowSchedulesList.d.vue.ts +12 -0
  27. package/dist/runtime/app/components/FlowSchedulesList.vue +99 -0
  28. package/dist/runtime/app/components/FlowSchedulesList.vue.d.ts +12 -0
  29. package/dist/runtime/app/components/FlowStepSelector.d.vue.ts +22 -0
  30. package/dist/runtime/app/components/FlowStepSelector.vue +238 -0
  31. package/dist/runtime/app/components/FlowStepSelector.vue.d.ts +22 -0
  32. package/dist/runtime/app/components/JobScheduling.d.vue.ts +6 -0
  33. package/dist/runtime/app/components/JobScheduling.vue +203 -0
  34. package/dist/runtime/app/components/JobScheduling.vue.d.ts +6 -0
  35. package/dist/runtime/app/components/ListItem.d.vue.ts +23 -0
  36. package/dist/runtime/app/components/ListItem.vue +70 -0
  37. package/dist/runtime/app/components/ListItem.vue.d.ts +23 -0
  38. package/dist/runtime/app/components/QueueConfigDetails.d.vue.ts +45 -0
  39. package/dist/runtime/app/components/QueueConfigDetails.vue +412 -0
  40. package/dist/runtime/app/components/QueueConfigDetails.vue.d.ts +45 -0
  41. package/dist/runtime/app/components/StatCounter.d.vue.ts +9 -0
  42. package/dist/runtime/app/components/StatCounter.vue +25 -0
  43. package/dist/runtime/app/components/StatCounter.vue.d.ts +9 -0
  44. package/dist/runtime/app/components/TimelineList.d.vue.ts +7 -0
  45. package/dist/runtime/app/components/TimelineList.vue +211 -0
  46. package/dist/runtime/app/components/TimelineList.vue.d.ts +7 -0
  47. package/dist/runtime/app/components/nhealth/component-router.d.vue.ts +46 -0
  48. package/dist/runtime/app/components/nhealth/component-router.vue +26 -0
  49. package/dist/runtime/app/components/nhealth/component-router.vue.d.ts +46 -0
  50. package/dist/runtime/app/components/nhealth/component-shell.d.vue.ts +24 -0
  51. package/dist/runtime/app/components/nhealth/component-shell.vue +89 -0
  52. package/dist/runtime/app/components/nhealth/component-shell.vue.d.ts +24 -0
  53. package/dist/runtime/app/composables/useAnalyzedFlows.d.ts +14 -0
  54. package/dist/runtime/app/composables/useAnalyzedFlows.js +8 -0
  55. package/dist/runtime/app/composables/useComponentRouter.d.ts +38 -0
  56. package/dist/runtime/app/composables/useComponentRouter.js +240 -0
  57. package/dist/runtime/app/composables/useFlowRunTimeline.d.ts +82 -0
  58. package/dist/runtime/app/composables/useFlowRunTimeline.js +67 -0
  59. package/dist/runtime/app/composables/useFlowRuns.d.ts +18 -0
  60. package/dist/runtime/app/composables/useFlowRuns.js +32 -0
  61. package/dist/runtime/app/composables/useFlowRunsInfinite.d.ts +24 -0
  62. package/dist/runtime/app/composables/useFlowRunsInfinite.js +123 -0
  63. package/dist/runtime/app/composables/useFlowRunsPolling.d.ts +9 -0
  64. package/dist/runtime/app/composables/useFlowRunsPolling.js +33 -0
  65. package/dist/runtime/app/composables/useFlowState.d.ts +127 -0
  66. package/dist/runtime/app/composables/useFlowState.js +225 -0
  67. package/dist/runtime/app/composables/useFlowWebSocket.d.ts +27 -0
  68. package/dist/runtime/app/composables/useFlowWebSocket.js +222 -0
  69. package/dist/runtime/app/composables/useFlowsNavigation.d.ts +10 -0
  70. package/dist/runtime/app/composables/useFlowsNavigation.js +58 -0
  71. package/dist/runtime/app/composables/useQueueJobs.d.ts +26 -0
  72. package/dist/runtime/app/composables/useQueueJobs.js +20 -0
  73. package/dist/runtime/app/composables/useQueueUpdates.d.ts +24 -0
  74. package/dist/runtime/app/composables/useQueueUpdates.js +54 -0
  75. package/dist/runtime/app/composables/useQueues.d.ts +45 -0
  76. package/dist/runtime/app/composables/useQueues.js +26 -0
  77. package/dist/runtime/app/composables/useQueuesLive.d.ts +16 -0
  78. package/dist/runtime/app/composables/useQueuesLive.js +62 -0
  79. package/dist/runtime/app/composables/useQueuesWebSocket.d.ts +17 -0
  80. package/dist/runtime/app/composables/useQueuesWebSocket.js +159 -0
  81. package/dist/runtime/app/pages/flows/index.d.vue.ts +3 -0
  82. package/dist/runtime/app/pages/flows/index.vue +683 -0
  83. package/dist/runtime/app/pages/flows/index.vue.d.ts +3 -0
  84. package/dist/runtime/app/pages/index.d.vue.ts +3 -0
  85. package/dist/runtime/app/pages/index.vue +34 -0
  86. package/dist/runtime/app/pages/index.vue.d.ts +3 -0
  87. package/dist/runtime/app/pages/queues/index.d.vue.ts +3 -0
  88. package/dist/runtime/app/pages/queues/index.vue +229 -0
  89. package/dist/runtime/app/pages/queues/index.vue.d.ts +3 -0
  90. package/dist/runtime/app/pages/queues/job.d.vue.ts +3 -0
  91. package/dist/runtime/app/pages/queues/job.vue +262 -0
  92. package/dist/runtime/app/pages/queues/job.vue.d.ts +3 -0
  93. package/dist/runtime/app/pages/queues/jobs.d.vue.ts +3 -0
  94. package/dist/runtime/app/pages/queues/jobs.vue +291 -0
  95. package/dist/runtime/app/pages/queues/jobs.vue.d.ts +3 -0
  96. package/dist/runtime/app/plugins/vueflow.client.d.ts +2 -0
  97. package/dist/runtime/app/plugins/vueflow.client.js +11 -0
  98. package/dist/types.d.mts +7 -0
  99. package/package.json +47 -0
@@ -0,0 +1,127 @@
1
+ import { type Ref } from '#imports';
2
+ /**
3
+ * Client-Side Flow State Reducer
4
+ *
5
+ * Reduces an array of events from the flow timeline into current state.
6
+ */
7
+ export interface FlowState {
8
+ status: 'running' | 'completed' | 'failed' | 'canceled' | 'stalled';
9
+ startedAt?: string;
10
+ completedAt?: string;
11
+ steps: Record<string, StepState>;
12
+ logs: LogEntry[];
13
+ meta?: Record<string, any>;
14
+ }
15
+ export interface StepState {
16
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'retrying' | 'waiting' | 'timeout';
17
+ attempt: number;
18
+ startedAt?: string;
19
+ completedAt?: string;
20
+ error?: string;
21
+ awaitType?: 'time' | 'event' | 'trigger';
22
+ awaitData?: any;
23
+ result?: any;
24
+ }
25
+ export interface LogEntry {
26
+ ts: string;
27
+ step?: string;
28
+ level: string;
29
+ msg: string;
30
+ data?: any;
31
+ }
32
+ export interface EventRecord {
33
+ id: string;
34
+ ts: string;
35
+ type: string;
36
+ runId: string;
37
+ flowName?: string;
38
+ stepName?: string;
39
+ stepId?: string;
40
+ attempt?: number;
41
+ data?: any;
42
+ }
43
+ /**
44
+ * Reduce an array of events into current flow state
45
+ */
46
+ export declare function reduceFlowState(events: EventRecord[]): FlowState;
47
+ /**
48
+ * Composable for managing flow state from event stream
49
+ *
50
+ * Usage:
51
+ * ```typescript
52
+ * const { state, events, addEvent, addEvents, reset } = useFlowState()
53
+ *
54
+ * // Add events as they arrive
55
+ * addEvent(newEvent)
56
+ *
57
+ * // Access computed state
58
+ * console.log(state.value.status) // 'running' | 'completed' | 'failed'
59
+ * ```
60
+ */
61
+ export declare function useFlowState(initialEvents?: EventRecord[]): {
62
+ events: Ref<EventRecord[], EventRecord[]>;
63
+ state: import("vue").ComputedRef<FlowState>;
64
+ addEvent: (event: EventRecord) => void;
65
+ addEvents: (newEvents: EventRecord[]) => void;
66
+ reset: (newEvents?: EventRecord[]) => void;
67
+ isRunning: import("vue").ComputedRef<boolean>;
68
+ isCompleted: import("vue").ComputedRef<boolean>;
69
+ isFailed: import("vue").ComputedRef<boolean>;
70
+ isCanceled: import("vue").ComputedRef<boolean>;
71
+ isStalled: import("vue").ComputedRef<boolean>;
72
+ stepList: import("vue").ComputedRef<{
73
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
74
+ attempt: number;
75
+ startedAt?: string;
76
+ completedAt?: string;
77
+ error?: string;
78
+ awaitType?: "time" | "event" | "trigger";
79
+ awaitData?: any;
80
+ result?: any;
81
+ key: string;
82
+ }[]>;
83
+ runningSteps: import("vue").ComputedRef<{
84
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
85
+ attempt: number;
86
+ startedAt?: string;
87
+ completedAt?: string;
88
+ error?: string;
89
+ awaitType?: "time" | "event" | "trigger";
90
+ awaitData?: any;
91
+ result?: any;
92
+ key: string;
93
+ }[]>;
94
+ waitingSteps: import("vue").ComputedRef<{
95
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
96
+ attempt: number;
97
+ startedAt?: string;
98
+ completedAt?: string;
99
+ error?: string;
100
+ awaitType?: "time" | "event" | "trigger";
101
+ awaitData?: any;
102
+ result?: any;
103
+ key: string;
104
+ }[]>;
105
+ failedSteps: import("vue").ComputedRef<{
106
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
107
+ attempt: number;
108
+ startedAt?: string;
109
+ completedAt?: string;
110
+ error?: string;
111
+ awaitType?: "time" | "event" | "trigger";
112
+ awaitData?: any;
113
+ result?: any;
114
+ key: string;
115
+ }[]>;
116
+ completedSteps: import("vue").ComputedRef<{
117
+ status: "pending" | "running" | "completed" | "failed" | "retrying" | "waiting" | "timeout";
118
+ attempt: number;
119
+ startedAt?: string;
120
+ completedAt?: string;
121
+ error?: string;
122
+ awaitType?: "time" | "event" | "trigger";
123
+ awaitData?: any;
124
+ result?: any;
125
+ key: string;
126
+ }[]>;
127
+ };
@@ -0,0 +1,225 @@
1
+ import { ref, computed } from "#imports";
2
+ export function reduceFlowState(events) {
3
+ const state = {
4
+ status: "running",
5
+ steps: {},
6
+ logs: []
7
+ };
8
+ for (const e of events) {
9
+ const eventType = e.type;
10
+ const stepKey = e.stepName;
11
+ switch (eventType) {
12
+ case "flow.start":
13
+ case "flow.started":
14
+ state.status = "running";
15
+ state.startedAt = e.ts;
16
+ if (e.flowName) state.meta = { ...state.meta, flowName: e.flowName };
17
+ if (e.data?.flowName) state.meta = { ...state.meta, flowName: e.data.flowName };
18
+ if (e.data?.input) state.meta = { ...state.meta, input: e.data.input };
19
+ break;
20
+ case "flow.complete":
21
+ case "flow.completed":
22
+ state.status = "completed";
23
+ state.completedAt = e.ts;
24
+ if (e.data?.result) state.meta = { ...state.meta, result: e.data.result };
25
+ break;
26
+ case "flow.failed":
27
+ state.status = "failed";
28
+ state.completedAt = e.ts;
29
+ if (e.data?.error) state.meta = { ...state.meta, error: e.data.error };
30
+ break;
31
+ case "flow.cancel":
32
+ case "flow.canceled":
33
+ state.status = "canceled";
34
+ state.completedAt = e.ts;
35
+ break;
36
+ case "flow.stalled":
37
+ state.status = "stalled";
38
+ if (e.data?.lastActivityAt) state.meta = { ...state.meta, lastActivityAt: e.data.lastActivityAt };
39
+ if (e.data?.stallTimeout) state.meta = { ...state.meta, stallTimeout: e.data.stallTimeout };
40
+ break;
41
+ case "step.started": {
42
+ if (!stepKey) break;
43
+ if (!state.steps[stepKey]) {
44
+ state.steps[stepKey] = {
45
+ status: "running",
46
+ attempt: 1
47
+ };
48
+ }
49
+ const currentStatus = state.steps[stepKey].status;
50
+ if (currentStatus !== "completed" && currentStatus !== "failed" && currentStatus !== "timeout") {
51
+ state.steps[stepKey].status = "running";
52
+ }
53
+ state.steps[stepKey].startedAt = e.ts;
54
+ state.steps[stepKey].attempt = e.attempt || state.steps[stepKey].attempt || 1;
55
+ break;
56
+ }
57
+ case "step.completed": {
58
+ if (!stepKey) break;
59
+ if (!state.steps[stepKey]) {
60
+ state.steps[stepKey] = { status: "completed", attempt: 1 };
61
+ }
62
+ state.steps[stepKey].status = "completed";
63
+ state.steps[stepKey].completedAt = e.ts;
64
+ if (e.data?.result !== void 0) state.steps[stepKey].result = e.data.result;
65
+ break;
66
+ }
67
+ case "step.failed": {
68
+ if (!stepKey) break;
69
+ if (!state.steps[stepKey]) {
70
+ state.steps[stepKey] = { status: "failed", attempt: 1 };
71
+ }
72
+ const willRetry = e.data?.willRetry || e.data?.retry;
73
+ state.steps[stepKey].status = willRetry ? "retrying" : "failed";
74
+ state.steps[stepKey].error = e.data?.error || e.data?.message;
75
+ if (!willRetry) {
76
+ state.steps[stepKey].completedAt = e.ts;
77
+ }
78
+ break;
79
+ }
80
+ case "step.retry": {
81
+ if (!stepKey) break;
82
+ if (!state.steps[stepKey]) {
83
+ state.steps[stepKey] = { status: "retrying", attempt: 1 };
84
+ }
85
+ state.steps[stepKey].status = "retrying";
86
+ state.steps[stepKey].attempt = e.data?.nextAttempt || e.attempt || 1;
87
+ state.steps[stepKey].error = e.data?.error;
88
+ break;
89
+ }
90
+ case "step.await.time":
91
+ case "step.await.event":
92
+ case "step.await.trigger": {
93
+ if (!stepKey) break;
94
+ if (!state.steps[stepKey]) {
95
+ state.steps[stepKey] = { status: "waiting", attempt: 1 };
96
+ }
97
+ state.steps[stepKey].status = "waiting";
98
+ state.steps[stepKey].awaitType = eventType.split(".")[2];
99
+ state.steps[stepKey].awaitData = e.data;
100
+ break;
101
+ }
102
+ case "step.resumed": {
103
+ if (!stepKey) break;
104
+ if (!state.steps[stepKey]) {
105
+ state.steps[stepKey] = { status: "running", attempt: 1 };
106
+ }
107
+ state.steps[stepKey].status = "running";
108
+ delete state.steps[stepKey].awaitType;
109
+ delete state.steps[stepKey].awaitData;
110
+ break;
111
+ }
112
+ case "step.await.timeout": {
113
+ if (!stepKey) break;
114
+ if (!state.steps[stepKey]) {
115
+ state.steps[stepKey] = { status: "timeout", attempt: 1 };
116
+ }
117
+ state.steps[stepKey].status = "timeout";
118
+ state.steps[stepKey].error = `Await timeout after ${e.data?.duration}ms`;
119
+ state.steps[stepKey].completedAt = e.ts;
120
+ break;
121
+ }
122
+ case "runner.log":
123
+ case "log": {
124
+ state.logs.push({
125
+ ts: e.ts,
126
+ step: stepKey,
127
+ level: e.data?.level || "info",
128
+ msg: e.data?.message || e.data?.msg || (typeof e.data === "string" ? e.data : String(e.data)),
129
+ data: e.data
130
+ });
131
+ break;
132
+ }
133
+ // Handle any unrecognized events - log for debugging
134
+ default: {
135
+ if (typeof console !== "undefined" && eventType && !eventType.startsWith("_")) {
136
+ console.debug("[useFlowState] Unhandled event type:", eventType, {
137
+ stepName: e.stepName,
138
+ stepKey,
139
+ data: e.data
140
+ });
141
+ }
142
+ }
143
+ }
144
+ }
145
+ if (!state.startedAt && events.length > 0 && events[0]) {
146
+ state.startedAt = events[0].ts;
147
+ }
148
+ if (state.status === "running" && state.startedAt && Object.keys(state.steps).length > 0) {
149
+ const hasRunningSteps = Object.values(state.steps).some(
150
+ (s) => s.status === "running" || s.status === "retrying" || s.status === "waiting"
151
+ );
152
+ const hasFailedSteps = Object.values(state.steps).some((s) => s.status === "failed");
153
+ const allStepsTerminal = Object.values(state.steps).every(
154
+ (s) => s.status === "completed" || s.status === "failed" || s.status === "timeout"
155
+ );
156
+ if (!hasRunningSteps && allStepsTerminal) {
157
+ if (hasFailedSteps) {
158
+ state.status = "failed";
159
+ } else {
160
+ state.status = "completed";
161
+ }
162
+ const latestCompletion = Object.values(state.steps).map((s) => s.completedAt).filter(Boolean).sort().pop();
163
+ if (latestCompletion) {
164
+ state.completedAt = latestCompletion;
165
+ }
166
+ }
167
+ }
168
+ return state;
169
+ }
170
+ export function useFlowState(initialEvents = []) {
171
+ const events = ref(initialEvents);
172
+ const state = computed(() => reduceFlowState(events.value));
173
+ const addEvent = (event) => {
174
+ events.value.push(event);
175
+ };
176
+ const addEvents = (newEvents) => {
177
+ events.value.push(...newEvents);
178
+ };
179
+ const reset = (newEvents = []) => {
180
+ events.value = newEvents;
181
+ };
182
+ const isRunning = computed(() => state.value.status === "running");
183
+ const isCompleted = computed(() => state.value.status === "completed");
184
+ const isFailed = computed(() => state.value.status === "failed");
185
+ const isCanceled = computed(() => state.value.status === "canceled");
186
+ const isStalled = computed(() => state.value.status === "stalled");
187
+ const stepList = computed(() => {
188
+ return Object.entries(state.value.steps).map(([key, step]) => ({
189
+ key,
190
+ ...step
191
+ }));
192
+ });
193
+ const runningSteps = computed(() => {
194
+ return stepList.value.filter((s) => s.status === "running");
195
+ });
196
+ const waitingSteps = computed(() => {
197
+ return stepList.value.filter((s) => s.status === "waiting");
198
+ });
199
+ const failedSteps = computed(() => {
200
+ return stepList.value.filter((s) => s.status === "failed");
201
+ });
202
+ const completedSteps = computed(() => {
203
+ return stepList.value.filter((s) => s.status === "completed");
204
+ });
205
+ return {
206
+ // Raw data
207
+ events,
208
+ state,
209
+ // Methods
210
+ addEvent,
211
+ addEvents,
212
+ reset,
213
+ // Computed helpers
214
+ isRunning,
215
+ isCompleted,
216
+ isFailed,
217
+ isCanceled,
218
+ isStalled,
219
+ stepList,
220
+ runningSteps,
221
+ waitingSteps,
222
+ failedSteps,
223
+ completedSteps
224
+ };
225
+ }
@@ -0,0 +1,27 @@
1
+ export interface UseFlowWebSocketOptions {
2
+ autoReconnect?: boolean;
3
+ maxRetries?: number;
4
+ baseDelayMs?: number;
5
+ maxDelayMs?: number;
6
+ onOpen?: () => void;
7
+ onError?: (err?: any) => void;
8
+ onClose?: (event?: CloseEvent) => void;
9
+ }
10
+ export interface FlowSubscription {
11
+ flowName: string;
12
+ runId: string;
13
+ onEvent: (event: any) => void;
14
+ onHistory?: (events: any[]) => void;
15
+ }
16
+ /**
17
+ * WebSocket composable for flow run events
18
+ * Replaces the SSE-based useEventSSE with a more reliable WebSocket implementation
19
+ */
20
+ export declare function useFlowWebSocket(): {
21
+ subscribe: (subscription: FlowSubscription, opts?: UseFlowWebSocketOptions) => void;
22
+ unsubscribe: () => void;
23
+ stop: () => void;
24
+ connected: import("vue").Ref<boolean, boolean>;
25
+ reconnecting: import("vue").Ref<boolean, boolean>;
26
+ };
27
+ export default useFlowWebSocket;
@@ -0,0 +1,222 @@
1
+ import { ref, onBeforeUnmount } from "#imports";
2
+ export function useFlowWebSocket() {
3
+ const ws = ref(null);
4
+ const connected = ref(false);
5
+ const reconnecting = ref(false);
6
+ let retry = 0;
7
+ let reconnectTimer = null;
8
+ let currentOptions;
9
+ let currentSubscription = null;
10
+ let pingInterval = null;
11
+ let isServerRestarting = false;
12
+ const computeDelay = (opts) => {
13
+ const base = Math.max(100, opts?.baseDelayMs ?? 1e3);
14
+ const max = Math.max(base, opts?.maxDelayMs ?? 1e4);
15
+ const exp = Math.min(max, base * Math.pow(2, retry));
16
+ const jitter = Math.floor(Math.random() * Math.min(1e3, exp / 4));
17
+ return exp + jitter;
18
+ };
19
+ const clearTimers = () => {
20
+ if (reconnectTimer) {
21
+ try {
22
+ clearTimeout(reconnectTimer);
23
+ } catch {
24
+ }
25
+ reconnectTimer = null;
26
+ }
27
+ if (pingInterval) {
28
+ try {
29
+ clearInterval(pingInterval);
30
+ } catch {
31
+ }
32
+ pingInterval = null;
33
+ }
34
+ };
35
+ const send = (data) => {
36
+ if (ws.value && ws.value.readyState === WebSocket.OPEN) {
37
+ ws.value.send(JSON.stringify(data));
38
+ }
39
+ };
40
+ const startPingInterval = () => {
41
+ clearTimers();
42
+ pingInterval = setInterval(() => {
43
+ if (ws.value && ws.value.readyState === WebSocket.OPEN) {
44
+ send({ type: "ping" });
45
+ }
46
+ }, 3e4);
47
+ };
48
+ const stop = () => {
49
+ clearTimers();
50
+ isServerRestarting = false;
51
+ try {
52
+ if (ws.value) {
53
+ ws.value.close(1e3, "Client closing");
54
+ }
55
+ } catch (err) {
56
+ console.warn("[useFlowWebSocket] Error closing WebSocket:", err);
57
+ }
58
+ ws.value = null;
59
+ connected.value = false;
60
+ reconnecting.value = false;
61
+ retry = 0;
62
+ currentSubscription = null;
63
+ };
64
+ const attemptReconnect = () => {
65
+ if (!currentOptions?.autoReconnect) {
66
+ stop();
67
+ return;
68
+ }
69
+ const max = Math.max(0, currentOptions?.maxRetries ?? 10);
70
+ if (retry >= max) {
71
+ console.error("[useFlowWebSocket] Max retries reached");
72
+ stop();
73
+ return;
74
+ }
75
+ retry++;
76
+ reconnecting.value = true;
77
+ const baseDelay = isServerRestarting ? 2e3 : computeDelay(currentOptions);
78
+ const delay = baseDelay;
79
+ console.log(`[useFlowWebSocket] Will attempt reconnection in ${delay}ms (attempt ${retry}/${max})${isServerRestarting ? " [server restart]" : ""}`);
80
+ clearTimers();
81
+ reconnectTimer = setTimeout(() => {
82
+ if (currentSubscription) {
83
+ innerSubscribe(currentSubscription, currentOptions);
84
+ }
85
+ }, delay);
86
+ };
87
+ const setupWebSocket = (socket, subscription, opts) => {
88
+ socket.onopen = () => {
89
+ console.log("[useFlowWebSocket] Connected");
90
+ connected.value = true;
91
+ reconnecting.value = false;
92
+ retry = 0;
93
+ startPingInterval();
94
+ send({
95
+ type: "subscribe",
96
+ flowName: subscription.flowName,
97
+ runId: subscription.runId
98
+ });
99
+ opts?.onOpen?.();
100
+ };
101
+ socket.onmessage = (event) => {
102
+ try {
103
+ const data = JSON.parse(event.data);
104
+ switch (data.type) {
105
+ case "connected":
106
+ console.log("[useFlowWebSocket] Server acknowledged connection");
107
+ break;
108
+ case "subscribed":
109
+ console.log("[useFlowWebSocket] Subscribed to flow:", data.flowName, data.runId);
110
+ break;
111
+ case "unsubscribed":
112
+ console.log("[useFlowWebSocket] Unsubscribed from flow:", data.flowName, data.runId);
113
+ break;
114
+ case "history":
115
+ if (subscription.onHistory) {
116
+ subscription.onHistory(data.events);
117
+ } else {
118
+ for (const eventData of data.events) {
119
+ subscription.onEvent(eventData);
120
+ }
121
+ }
122
+ break;
123
+ case "event":
124
+ subscription.onEvent(data.event);
125
+ break;
126
+ case "pong":
127
+ break;
128
+ case "server-restart":
129
+ console.log("[useFlowWebSocket] Server is restarting (HMR)");
130
+ isServerRestarting = true;
131
+ break;
132
+ case "error":
133
+ console.error("[useFlowWebSocket] Server error:", data.message);
134
+ opts?.onError?.(new Error(data.message));
135
+ break;
136
+ default:
137
+ console.warn("[useFlowWebSocket] Unknown message type:", data.type);
138
+ }
139
+ } catch (err) {
140
+ console.error("[useFlowWebSocket] Error parsing message:", err);
141
+ }
142
+ };
143
+ socket.onerror = (err) => {
144
+ console.error("[useFlowWebSocket] WebSocket error:", err);
145
+ opts?.onError?.(err);
146
+ };
147
+ socket.onclose = (event) => {
148
+ console.log("[useFlowWebSocket] Connection closed:", event.code, event.reason);
149
+ connected.value = false;
150
+ clearTimers();
151
+ opts?.onClose?.(event);
152
+ const shouldReconnect = event.code !== 1e3 && opts?.autoReconnect;
153
+ if (shouldReconnect) {
154
+ console.log("[useFlowWebSocket] Will attempt reconnection (code:", event.code, ")");
155
+ attemptReconnect();
156
+ } else {
157
+ isServerRestarting = false;
158
+ }
159
+ };
160
+ };
161
+ const innerSubscribe = (subscription, opts) => {
162
+ if (import.meta.server || typeof WebSocket === "undefined") {
163
+ console.warn("[useFlowWebSocket] WebSocket not available (SSR context)");
164
+ return;
165
+ }
166
+ if (ws.value && ws.value.readyState === WebSocket.OPEN) {
167
+ console.log("[useFlowWebSocket] Reusing connection, switching subscription");
168
+ if (currentSubscription) {
169
+ send({
170
+ type: "unsubscribe",
171
+ flowName: currentSubscription.flowName,
172
+ runId: currentSubscription.runId
173
+ });
174
+ }
175
+ currentSubscription = subscription;
176
+ send({
177
+ type: "subscribe",
178
+ flowName: subscription.flowName,
179
+ runId: subscription.runId
180
+ });
181
+ return;
182
+ }
183
+ if (ws.value) {
184
+ stop();
185
+ }
186
+ currentOptions = opts;
187
+ currentSubscription = subscription;
188
+ try {
189
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
190
+ const wsUrl = `${protocol}//${window.location.host}/api/_flows/ws`;
191
+ const socket = new WebSocket(wsUrl);
192
+ ws.value = socket;
193
+ setupWebSocket(socket, subscription, opts);
194
+ } catch (err) {
195
+ console.error("[useFlowWebSocket] Error creating WebSocket:", err);
196
+ opts?.onError?.(err);
197
+ attemptReconnect();
198
+ }
199
+ };
200
+ const subscribe = (subscription, opts) => {
201
+ innerSubscribe(subscription, opts);
202
+ };
203
+ const unsubscribe = () => {
204
+ if (currentSubscription && ws.value && ws.value.readyState === WebSocket.OPEN) {
205
+ send({
206
+ type: "unsubscribe",
207
+ flowName: currentSubscription.flowName,
208
+ runId: currentSubscription.runId
209
+ });
210
+ }
211
+ currentSubscription = null;
212
+ };
213
+ onBeforeUnmount(() => stop());
214
+ return {
215
+ subscribe,
216
+ unsubscribe,
217
+ stop,
218
+ connected,
219
+ reconnecting
220
+ };
221
+ }
222
+ export default useFlowWebSocket;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Composable for managing flows page navigation state
3
+ * Uses URL query params for persistence across HMR
4
+ */
5
+ export declare function useFlowsNavigation(): {
6
+ selectedFlow: import("vue").WritableComputedRef<string, string>;
7
+ selectedRunId: import("vue").WritableComputedRef<string, string>;
8
+ timelineOpen: import("vue").Ref<boolean, boolean>;
9
+ selectedTab: import("vue").WritableComputedRef<string, string>;
10
+ };
@@ -0,0 +1,58 @@
1
+ import { computed, ref, watch, useRoute, useRouter } from "#imports";
2
+ export function useFlowsNavigation() {
3
+ const route = useRoute();
4
+ const router = useRouter();
5
+ const selectedFlow = computed({
6
+ get: () => route.query.flow || "",
7
+ set: (value) => {
8
+ router.push({
9
+ query: {
10
+ ...route.query,
11
+ flow: value || void 0,
12
+ run: void 0
13
+ // Clear run when flow changes
14
+ }
15
+ });
16
+ }
17
+ });
18
+ const selectedRunId = computed({
19
+ get: () => route.query.run || "",
20
+ set: (value) => {
21
+ router.push({
22
+ query: {
23
+ ...route.query,
24
+ run: value || void 0
25
+ }
26
+ });
27
+ }
28
+ });
29
+ const timelineOpen = ref(route.query.timeline === "true");
30
+ watch(timelineOpen, (value) => {
31
+ router.push({
32
+ query: {
33
+ ...route.query,
34
+ timeline: value ? "true" : void 0
35
+ }
36
+ });
37
+ });
38
+ watch(() => route.query.timeline, (value) => {
39
+ timelineOpen.value = value === "true";
40
+ });
41
+ const selectedTab = computed({
42
+ get: () => route.query.tab || "overview",
43
+ set: (value) => {
44
+ router.push({
45
+ query: {
46
+ ...route.query,
47
+ tab: value !== "overview" ? value : void 0
48
+ }
49
+ });
50
+ }
51
+ });
52
+ return {
53
+ selectedFlow,
54
+ selectedRunId,
55
+ timelineOpen,
56
+ selectedTab
57
+ };
58
+ }
@@ -0,0 +1,26 @@
1
+ import { type Ref } from '#imports';
2
+ import type { FetchError } from 'ofetch';
3
+ export interface Job {
4
+ id: string;
5
+ name: string;
6
+ data: any;
7
+ state?: 'waiting' | 'active' | 'completed' | 'failed' | 'delayed' | 'paused';
8
+ returnvalue?: any;
9
+ failedReason?: string;
10
+ timestamp?: number;
11
+ processedOn?: number;
12
+ finishedOn?: number;
13
+ }
14
+ export interface JobsResponse {
15
+ jobs: Job[];
16
+ }
17
+ /**
18
+ * Composable for fetching jobs for a queue
19
+ * Client-only to avoid hydration mismatches
20
+ */
21
+ export declare function useQueueJobs(queueName: Ref<string>, state?: Ref<string | null>): {
22
+ data: Ref<JobsResponse | null | undefined>;
23
+ refresh: () => Promise<void>;
24
+ status: Ref<'idle' | 'pending' | 'success' | 'error'>;
25
+ error: Ref<FetchError | null | undefined>;
26
+ };