pinakes: import in parallel; various UI improvements
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I1eb47cd79cd4145c56af966f6756fe1d6a6a6964
This commit is contained in:
parent
278bcaa4b0
commit
116fe7b059
42 changed files with 4189 additions and 316 deletions
|
|
@ -1,8 +1,221 @@
|
|||
use axum::Json;
|
||||
use std::time::Instant;
|
||||
|
||||
pub async fn health() -> Json<serde_json::Value> {
|
||||
Json(serde_json::json!({
|
||||
"status": "ok",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
}))
|
||||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::IntoResponse;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
/// Basic health check response
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct HealthResponse {
|
||||
pub status: String,
|
||||
pub version: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub database: Option<DatabaseHealth>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filesystem: Option<FilesystemHealth>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cache: Option<CacheHealth>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DatabaseHealth {
|
||||
pub status: String,
|
||||
pub latency_ms: u64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub media_count: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct FilesystemHealth {
|
||||
pub status: String,
|
||||
pub roots_configured: usize,
|
||||
pub roots_accessible: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct CacheHealth {
|
||||
pub hit_rate: f64,
|
||||
pub total_entries: u64,
|
||||
pub responses_size: u64,
|
||||
pub queries_size: u64,
|
||||
pub media_size: u64,
|
||||
}
|
||||
|
||||
/// Comprehensive health check - includes database, filesystem, and cache status
|
||||
pub async fn health(State(state): State<AppState>) -> Json<HealthResponse> {
|
||||
let mut response = HealthResponse {
|
||||
status: "ok".to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
database: None,
|
||||
filesystem: None,
|
||||
cache: None,
|
||||
};
|
||||
|
||||
// Check database health
|
||||
let db_start = Instant::now();
|
||||
let db_health = match state.storage.count_media().await {
|
||||
Ok(count) => DatabaseHealth {
|
||||
status: "ok".to_string(),
|
||||
latency_ms: db_start.elapsed().as_millis() as u64,
|
||||
media_count: Some(count),
|
||||
},
|
||||
Err(e) => {
|
||||
response.status = "degraded".to_string();
|
||||
DatabaseHealth {
|
||||
status: format!("error: {}", e),
|
||||
latency_ms: db_start.elapsed().as_millis() as u64,
|
||||
media_count: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
response.database = Some(db_health);
|
||||
|
||||
// Check filesystem health (root directories)
|
||||
let roots = match state.storage.list_root_dirs().await {
|
||||
Ok(r) => r,
|
||||
Err(_) => Vec::new(),
|
||||
};
|
||||
let roots_accessible = roots.iter().filter(|r| r.exists()).count();
|
||||
if roots_accessible < roots.len() {
|
||||
response.status = "degraded".to_string();
|
||||
}
|
||||
response.filesystem = Some(FilesystemHealth {
|
||||
status: if roots_accessible == roots.len() {
|
||||
"ok"
|
||||
} else {
|
||||
"degraded"
|
||||
}
|
||||
.to_string(),
|
||||
roots_configured: roots.len(),
|
||||
roots_accessible,
|
||||
});
|
||||
|
||||
// Get cache statistics
|
||||
let cache_stats = state.cache.stats();
|
||||
response.cache = Some(CacheHealth {
|
||||
hit_rate: cache_stats.overall_hit_rate(),
|
||||
total_entries: cache_stats.total_entries(),
|
||||
responses_size: cache_stats.responses.size,
|
||||
queries_size: cache_stats.queries.size,
|
||||
media_size: cache_stats.media.size,
|
||||
});
|
||||
|
||||
Json(response)
|
||||
}
|
||||
|
||||
/// Liveness probe - just checks if the server is running
|
||||
/// Returns 200 OK if the server process is alive
|
||||
pub async fn liveness() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({
|
||||
"status": "alive"
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
/// Readiness probe - checks if the server can serve requests
|
||||
/// Returns 200 OK if database is accessible
|
||||
pub async fn readiness(State(state): State<AppState>) -> impl IntoResponse {
|
||||
// Check database connectivity
|
||||
let db_start = Instant::now();
|
||||
match state.storage.count_media().await {
|
||||
Ok(_) => {
|
||||
let latency = db_start.elapsed().as_millis() as u64;
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(serde_json::json!({
|
||||
"status": "ready",
|
||||
"database_latency_ms": latency
|
||||
})),
|
||||
)
|
||||
}
|
||||
Err(e) => (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({
|
||||
"status": "not_ready",
|
||||
"reason": e.to_string()
|
||||
})),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Detailed health check for monitoring dashboards
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DetailedHealthResponse {
|
||||
pub status: String,
|
||||
pub version: String,
|
||||
pub uptime_seconds: u64,
|
||||
pub database: DatabaseHealth,
|
||||
pub filesystem: FilesystemHealth,
|
||||
pub cache: CacheHealth,
|
||||
pub jobs: JobsHealth,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct JobsHealth {
|
||||
pub pending: usize,
|
||||
pub running: usize,
|
||||
}
|
||||
|
||||
pub async fn health_detailed(State(state): State<AppState>) -> Json<DetailedHealthResponse> {
|
||||
// Check database
|
||||
let db_start = Instant::now();
|
||||
let (db_status, media_count) = match state.storage.count_media().await {
|
||||
Ok(count) => ("ok".to_string(), Some(count)),
|
||||
Err(e) => (format!("error: {}", e), None),
|
||||
};
|
||||
let db_latency = db_start.elapsed().as_millis() as u64;
|
||||
|
||||
// Check filesystem
|
||||
let roots = state.storage.list_root_dirs().await.unwrap_or_default();
|
||||
let roots_accessible = roots.iter().filter(|r| r.exists()).count();
|
||||
|
||||
// Get cache stats
|
||||
let cache_stats = state.cache.stats();
|
||||
|
||||
// Get job queue stats
|
||||
let job_stats = state.job_queue.stats().await;
|
||||
|
||||
let overall_status = if db_status == "ok" && roots_accessible == roots.len() {
|
||||
"ok"
|
||||
} else {
|
||||
"degraded"
|
||||
};
|
||||
|
||||
Json(DetailedHealthResponse {
|
||||
status: overall_status.to_string(),
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
uptime_seconds: 0, // Could track server start time
|
||||
database: DatabaseHealth {
|
||||
status: db_status,
|
||||
latency_ms: db_latency,
|
||||
media_count,
|
||||
},
|
||||
filesystem: FilesystemHealth {
|
||||
status: if roots_accessible == roots.len() {
|
||||
"ok"
|
||||
} else {
|
||||
"degraded"
|
||||
}
|
||||
.to_string(),
|
||||
roots_configured: roots.len(),
|
||||
roots_accessible,
|
||||
},
|
||||
cache: CacheHealth {
|
||||
hit_rate: cache_stats.overall_hit_rate(),
|
||||
total_entries: cache_stats.total_entries(),
|
||||
responses_size: cache_stats.responses.size,
|
||||
queries_size: cache_stats.queries.size,
|
||||
media_size: cache_stats.media.size,
|
||||
},
|
||||
jobs: JobsHealth {
|
||||
pending: job_stats.pending,
|
||||
running: job_stats.running,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue