feat: add statistics API and dashboard for asset metrics
All checks were successful
Rust CI / build-test (push) Successful in 1m22s

- Implemented `/api/stats` endpoint to return JSON metrics including active assets, total uploads, storage usage, and recent activity.
- Created `stats.html` page to display real-time statistics with auto-refresh functionality.
- Enhanced asset logging to include uploader IP and detailed event information for uploads and deletions.
- Updated asset model to store uploader IP for audit purposes.
- Improved logging functionality to ensure log directory exists before writing.
- Refactored asset creation and management to support new features and logging.
This commit is contained in:
2026-01-09 20:59:24 +01:00
parent 954a5be8cb
commit d6c465466a
10 changed files with 1036 additions and 305 deletions

View File

@@ -3,6 +3,7 @@ use chrono::{Duration, Utc};
use serde::{Deserialize, Serialize};
use crate::DATA_STORAGE;
use crate::logs::log_asset_event;
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Asset {
@@ -12,10 +13,12 @@ pub struct Asset {
expires_at: i64,
mime: String,
content: Vec<u8>,
#[serde(default)]
uploader_ip: Option<String>,
}
impl Asset {
pub fn new(share_duration: u32, mime: String, content: Vec<u8>) -> Self {
pub fn new(share_duration: u32, mime: String, content: Vec<u8>, uploader_ip: Option<String>) -> Self {
let id = uuid::Uuid::new_v4().to_string();
let created_at = Utc::now().timestamp_millis();
let expires_at = created_at + Duration::minutes(share_duration as i64).num_milliseconds();
@@ -26,6 +29,7 @@ impl Asset {
expires_at,
mime,
content,
uploader_ip,
}
}
pub fn is_expired(&self) -> bool {
@@ -44,6 +48,26 @@ impl Asset {
self.content.clone()
}
pub fn share_duration(&self) -> u32 {
self.share_duration
}
pub fn created_at(&self) -> i64 {
self.created_at
}
pub fn expires_at(&self) -> i64 {
self.expires_at
}
pub fn size_bytes(&self) -> usize {
self.content.len()
}
pub fn uploader_ip(&self) -> Option<&str> {
self.uploader_ip.as_deref()
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
let bytes = serde_json::to_vec(self)?;
Ok(bytes)
@@ -68,6 +92,16 @@ pub async fn clear_assets() -> Result<()> {
let asset = serde_json::from_slice::<Asset>(&data)?;
if asset.is_expired() {
println!("Removing expired asset: {}", asset.id());
log_asset_event(
"delete_expired",
asset.id(),
asset.mime(),
asset.size_bytes(),
asset.share_duration(),
asset.created_at(),
asset.expires_at(),
asset.uploader_ip().unwrap_or("-"),
);
std::fs::remove_file(&path)?;
}
}