1use askama::Template;
2use axum::{
3 extract::{Form, Path, State},
4 http::StatusCode,
5 response::{Html, Json, Redirect},
6};
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10use crate::http_server::AppState;
11
12#[derive(Template)]
13#[template(path = "dashboard.html")]
14pub struct DashboardTemplate {
15 pub queues: Vec<QueueInfo>,
16 pub messages: Vec<MessageInfo>,
17 pub total_queues: usize,
18 pub total_messages: usize,
19 pub total_available_messages: usize,
20 pub total_in_flight_messages: usize,
21}
22
23#[derive(Template)]
24#[template(path = "messages.html")]
25pub struct MessagesTemplate {
26 pub messages: Vec<MessageInfo>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct QueueInfo {
31 pub name: String,
32 pub created_at: String,
33 pub available_messages: u32,
34 pub in_flight_messages: u32,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct MessageInfo {
39 pub id: String,
40 pub body: String,
41 pub created_at: String,
42 pub visibility_timeout: String,
43 pub receive_count: u32,
44 pub attributes: String,
45 pub deduplication_id: String,
46 pub status: String,
47 pub processed_at: String,
48 pub deleted_at: String,
49}
50
51#[derive(Debug, Serialize)]
52pub struct ApiResponse {
53 pub success: bool,
54 pub message: String,
55}
56
57pub async fn dashboard(State(state): State<Arc<AppState>>) -> Result<Html<String>, String> {
58 let queues_data = state
60 .queue_service
61 .list_queues()
62 .await
63 .map_err(|e| format!("Failed to list queues: {}", e))?;
64
65 let mut queues = Vec::new();
66 let mut total_available = 0u32;
67 let mut total_in_flight = 0u32;
68
69 for (queue_name, created_at) in queues_data {
71 if let Ok(Some(attrs)) = state.queue_service.get_queue_attributes(&queue_name).await {
72 total_available += attrs.approximate_number_of_messages;
73 total_in_flight += attrs.approximate_number_of_messages_not_visible;
74
75 queues.push(QueueInfo {
76 name: queue_name,
77 created_at,
78 available_messages: attrs.approximate_number_of_messages,
79 in_flight_messages: attrs.approximate_number_of_messages_not_visible,
80 });
81 }
82 }
83
84 let template = DashboardTemplate {
85 total_queues: queues.len(),
86 total_messages: (total_available + total_in_flight) as usize,
87 total_available_messages: total_available as usize,
88 total_in_flight_messages: total_in_flight as usize,
89 queues,
90 messages: vec![], };
92
93 let html = template
94 .render()
95 .map_err(|e| format!("Template render error: {}", e))?;
96
97 Ok(Html(html))
98}
99
100pub async fn queue_messages(
101 Path(queue_name): Path<String>,
102 State(state): State<Arc<AppState>>,
103) -> Result<Html<String>, String> {
104 let messages = get_queue_messages(&state, &queue_name)
109 .await
110 .map_err(|e| format!("Failed to get messages: {}", e))?;
111
112 let template = MessagesTemplate { messages };
113
114 let html = template
115 .render()
116 .map_err(|e| format!("Template render error: {}", e))?;
117
118 Ok(Html(html))
119}
120
121async fn get_queue_messages(
122 state: &Arc<AppState>,
123 queue_name: &str,
124) -> Result<Vec<MessageInfo>, Box<dyn std::error::Error>> {
125 let messages_data = state
126 .queue_service
127 .get_all_queue_messages(queue_name)
128 .await?;
129
130 let mut messages = Vec::new();
131 for (
132 id,
133 body,
134 created_at,
135 visibility_timeout,
136 receive_count,
137 attributes,
138 deduplication_id,
139 status,
140 processed_at,
141 deleted_at,
142 ) in messages_data
143 {
144 messages.push(MessageInfo {
145 id,
146 body,
147 created_at,
148 visibility_timeout: visibility_timeout.unwrap_or_else(|| "None".to_string()),
149 receive_count,
150 attributes: attributes.unwrap_or_else(|| "None".to_string()),
151 deduplication_id: deduplication_id.unwrap_or_else(|| "None".to_string()),
152 status,
153 processed_at: processed_at.unwrap_or_else(|| "Never".to_string()),
154 deleted_at: deleted_at.unwrap_or_else(|| "Never".to_string()),
155 });
156 }
157
158 Ok(messages)
159}
160
161#[derive(Debug, Deserialize)]
163pub struct CreateQueueForm {
164 pub queue_name: String,
165 pub queue_type: Option<String>,
166 pub visibility_timeout_seconds: Option<u32>,
167 pub message_retention_period_seconds: Option<u32>,
168 pub max_receive_count: Option<u32>,
169 pub delay_seconds: Option<u32>,
170 pub receive_message_wait_time_seconds: Option<u32>,
171 pub dead_letter_target_queue: Option<String>,
172 pub content_based_deduplication: Option<String>,
173}
174
175pub async fn create_queue_ui(
177 State(state): State<Arc<AppState>>,
178 Form(form): Form<CreateQueueForm>,
179) -> Result<Redirect, String> {
180 use crate::config::QueueConfig;
181
182 if form.queue_name.trim().is_empty() {
183 return Err("Queue name cannot be empty".to_string());
184 }
185
186 let is_fifo = form
188 .queue_type
189 .as_ref()
190 .map(|t| t == "fifo")
191 .unwrap_or(false);
192
193 if form.visibility_timeout_seconds.is_none()
195 && form.message_retention_period_seconds.is_none()
196 && form.max_receive_count.is_none()
197 && form.delay_seconds.is_none()
198 && form.receive_message_wait_time_seconds.is_none()
199 && form
200 .dead_letter_target_queue
201 .as_ref()
202 .is_none_or(|s| s.trim().is_empty())
203 && !is_fifo
204 {
205 match state.queue_service.create_queue(&form.queue_name).await {
206 Ok(_) => return Ok(Redirect::to("/ui")),
207 Err(e) => return Err(format!("Failed to create queue: {}", e)),
208 }
209 }
210
211 let mut config = QueueConfig {
213 name: form.queue_name.clone(),
214 is_fifo,
215 ..Default::default()
216 };
217
218 if let Some(timeout) = form.visibility_timeout_seconds {
220 config.visibility_timeout_seconds = timeout;
221 }
222
223 if let Some(retention) = form.message_retention_period_seconds {
224 config.message_retention_period_seconds = retention;
225 }
226
227 if let Some(max_receive) = form.max_receive_count {
228 config.max_receive_count = Some(max_receive);
229 }
230
231 if let Some(delay) = form.delay_seconds {
232 config.delay_seconds = delay;
233 }
234
235 if let Some(wait_time) = form.receive_message_wait_time_seconds {
236 config.receive_message_wait_time_seconds = wait_time;
237 }
238
239 if let Some(dlq_queue) = form
240 .dead_letter_target_queue
241 .filter(|s| !s.trim().is_empty())
242 {
243 config.dead_letter_target_arn = Some(format!("qlite://queue/{}", dlq_queue));
245 }
246
247 if is_fifo {
249 config.content_based_deduplication = form
250 .content_based_deduplication
251 .map(|v| v == "on")
252 .unwrap_or(true); }
254
255 match state.queue_service.create_queue_with_config(&config).await {
256 Ok(_) => Ok(Redirect::to("/ui")),
257 Err(e) => Err(format!("Failed to create queue: {}", e)),
258 }
259}
260
261pub async fn delete_queue_ui(
262 State(state): State<Arc<AppState>>,
263 Path(queue_name): Path<String>,
264) -> Result<Redirect, String> {
265 match state.queue_service.delete_queue(&queue_name).await {
266 Ok(_) => Ok(Redirect::to("/ui")),
267 Err(e) => Err(format!("Failed to delete queue: {}", e)),
268 }
269}
270
271pub async fn delete_message_ui(
272 State(state): State<Arc<AppState>>,
273 Path(message_id): Path<String>,
274) -> Result<Redirect, String> {
275 match state.queue_service.delete_message(&message_id).await {
276 Ok(_) => Ok(Redirect::to("/ui")),
277 Err(e) => Err(format!("Failed to delete message: {}", e)),
278 }
279}
280
281pub async fn restore_message_ui(
282 State(state): State<Arc<AppState>>,
283 Path(message_id): Path<String>,
284) -> Result<Redirect, String> {
285 match state.queue_service.restore_message(&message_id).await {
286 Ok(_) => Ok(Redirect::to("/ui")),
287 Err(e) => Err(format!("Failed to restore message: {}", e)),
288 }
289}
290
291pub async fn delete_queue_json(
293 State(state): State<Arc<AppState>>,
294 Path(queue_name): Path<String>,
295) -> Result<Json<ApiResponse>, (StatusCode, Json<ApiResponse>)> {
296 match state.queue_service.delete_queue(&queue_name).await {
297 Ok(_) => Ok(Json(ApiResponse {
298 success: true,
299 message: format!("Queue '{}' deleted successfully", queue_name),
300 })),
301 Err(e) => Err((
302 StatusCode::INTERNAL_SERVER_ERROR,
303 Json(ApiResponse {
304 success: false,
305 message: format!("Failed to delete queue: {}", e),
306 }),
307 )),
308 }
309}
310
311pub async fn delete_message_json(
312 State(state): State<Arc<AppState>>,
313 Path(message_id): Path<String>,
314) -> Result<Json<ApiResponse>, (StatusCode, Json<ApiResponse>)> {
315 match state.queue_service.delete_message(&message_id).await {
316 Ok(_) => Ok(Json(ApiResponse {
317 success: true,
318 message: "Message deleted successfully".to_string(),
319 })),
320 Err(e) => Err((
321 StatusCode::INTERNAL_SERVER_ERROR,
322 Json(ApiResponse {
323 success: false,
324 message: format!("Failed to delete message: {}", e),
325 }),
326 )),
327 }
328}
329
330pub async fn restore_message_json(
331 State(state): State<Arc<AppState>>,
332 Path(message_id): Path<String>,
333) -> Result<Json<ApiResponse>, (StatusCode, Json<ApiResponse>)> {
334 match state.queue_service.restore_message(&message_id).await {
335 Ok(_) => Ok(Json(ApiResponse {
336 success: true,
337 message: "Message restored successfully".to_string(),
338 })),
339 Err(e) => Err((
340 StatusCode::INTERNAL_SERVER_ERROR,
341 Json(ApiResponse {
342 success: false,
343 message: format!("Failed to restore message: {}", e),
344 }),
345 )),
346 }
347}