1use super::ServerDataWithConfig;
2use crate::server::{config, logging::ServerLogger};
3use actix_web::{web, HttpResponse, Result as ActixResult};
4use serde::{Deserialize, Serialize};
5use serde_json::json;
6use std::collections::VecDeque;
7use std::sync::{Arc, Mutex};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11struct Message {
12 message: String,
13 from: String,
14 timestamp: String,
15 id: u32,
16}
17
18pub async fn status_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
19 let uptime = SystemTime::now()
20 .duration_since(UNIX_EPOCH)
21 .unwrap_or_default()
22 .as_secs();
23 let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
24
25 Ok(HttpResponse::Ok().json(json!({
26 "status": "running",
27 "server_id": data.server.id,
28 "server_name": data.server.name,
29 "port": data.server.port,
30 "proxy_port": data.proxy_https_port, "server": config::get_server_name(),
32 "version": config::get_server_version(),
33 "uptime_seconds": uptime,
34 "static_files": true,
35 "template_system": true,
36 "hot_reload": true,
37 "websocket_endpoint": "/ws/hot-reload",
38 "server_directory": server_dir,
39 "log_file": format!(".rss/servers/{}-[{}].log", data.server.name, data.server.port),
40 "certificate_file": format!(".rss/certs/{}-{}.cert", data.server.name, data.server.port),
41 "private_key_file": format!(".rss/certs/{}-{}.key", data.server.name, data.server.port),
42 "urls": {
43 "http": format!("http://127.0.0.1:{}", data.server.port),
44 "proxy": format!("https://{}.localhost:{}", data.server.name, data.proxy_https_port)
45 }
46 })))
47}
48
49pub async fn info_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
50 let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
51
52 Ok(HttpResponse::Ok().json(json!({
53 "name": "Rush Sync Server",
54 "version": config::get_server_version(),
55 "server_id": data.server.id,
56 "server_name": data.server.name,
57 "port": data.server.port,
58 "proxy_port": data.proxy_https_port, "static_files_enabled": true,
60 "template_system": "enabled",
61 "hot_reload_enabled": true,
62 "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port),
63 "server_directory": server_dir,
64 "certificate": {
65 "cert_file": format!(".rss/certs/{}-{}.cert", data.server.name, data.server.port),
66 "key_file": format!(".rss/certs/{}-{}.key", data.server.name, data.server.port),
67 "common_name": format!("{}.localhost", data.server.name)
68 },
69 "urls": {
70 "http": format!("http://127.0.0.1:{}", data.server.port),
71 "proxy": format!("https://{}.localhost:{}", data.server.name, data.proxy_https_port),
72 "websocket": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port)
73 },
74 "endpoints": [
75 { "path": "/", "method": "GET", "description": "Static files from server directory", "type": "static" },
76 { "path": "/.rss/favicon.svg", "method": "GET", "description": "SVG favicon", "type": "static" },
77 { "path": "/api/status", "method": "GET", "description": "Server status", "type": "api" },
78 { "path": "/api/info", "method": "GET", "description": "API information", "type": "api" },
79 { "path": "/api/metrics", "method": "GET", "description": "Server metrics", "type": "api" },
80 { "path": "/api/stats", "method": "GET", "description": "Request statistics", "type": "api" },
81 { "path": "/api/logs", "method": "GET", "description": "Live server logs", "type": "api" },
82 { "path": "/api/logs/raw", "method": "GET", "description": "Raw log data (JSON)", "type": "api" },
83 { "path": "/api/health", "method": "GET", "description": "Health check", "type": "api" },
84 { "path": "/ws/hot-reload", "method": "GET", "description": "WebSocket hot reload", "type": "websocket" }
85 ]
86 })))
87}
88
89pub async fn metrics_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
90 let uptime = SystemTime::now()
91 .duration_since(UNIX_EPOCH)
92 .unwrap_or_default()
93 .as_secs();
94 let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
95 let log_file_size = if let Ok(logger) = ServerLogger::new(&data.server.name, data.server.port) {
96 logger.get_log_file_size_bytes().unwrap_or(0)
97 } else {
98 0
99 };
100
101 let file_count = std::fs::read_dir(&server_dir)
102 .map(|entries| entries.count())
103 .unwrap_or(0);
104
105 Ok(HttpResponse::Ok().json(json!({
106 "server_id": data.server.id,
107 "server_name": data.server.name,
108 "port": data.server.port,
109 "uptime_seconds": uptime,
110 "status": "running",
111 "hot_reload": {
112 "enabled": true,
113 "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.server.port),
114 "watching_directory": server_dir,
115 "file_watcher": "active"
116 },
117 "static_files": {
118 "directory": server_dir,
119 "file_count": file_count,
120 "enabled": true,
121 "template_based": true
122 },
123 "logging": {
124 "file_size_bytes": log_file_size,
125 "enabled": true
126 },
127 "endpoints_count": 10,
128 "last_updated": uptime
129 })))
130}
131
132pub async fn stats_handler(data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
133 let server_dir = format!("www/{}-[{}]", data.server.name, data.server.port);
134
135 let stats = if let Ok(logger) = ServerLogger::new(&data.server.name, data.server.port) {
136 logger.get_request_stats().await.unwrap_or_default()
137 } else {
138 Default::default()
139 };
140
141 Ok(HttpResponse::Ok().json(json!({
142 "server_id": data.server.id,
143 "server_name": data.server.name,
144 "server_directory": server_dir,
145 "total_requests": stats.total_requests,
146 "unique_ips": stats.unique_ips,
147 "error_requests": stats.error_requests,
148 "security_alerts": stats.security_alerts,
149 "performance_warnings": stats.performance_warnings,
150 "avg_response_time_ms": stats.avg_response_time,
151 "max_response_time_ms": stats.max_response_time,
152 "total_bytes_sent": stats.total_bytes_sent,
153 "uptime_seconds": SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
154 "hot_reload_status": "active"
155 })))
156}
157
158pub async fn health_handler(_data: web::Data<ServerDataWithConfig>) -> ActixResult<HttpResponse> {
159 let timestamp = SystemTime::now()
160 .duration_since(UNIX_EPOCH)
161 .unwrap_or_default()
162 .as_secs();
163
164 Ok(HttpResponse::Ok().json(json!({
165 "status": "healthy",
166 "timestamp": timestamp,
167 "uptime": "running",
168 "logging": "active",
169 "static_files": "enabled",
170 "template_system": "active",
171 "hot_reload": "active",
172 "file_watcher": "monitoring",
173 "config": "loaded from TOML"
174 })))
175}
176
177pub async fn ping_handler() -> ActixResult<HttpResponse> {
178 Ok(HttpResponse::Ok().json(json!({
179 "status": "pong",
180 "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
181 "server": "rush-sync-server",
182 "message": "Ping received successfully"
183 })))
184}
185
186lazy_static::lazy_static! {
188 static ref MESSAGES: Arc<Mutex<VecDeque<Message>>> = Arc::new(Mutex::new(VecDeque::new()));
189 static ref MESSAGE_COUNTER: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
190}
191
192pub async fn message_handler(body: web::Json<serde_json::Value>) -> ActixResult<HttpResponse> {
194 let message_text = body
195 .get("message")
196 .and_then(|v| v.as_str())
197 .unwrap_or("No message");
198
199 let from = body
200 .get("from")
201 .and_then(|v| v.as_str())
202 .unwrap_or("Unknown");
203
204 let timestamp = body
205 .get("timestamp")
206 .and_then(|v| v.as_str())
207 .map(|s| s.to_string())
208 .unwrap_or_else(|| chrono::Local::now().to_rfc3339());
209
210 {
212 let mut messages = MESSAGES.lock().unwrap();
213 let mut counter = MESSAGE_COUNTER.lock().unwrap();
214 *counter += 1;
215
216 let message = Message {
217 message: message_text.to_string(),
218 from: from.to_string(),
219 timestamp: timestamp.to_string(),
220 id: *counter,
221 };
222
223 messages.push_back(message);
224
225 if messages.len() > 100 {
227 messages.pop_front();
228 }
229 }
230
231 let message_id = {
233 let counter = MESSAGE_COUNTER.lock().unwrap();
234 *counter
235 };
236
237 log::info!("Message received from {}: {}", from, message_text);
238
239 Ok(HttpResponse::Ok().json(json!({
240 "status": "received",
241 "timestamp": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
242 "message_id": message_id
243 })))
244}
245
246pub async fn messages_handler() -> ActixResult<HttpResponse> {
248 let messages = {
249 let messages_lock = MESSAGES.lock().unwrap();
250 messages_lock.iter().cloned().collect::<Vec<_>>()
251 };
252
253 Ok(HttpResponse::Ok().json(json!({
254 "messages": messages,
255 "count": messages.len(),
256 "status": "success"
257 })))
258}
259
260pub async fn close_browser_handler() -> ActixResult<HttpResponse> {
261 let html = r#"
262<script>
263setTimeout(() => { window.close(); }, 100);
264document.write('<h1>Server stopped - closing...</h1>');
265</script>
266"#;
267 Ok(HttpResponse::Ok().content_type("text/html").body(html))
268}