1use crate::monitoring::{MonitoringService, WorkflowMetrics};
4use crate::workflow::WorkflowId;
5use axum::{
6 extract::{
7 ws::{Message, WebSocket},
8 State, WebSocketUpgrade,
9 },
10 response::Response,
11};
12use futures::{sink::SinkExt, stream::StreamExt};
13use serde::{Deserialize, Serialize};
14use std::sync::Arc;
15use tokio::sync::broadcast;
16use tracing::{debug, error, info};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum WorkflowEvent {
22 WorkflowStarted {
24 workflow_id: WorkflowId,
26 workflow_name: String,
28 },
29
30 WorkflowCompleted {
32 workflow_id: WorkflowId,
34 success: bool,
36 },
37
38 TaskStateChanged {
40 workflow_id: WorkflowId,
42 task_id: crate::task::TaskId,
44 task_name: String,
46 state: crate::task::TaskState,
48 },
49
50 ProgressUpdate {
52 workflow_id: WorkflowId,
54 progress: f64,
56 completed_tasks: usize,
58 total_tasks: usize,
60 },
61
62 MetricsUpdate {
64 metrics: WorkflowMetrics,
66 },
67
68 Error {
70 workflow_id: WorkflowId,
72 message: String,
74 },
75}
76
77#[derive(Clone)]
79pub struct WebSocketState {
80 pub broadcaster: Arc<broadcast::Sender<WorkflowEvent>>,
82 pub monitoring: Arc<MonitoringService>,
84}
85
86impl WebSocketState {
87 #[must_use]
89 pub fn new(monitoring: Arc<MonitoringService>) -> Self {
90 let (tx, _) = broadcast::channel(1000);
91 Self {
92 broadcaster: Arc::new(tx),
93 monitoring,
94 }
95 }
96
97 pub fn broadcast(&self, event: WorkflowEvent) {
99 let _ = self.broadcaster.send(event);
100 }
101
102 pub fn broadcast_workflow_started(&self, workflow_id: WorkflowId, workflow_name: String) {
104 self.broadcast(WorkflowEvent::WorkflowStarted {
105 workflow_id,
106 workflow_name,
107 });
108 }
109
110 pub fn broadcast_workflow_completed(&self, workflow_id: WorkflowId, success: bool) {
112 self.broadcast(WorkflowEvent::WorkflowCompleted {
113 workflow_id,
114 success,
115 });
116 }
117
118 pub fn broadcast_task_state_changed(
120 &self,
121 workflow_id: WorkflowId,
122 task_id: crate::task::TaskId,
123 task_name: String,
124 state: crate::task::TaskState,
125 ) {
126 self.broadcast(WorkflowEvent::TaskStateChanged {
127 workflow_id,
128 task_id,
129 task_name,
130 state,
131 });
132 }
133
134 pub fn broadcast_progress_update(
136 &self,
137 workflow_id: WorkflowId,
138 progress: f64,
139 completed_tasks: usize,
140 total_tasks: usize,
141 ) {
142 self.broadcast(WorkflowEvent::ProgressUpdate {
143 workflow_id,
144 progress,
145 completed_tasks,
146 total_tasks,
147 });
148 }
149
150 pub fn broadcast_metrics_update(&self, metrics: WorkflowMetrics) {
152 self.broadcast(WorkflowEvent::MetricsUpdate { metrics });
153 }
154
155 pub fn broadcast_error(&self, workflow_id: WorkflowId, message: String) {
157 self.broadcast(WorkflowEvent::Error {
158 workflow_id,
159 message,
160 });
161 }
162}
163
164pub async fn websocket_handler(
166 ws: WebSocketUpgrade,
167 State(state): State<WebSocketState>,
168) -> Response {
169 ws.on_upgrade(|socket| handle_websocket(socket, state))
170}
171
172async fn handle_websocket(socket: WebSocket, state: WebSocketState) {
173 let (mut sender, mut receiver) = socket.split();
174
175 let mut event_rx = state.broadcaster.subscribe();
177
178 info!("WebSocket client connected");
179
180 let active_workflows = state.monitoring.get_active_workflows();
182 for metrics in active_workflows {
183 let event = WorkflowEvent::MetricsUpdate { metrics };
184 if let Ok(json) = serde_json::to_string(&event) {
185 if sender.send(Message::Text(json.into())).await.is_err() {
186 error!("Failed to send initial state");
187 return;
188 }
189 }
190 }
191
192 let mut send_task = tokio::spawn(async move {
194 while let Ok(event) = event_rx.recv().await {
195 if let Ok(json) = serde_json::to_string(&event) {
196 if sender.send(Message::Text(json.into())).await.is_err() {
197 break;
198 }
199 }
200 }
201 });
202
203 let mut recv_task = tokio::spawn(async move {
205 while let Some(Ok(msg)) = receiver.next().await {
206 match msg {
207 Message::Text(text) => {
208 debug!("Received WebSocket message: {}", text);
209 }
211 Message::Close(_) => {
212 info!("WebSocket client disconnected");
213 break;
214 }
215 Message::Ping(_data) => {
216 debug!("Received ping");
217 }
219 _ => {}
220 }
221 }
222 });
223
224 tokio::select! {
226 _ = &mut send_task => {
227 recv_task.abort();
228 }
229 _ = &mut recv_task => {
230 send_task.abort();
231 }
232 }
233
234 info!("WebSocket connection closed");
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(tag = "type", rename_all = "snake_case")]
240pub enum ClientMessage {
241 Subscribe {
243 workflow_id: WorkflowId,
245 },
246
247 Unsubscribe {
249 workflow_id: WorkflowId,
251 },
252
253 SubscribeAll,
255
256 Ping,
258}
259
260pub struct WebSocketManager {
262 state: WebSocketState,
263}
264
265impl WebSocketManager {
266 #[must_use]
268 pub fn new(monitoring: Arc<MonitoringService>) -> Self {
269 Self {
270 state: WebSocketState::new(monitoring),
271 }
272 }
273
274 #[must_use]
276 pub fn state(&self) -> WebSocketState {
277 self.state.clone()
278 }
279
280 pub fn broadcast(&self, event: WorkflowEvent) {
282 self.state.broadcast(event);
283 }
284
285 #[must_use]
287 pub fn subscriber_count(&self) -> usize {
288 self.state.broadcaster.receiver_count()
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_websocket_state_creation() {
298 let monitoring = Arc::new(MonitoringService::new());
299 let state = WebSocketState::new(monitoring);
300 assert_eq!(state.broadcaster.receiver_count(), 0);
301 }
302
303 #[tokio::test]
304 async fn test_broadcast_event() {
305 let monitoring = Arc::new(MonitoringService::new());
306 let state = WebSocketState::new(monitoring);
307
308 let mut rx = state.broadcaster.subscribe();
309
310 let workflow_id = WorkflowId::new();
311 state.broadcast_workflow_started(workflow_id, "test-workflow".to_string());
312
313 let event = rx.recv().await.expect("should succeed in test");
314 match event {
315 WorkflowEvent::WorkflowStarted {
316 workflow_id: id,
317 workflow_name,
318 } => {
319 assert_eq!(id, workflow_id);
320 assert_eq!(workflow_name, "test-workflow");
321 }
322 _ => panic!("Wrong event type"),
323 }
324 }
325
326 #[test]
327 fn test_websocket_manager() {
328 let monitoring = Arc::new(MonitoringService::new());
329 let manager = WebSocketManager::new(monitoring);
330
331 assert_eq!(manager.subscriber_count(), 0);
332 }
333
334 #[test]
335 fn test_workflow_event_serialization() {
336 let event = WorkflowEvent::WorkflowStarted {
337 workflow_id: WorkflowId::new(),
338 workflow_name: "test".to_string(),
339 };
340
341 let json = serde_json::to_string(&event).expect("should succeed in test");
342 assert!(json.contains("workflow_started"));
343 }
344
345 #[test]
346 fn test_client_message_serialization() {
347 let msg = ClientMessage::Subscribe {
348 workflow_id: WorkflowId::new(),
349 };
350
351 let json = serde_json::to_string(&msg).expect("should succeed in test");
352 assert!(json.contains("subscribe"));
353 }
354}