added list (home) and details pages
This commit is contained in:
168
src/handlers.rs
168
src/handlers.rs
@@ -1,55 +1,151 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Path, State},
|
||||
response::{Html, IntoResponse},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use chrono::{DateTime, Utc};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tera::Context;
|
||||
|
||||
use crate::{AppState, TEMPLATES};
|
||||
|
||||
/// Handler for the Home Page
|
||||
pub async fn home_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Report {
|
||||
pub id: u64,
|
||||
pub mac_address: String,
|
||||
pub timestamp: String,
|
||||
pub slimes: HashMap<String, Vec<String>>,
|
||||
pub benchmark: Benchmark,
|
||||
pub client_version: String,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct ReportRow {
|
||||
pub id: u64,
|
||||
pub score: u64,
|
||||
pub cores: u32,
|
||||
pub ram: String,
|
||||
pub os: String,
|
||||
pub hostname: String,
|
||||
pub time_ago: String,
|
||||
pub signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Benchmark {
|
||||
pub logical_cores: u32,
|
||||
pub multi_thread: ScoreDetail,
|
||||
pub single_thread: ScoreDetail,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ScoreDetail {
|
||||
pub score: u64,
|
||||
}
|
||||
|
||||
fn format_time_ago(timestamp: &str) -> String {
|
||||
let Ok(ts) = DateTime::parse_from_rfc3339(timestamp) else {
|
||||
return "unknown".into();
|
||||
};
|
||||
let now = Utc::now();
|
||||
let diff = now.signed_duration_since(ts.with_timezone(&Utc));
|
||||
|
||||
if diff.num_days() > 0 {
|
||||
format!("{}d ago", diff.num_days())
|
||||
} else if diff.num_hours() > 0 {
|
||||
format!("{}h ago", diff.num_hours())
|
||||
} else if diff.num_minutes() > 0 {
|
||||
format!("{}min ago", diff.num_minutes())
|
||||
} else {
|
||||
"just now".into()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_total_ram(ram_str: &str) -> String {
|
||||
let total_part = ram_str.split('/').last().unwrap_or("").trim();
|
||||
let digits: String = total_part.chars().filter(|c| c.is_ascii_digit()).collect();
|
||||
if let Ok(mb) = digits.parse::<f32>() {
|
||||
format!("{}GB", (mb / 1024.0).round())
|
||||
} else {
|
||||
"Unknown".into()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn home_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let response = reqwest::get("https://alatreon.org/slimes?limit=1000")
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let raw_reports = response.json::<Vec<Report>>().await.map_err(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("JSON Error: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
let reports: Vec<ReportRow> = raw_reports
|
||||
.into_iter()
|
||||
.map(|s| ReportRow {
|
||||
id: s.id,
|
||||
score: s.benchmark.multi_thread.score,
|
||||
cores: s.benchmark.logical_cores,
|
||||
ram: s
|
||||
.slimes
|
||||
.get("RAM")
|
||||
.and_then(|v| v.first())
|
||||
.map(|r| parse_total_ram(r))
|
||||
.unwrap_or_else(|| "-".into()),
|
||||
os: s
|
||||
.slimes
|
||||
.get("OS")
|
||||
.and_then(|v| v.first())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "Unknown".into()),
|
||||
hostname: s
|
||||
.slimes
|
||||
.get("Hostname")
|
||||
.and_then(|v| v.first())
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "-".into()),
|
||||
time_ago: format_time_ago(&s.timestamp),
|
||||
signature: s.signature,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("reports", &reports);
|
||||
context.insert("title", &state.app_name);
|
||||
context.insert("server_time", &chrono::Local::now().to_rfc2822());
|
||||
|
||||
match TEMPLATES.render("index.html", &context) {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
Ok(html) => Ok(Html(html)),
|
||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler for the Showcase Page
|
||||
pub async fn showcase_handler() -> impl IntoResponse {
|
||||
#[derive(Serialize)]
|
||||
struct ShowcaseItem {
|
||||
name: String,
|
||||
description: String,
|
||||
}
|
||||
pub async fn report_details_handler(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<u64>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let url = format!("https://alatreon.org/slimes/{}", id);
|
||||
|
||||
let report = reqwest::get(url)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||
.json::<Report>()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::NOT_FOUND, format!("Report not found: {}", e)))?;
|
||||
|
||||
let mut context = Context::new();
|
||||
context.insert("title", "Showcase");
|
||||
context.insert("report", &report);
|
||||
context.insert("title", &format!("Report #{} | {}", id, state.app_name));
|
||||
context.insert("time_ago", &format_time_ago(&report.timestamp));
|
||||
|
||||
let items = vec![
|
||||
ShowcaseItem {
|
||||
name: "Project Alpha".into(),
|
||||
description: "A cool Rust project".into(),
|
||||
},
|
||||
ShowcaseItem {
|
||||
name: "Project Beta".into(),
|
||||
description: "An Axum powered API".into(),
|
||||
},
|
||||
ShowcaseItem {
|
||||
name: "Project Gamma".into(),
|
||||
description: "Tera template engine".into(),
|
||||
},
|
||||
];
|
||||
context.insert("items", &items);
|
||||
|
||||
match TEMPLATES.render("showcase.html", &context) {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(e) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
match TEMPLATES.render("details.html", &context) {
|
||||
Ok(html) => Ok(Html(html)),
|
||||
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -5,7 +5,9 @@ use std::sync::Arc;
|
||||
use tera::Tera;
|
||||
|
||||
mod handlers;
|
||||
use handlers::{home_handler, showcase_handler};
|
||||
use handlers::home_handler;
|
||||
|
||||
use crate::handlers::report_details_handler;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TEMPLATES: Tera = {
|
||||
@@ -14,7 +16,7 @@ lazy_static! {
|
||||
("_layout.html", include_str!("../templates/_layout.html")),
|
||||
("_base.css", include_str!("../templates/_base.css")),
|
||||
("index.html", include_str!("../templates/homepage.html")),
|
||||
("showcase.html", include_str!("../templates/showcase.html")),
|
||||
("details.html", include_str!("../templates/details.html")),
|
||||
])
|
||||
.unwrap();
|
||||
tera
|
||||
@@ -29,7 +31,7 @@ pub struct Cli {
|
||||
port: String,
|
||||
|
||||
/// Whether to listen on 0.0.0.0
|
||||
#[arg(short, long)]
|
||||
#[arg(long)]
|
||||
host: bool,
|
||||
|
||||
/// Verbose mode
|
||||
@@ -55,7 +57,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(home_handler))
|
||||
.route("/showcase", get(showcase_handler))
|
||||
.route("/report/{id}", get(report_details_handler))
|
||||
.with_state(shared_state);
|
||||
|
||||
let addr = if cli.host {
|
||||
|
||||
Reference in New Issue
Block a user