Refactor statistics page and enhance logging

- Updated the layout and styling of the statistics page for better responsiveness and visual appeal.
- Introduced a new error page for 404 errors with user-friendly messaging and navigation options.
- Enhanced logging functionality to capture detailed events related to asset uploads, deletions, and HTTP requests.
- Implemented an AssetTracker to manage assets in memory, allowing for efficient tracking and retrieval.
- Improved the API for uploading and retrieving assets, ensuring better error handling and response formatting.
- Added auto-refresh functionality to the statistics page to keep data up-to-date.
This commit is contained in:
2026-01-11 07:51:47 +01:00
parent 81656ec0da
commit cde83139b1
13 changed files with 974 additions and 847 deletions

View File

@@ -1,68 +1,72 @@
use std::{fs::OpenOptions, io::Write, time::Instant};
use std::{fs::OpenOptions, io::Write};
use actix_web::HttpRequest;
use serde::Serialize;
use crate::LOG_DIR;
use crate::{LOG_DIR, data_mgt::Asset};
pub fn log_to_file(req: &HttpRequest, start: Instant) {
let delta = start.elapsed().as_nanos();
println!("Request processed in {} ns", delta);
let duration_ms = delta as f64 / 1000_000.0;
#[derive(Debug, Serialize)]
pub struct LogHttpRequest {
pub method: String,
pub path: String,
pub query_string: String,
pub scheme: String,
pub ip: String,
pub real_ip: String,
pub user_agent: String,
}
impl From<HttpRequest> for LogHttpRequest {
fn from(req: HttpRequest) -> Self {
let method = req.method().as_str().to_string();
let uri = req.uri();
let path = uri.path().to_string();
let query_string = uri.query().unwrap_or("-").to_string();
let log_path = LOG_DIR.to_string() + "access.log";
let connection_info = req.connection_info();
let scheme = connection_info.scheme().to_string();
let ip = connection_info.peer_addr().unwrap_or("-").to_string();
let real_ip = connection_info.realip_remote_addr().unwrap_or("-").to_string();
// Ensure log directory exists
if let Err(e) = std::fs::create_dir_all(LOG_DIR) {
eprintln!("failed to create log dir: {}", e);
return;
let user_agent = req
.headers()
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("-")
.to_string();
LogHttpRequest {
method,
path,
query_string,
scheme,
ip,
real_ip,
user_agent,
}
}
let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) else {
eprintln!("failed to open log file");
return;
};
let ts = chrono::Local::now().to_rfc3339();
let method = req.method();
let uri = req.uri();
let path = uri.path();
let query = uri.query().unwrap_or("-");
let connection_info = req.connection_info();
let scheme = connection_info.scheme();
let ip = connection_info.peer_addr().unwrap_or("-");
let real_ip = connection_info.realip_remote_addr().unwrap_or("-");
let ua = req
.headers()
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("-");
let line = format!(
"{ts} scheme={scheme} ip={ip} real_ip={real_ip} method={method} path={path} qs={query} dur_ms={duration_ms} ua=\"{ua}\"\n"
);
let _ = file.write_all(line.as_bytes());
}
pub fn log_asset_event(
action: &str,
id: &str,
mime: &str,
size_bytes: usize,
duration_min: u32,
created_at_ms: i64,
expires_at_ms: i64,
uploader_ip: &str,
) {
// Ensure logging directory exists before writing
if let Err(e) = std::fs::create_dir_all(LOG_DIR) {
eprintln!("failed to create log dir for asset event: {}", e);
return;
}
#[derive(Debug, Serialize)]
pub enum LogEventType<'a> {
AssetUploaded(&'a Asset),
AssetDeleted(&'a Asset),
HttpRequest(&'a LogHttpRequest),
}
#[derive(Debug, Serialize)]
pub struct LogEvent<'a> {
pub time: String,
pub event: LogEventType<'a>,
}
impl<'a> From<LogEventType<'a>> for LogEvent<'a> {
fn from(event: LogEventType<'a>) -> Self {
let time = chrono::Utc::now().to_rfc3339();
LogEvent { time, event }
}
}
pub fn log_event(event: LogEventType) {
let log_path = LOG_DIR.to_string() + "access.log";
let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) else {
@@ -70,11 +74,8 @@ pub fn log_asset_event(
return;
};
let ts = chrono::Local::now().to_rfc3339();
let log_event: LogEvent = event.into();
let line = serde_json::to_string(&log_event).unwrap_or_else(|e| e.to_string());
let line = format!(
"{ts} event=asset action={action} id={id} mime={mime} size_bytes={size_bytes} duration_min={duration_min} created_at_ms={created_at_ms} expires_at_ms={expires_at_ms} uploader_ip={uploader_ip}\n"
);
let _ = file.write_all(line.as_bytes());
let _ = writeln!(file, "{}", line);
}