rush_sync_server/server/handlers/web/
api.rs1use super::PROXY_PORT;
3use crate::server::{config, logging::ServerLogger, types::ServerData};
4use actix_web::{web, HttpResponse, Result as ActixResult};
5use serde_json::json;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8pub async fn status_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
9 let uptime = SystemTime::now()
10 .duration_since(UNIX_EPOCH)
11 .unwrap_or_default()
12 .as_secs();
13 let server_dir = format!("www/{}-[{}]", data.name, data.port);
14
15 Ok(HttpResponse::Ok().json(json!({
16 "status": "running",
17 "server_id": data.id,
18 "server_name": data.name,
19 "port": data.port,
20 "proxy_port": PROXY_PORT,
21 "server": config::get_server_name(),
22 "version": config::get_server_version(),
23 "uptime_seconds": uptime,
24 "static_files": true,
25 "template_system": true,
26 "hot_reload": true,
27 "websocket_endpoint": "/ws/hot-reload",
28 "server_directory": server_dir,
29 "log_file": format!(".rss/servers/{}-[{}].log", data.name, data.port),
30 "certificate_file": format!(".rss/certs/{}-{}.cert", data.name, data.port),
31 "private_key_file": format!(".rss/certs/{}-{}.key", data.name, data.port),
32 "urls": {
33 "http": format!("http://127.0.0.1:{}", data.port),
34 "proxy": format!("https://{}.localhost:{}", data.name, PROXY_PORT)
35 }
36 })))
37}
38
39pub async fn info_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
40 let server_dir = format!("www/{}-[{}]", data.name, data.port);
41
42 Ok(HttpResponse::Ok().json(json!({
43 "name": "Rush Sync Server",
44 "version": config::get_server_version(),
45 "server_id": data.id,
46 "server_name": data.name,
47 "port": data.port,
48 "proxy_port": PROXY_PORT,
49 "static_files_enabled": true,
50 "template_system": "enabled",
51 "hot_reload_enabled": true,
52 "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port),
53 "server_directory": server_dir,
54 "certificate": {
55 "cert_file": format!(".rss/certs/{}-{}.cert", data.name, data.port),
56 "key_file": format!(".rss/certs/{}-{}.key", data.name, data.port),
57 "common_name": format!("{}.localhost", data.name)
58 },
59 "urls": {
60 "http": format!("http://127.0.0.1:{}", data.port),
61 "proxy": format!("https://{}.localhost:{}", data.name, PROXY_PORT),
62 "websocket": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port)
63 },
64 "endpoints": [
65 { "path": "/", "method": "GET", "description": "Static files from server directory", "type": "static" },
66 { "path": "/.rss/favicon.svg", "method": "GET", "description": "SVG favicon", "type": "static" },
67 { "path": "/api/status", "method": "GET", "description": "Server status", "type": "api" },
68 { "path": "/api/info", "method": "GET", "description": "API information", "type": "api" },
69 { "path": "/api/metrics", "method": "GET", "description": "Server metrics", "type": "api" },
70 { "path": "/api/stats", "method": "GET", "description": "Request statistics", "type": "api" },
71 { "path": "/api/logs", "method": "GET", "description": "Live server logs", "type": "api" },
72 { "path": "/api/logs/raw", "method": "GET", "description": "Raw log data (JSON)", "type": "api" },
73 { "path": "/api/health", "method": "GET", "description": "Health check", "type": "api" },
74 { "path": "/ws/hot-reload", "method": "GET", "description": "WebSocket hot reload", "type": "websocket" }
75 ]
76 })))
77}
78
79pub async fn metrics_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
80 let uptime = SystemTime::now()
81 .duration_since(UNIX_EPOCH)
82 .unwrap_or_default()
83 .as_secs();
84 let server_dir = format!("www/{}-[{}]", data.name, data.port);
85 let log_file_size = if let Ok(logger) = ServerLogger::new(&data.name, data.port) {
86 logger.get_log_file_size_bytes().unwrap_or(0)
87 } else {
88 0
89 };
90
91 let file_count = std::fs::read_dir(&server_dir)
92 .map(|entries| entries.count())
93 .unwrap_or(0);
94
95 Ok(HttpResponse::Ok().json(json!({
96 "server_id": data.id,
97 "server_name": data.name,
98 "port": data.port,
99 "uptime_seconds": uptime,
100 "status": "running",
101 "hot_reload": {
102 "enabled": true,
103 "websocket_url": format!("ws://127.0.0.1:{}/ws/hot-reload", data.port),
104 "watching_directory": server_dir,
105 "file_watcher": "active"
106 },
107 "static_files": {
108 "directory": server_dir,
109 "file_count": file_count,
110 "enabled": true,
111 "template_based": true
112 },
113 "logging": {
114 "file_size_bytes": log_file_size,
115 "enabled": true
116 },
117 "endpoints_count": 10,
118 "last_updated": uptime
119 })))
120}
121
122pub async fn stats_handler(data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
123 let server_dir = format!("www/{}-[{}]", data.name, data.port);
124
125 let stats = if let Ok(logger) = ServerLogger::new(&data.name, data.port) {
126 logger.get_request_stats().await.unwrap_or_default()
127 } else {
128 Default::default()
129 };
130
131 Ok(HttpResponse::Ok().json(json!({
132 "server_id": data.id,
133 "server_name": data.name,
134 "server_directory": server_dir,
135 "total_requests": stats.total_requests,
136 "unique_ips": stats.unique_ips,
137 "error_requests": stats.error_requests,
138 "security_alerts": stats.security_alerts,
139 "performance_warnings": stats.performance_warnings,
140 "avg_response_time_ms": stats.avg_response_time,
141 "max_response_time_ms": stats.max_response_time,
142 "total_bytes_sent": stats.total_bytes_sent,
143 "uptime_seconds": SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs(),
144 "hot_reload_status": "active"
145 })))
146}
147
148pub async fn health_handler(_data: web::Data<ServerData>) -> ActixResult<HttpResponse> {
149 let timestamp = SystemTime::now()
150 .duration_since(UNIX_EPOCH)
151 .unwrap_or_default()
152 .as_secs();
153
154 Ok(HttpResponse::Ok().json(json!({
155 "status": "healthy",
156 "timestamp": timestamp,
157 "uptime": "running",
158 "logging": "active",
159 "static_files": "enabled",
160 "template_system": "active",
161 "hot_reload": "active",
162 "file_watcher": "monitoring",
163 "config": "loaded from TOML"
164 })))
165}
166
167pub async fn close_browser_handler() -> ActixResult<HttpResponse> {
168 let html = r#"
169<script>
170setTimeout(() => { window.close(); }, 100);
171document.write('<h1>Server stopped - closing...</h1>');
172</script>
173"#;
174 Ok(HttpResponse::Ok().content_type("text/html").body(html))
175}