最后活跃于 2 weeks ago Unlisted

Código completo del servidor HTTP crustaceo-api con Axum 0.8

atareao's Avatar atareao 修订了这个 Gist 2 weeks ago. 转到此修订

1 file changed, 160 insertions

main.rs(文件已创建)

@@ -0,0 +1,160 @@
1 + use axum::{
2 + Router,
3 + extract::{Query, State},
4 + response::{IntoResponse, Response},
5 + routing::get,
6 + http::StatusCode,
7 + };
8 + use serde::{Deserialize, Serialize};
9 + use std::process::Command;
10 + use tower_http::cors::{CorsLayer, Any};
11 + use tower_http::trace::TraceLayer;
12 +
13 + struct AppError(anyhow::Error);
14 +
15 + impl IntoResponse for AppError {
16 + fn into_response(self) -> Response {
17 + let body = serde_json::json!({
18 + "error": self.0.to_string(),
19 + });
20 + (StatusCode::INTERNAL_SERVER_ERROR, axum::Json(body)).into_response()
21 + }
22 + }
23 +
24 + impl<E: Into<anyhow::Error>> From<E> for AppError {
25 + fn from(err: E) -> Self {
26 + AppError(err.into())
27 + }
28 + }
29 +
30 + #[derive(Clone)]
31 + struct AppState {
32 + start_time: chrono::DateTime<chrono::Local>,
33 + }
34 +
35 + #[derive(Serialize)]
36 + struct HealthResponse {
37 + status: String,
38 + version: String,
39 + }
40 +
41 + async fn health() -> axum::Json<HealthResponse> {
42 + axum::Json(HealthResponse {
43 + status: "ok".to_string(),
44 + version: "0.1.0".to_string(),
45 + })
46 + }
47 +
48 + #[derive(Serialize)]
49 + struct SystemInfo {
50 + hostname: String,
51 + uptime: String,
52 + cpu: String,
53 + memory: String,
54 + timestamp: String,
55 + }
56 +
57 + async fn system_info() -> Result<axum::Json<SystemInfo>, AppError> {
58 + let hostname = String::from_utf8_lossy(
59 + &Command::new("hostname").output()?.stdout
60 + ).trim().to_string();
61 + let uptime = String::from_utf8_lossy(
62 + &Command::new("uptime").arg("-p").output()?.stdout
63 + ).trim().to_string();
64 + let cpu = String::from_utf8_lossy(
65 + &Command::new("sh")
66 + .args(["-c", "lscpu | grep 'Model name' | cut -d: -f2 | xargs"])
67 + .output()?.stdout
68 + ).trim().to_string();
69 + let memory = String::from_utf8_lossy(
70 + &Command::new("free").arg("-h").args(["--si"]).output()?.stdout
71 + ).trim().to_string();
72 + Ok(axum::Json(SystemInfo {
73 + hostname, uptime, cpu, memory,
74 + timestamp: chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
75 + }))
76 + }
77 +
78 + #[derive(Deserialize)]
79 + struct LogQuery {
80 + lines: Option<u32>,
81 + }
82 +
83 + #[derive(Serialize)]
84 + struct LogResponse {
85 + lines: u32,
86 + logs: Vec<String>,
87 + }
88 +
89 + async fn get_logs(Query(params): Query<LogQuery>) -> Result<axum::Json<LogResponse>, AppError> {
90 + let lines = params.lines.unwrap_or(50).clamp(1, 500);
91 + let output = Command::new("journalctl")
92 + .args(["-n", &lines.to_string(), "--no-pager", "-o", "cat"])
93 + .output()
94 + .map_err(|e| AppError(anyhow::anyhow!("Error ejecutando journalctl: {}", e)))?;
95 + if !output.status.success() {
96 + return Err(AppError(anyhow::anyhow!(
97 + "journalctl falló: {}",
98 + String::from_utf8_lossy(&output.stderr)
99 + )));
100 + }
101 + let logs: Vec<String> = String::from_utf8_lossy(&output.stdout)
102 + .lines()
103 + .map(|l| l.to_string())
104 + .collect();
105 + Ok(axum::Json(LogResponse { lines, logs }))
106 + }
107 +
108 + async fn get_disk(State(state): State<AppState>) -> Result<axum::Json<serde_json::Value>, AppError> {
109 + let output = Command::new("df")
110 + .args(["-h", "--type=ext4", "--type=btrfs", "--type=xfs",
111 + "--exclude-type=tmpfs", "--exclude-type=devtmpfs"])
112 + .output()?;
113 + let stdout = String::from_utf8_lossy(&output.stdout);
114 + let mut disks = Vec::new();
115 + for line in stdout.lines().skip(1) {
116 + let parts: Vec<&str> = line.split_whitespace().collect();
117 + if parts.len() >= 6 {
118 + disks.push(serde_json::json!({
119 + "filesystem": parts[0],
120 + "size": parts[1],
121 + "used": parts[2],
122 + "avail": parts[3],
123 + "use_percent": parts[4],
124 + "mounted_on": parts[5],
125 + }));
126 + }
127 + }
128 + let uptime_secs = (chrono::Local::now() - state.start_time).num_seconds();
129 + Ok(axum::Json(serde_json::json!({
130 + "disks": disks,
131 + "server_uptime_seconds": uptime_secs,
132 + "timestamp": chrono::Local::now().to_rfc3339(),
133 + })))
134 + }
135 +
136 + #[tokio::main]
137 + async fn main() {
138 + tracing_subscriber::fmt()
139 + .with_env_filter(
140 + tracing_subscriber::EnvFilter::try_from_default_env()
141 + .unwrap_or_else(|_| "crustaceo_api=debug,tower_http=debug".into()),
142 + )
143 + .init();
144 + let state = AppState {
145 + start_time: chrono::Local::now(),
146 + };
147 + let app = Router::new()
148 + .route("/health", get(health))
149 + .route("/info", get(system_info))
150 + .route("/logs", get(get_logs))
151 + .route("/disk", get(get_disk))
152 + .layer(CorsLayer::new().allow_origin(Any).allow_methods(Any).allow_headers(Any))
153 + .layer(TraceLayer::new_for_http())
154 + .with_state(state);
155 + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
156 + .await
157 + .expect("No se pudo enlazar al puerto 3000");
158 + println!("🦀 crustaceo-api escuchando en http://0.0.0.0:3000");
159 + axum::serve(listener, app).await.unwrap();
160 + }
上一页 下一页