use axum::{
body::Body,
extract::{Path, Query},
http::{header, HeaderValue, StatusCode},
middleware,
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use crate::services::basic_auth_service::axum_basic_auth;
use crate::services::http_response::DashboardResponse;
use crate::services::{
cron_service::{self, CronActionRequest},
metrics_service::{self, MetricsQuery},
};
use crate::utils::pagination::PaginationQuery;
impl IntoResponse for DashboardResponse {
fn into_response(self) -> Response {
let status =
StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
let content_type = HeaderValue::from_str(&self.content_type)
.unwrap_or_else(|_| HeaderValue::from_static("application/octet-stream"));
let mut builder = Response::builder()
.status(status)
.header(header::CONTENT_TYPE, content_type);
for (name, value) in &self.headers {
builder = builder.header(name.as_str(), value.as_str());
}
builder
.body(Body::from(self.body))
.unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())
}
}
async fn render_metrics(Query(query): Query<MetricsQuery>) -> DashboardResponse {
metrics_service::render_metrics(query).await
}
async fn render_metrics_for_queue(
Path(queue): Path<String>,
Query(query): Query<PaginationQuery>,
) -> DashboardResponse {
metrics_service::render_metrics_for_queue(queue, query).await
}
async fn render_dead_jobs(Query(query): Query<PaginationQuery>) -> DashboardResponse {
metrics_service::render_dead_jobs(query).await
}
async fn render_delayed_jobs(Query(query): Query<PaginationQuery>) -> DashboardResponse {
metrics_service::render_delayed_jobs(query).await
}
async fn render_scheduled_jobs(Query(query): Query<PaginationQuery>) -> DashboardResponse {
metrics_service::render_scheduled_jobs(query).await
}
async fn export_queue_csv(Path(queue): Path<String>) -> DashboardResponse {
metrics_service::export_queue_csv(queue).await
}
async fn get_metrics_summary() -> DashboardResponse {
metrics_service::get_metrics_summary().await
}
async fn job_action(Json(payload): Json<serde_json::Value>) -> DashboardResponse {
metrics_service::job_action(payload).await
}
async fn render_failed_jobs(Query(query): Query<PaginationQuery>) -> DashboardResponse {
metrics_service::render_failed_jobs(query).await
}
async fn render_retry_jobs(Query(query): Query<PaginationQuery>) -> DashboardResponse {
metrics_service::render_retry_jobs(query).await
}
async fn render_cron_jobs() -> DashboardResponse {
cron_service::render_cron_jobs().await
}
async fn cron_action(Json(payload): Json<CronActionRequest>) -> DashboardResponse {
cron_service::cron_action(payload).await
}
pub fn qrush_metrics_router() -> Router {
let metrics = Router::new()
.route("/health", get(|| async { "healthy" }))
.route("/", get(render_metrics))
.route("/queues/{queue}", get(render_metrics_for_queue))
.route("/extras/dead", get(render_dead_jobs))
.route("/extras/delayed", get(render_delayed_jobs))
.route("/extras/scheduled", get(render_scheduled_jobs))
.route("/queues/{queue}/export", get(export_queue_csv))
.route("/extras/summary", get(get_metrics_summary))
.route("/jobs/action", post(job_action))
.route("/extras/cron", get(render_cron_jobs))
.route("/cron/action", post(cron_action))
.route("/extras/failed", get(render_failed_jobs))
.route("/extras/retry", get(render_retry_jobs))
.layer(middleware::from_fn(axum_basic_auth));
Router::new().nest("/metrics", metrics)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn error_status_and_content_type_map() {
let resp = DashboardResponse::json_status(404, &serde_json::json!({"error": "nope"}))
.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
assert_eq!(
resp.headers().get("content-type").unwrap(),
"application/json"
);
}
#[test]
fn csv_disposition_header_maps() {
let resp = DashboardResponse::csv(b"a,b\n".to_vec(), "queue_x.csv").into_response();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get("content-type").unwrap(), "text/csv");
assert!(resp
.headers()
.get("content-disposition")
.unwrap()
.to_str()
.unwrap()
.contains("queue_x.csv"));
}
}