feat(frontend): logs pagination
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
auth,
|
||||
AppContext, auth,
|
||||
db::{self, models::RequestLog},
|
||||
AppContext,
|
||||
};
|
||||
|
||||
use super::models::*;
|
||||
@@ -239,10 +238,7 @@ pub async fn update_rule(
|
||||
{
|
||||
Ok(_) => match db::repositories::rules::find_by_id(&state.db, id).await {
|
||||
Ok(Some(rule)) => Ok(Json(rule.into())),
|
||||
_ => Err((
|
||||
StatusCode::NOT_FOUND,
|
||||
Json(ApiError::new("Rule not found")),
|
||||
)),
|
||||
_ => Err((StatusCode::NOT_FOUND, Json(ApiError::new("Rule not found")))),
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to update rule: {}", e);
|
||||
@@ -321,21 +317,29 @@ pub async fn verify_command(
|
||||
pub struct ListLogsParams {
|
||||
pub method: Option<String>,
|
||||
pub search: Option<String>,
|
||||
pub page: Option<usize>,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
pub async fn list_logs(
|
||||
State(state): State<Arc<AppContext>>,
|
||||
Query(params): Query<ListLogsParams>,
|
||||
) -> Result<Json<Vec<RequestLog>>, (StatusCode, Json<ApiError>)> {
|
||||
match db::repositories::logs::list(
|
||||
&state.db,
|
||||
params.method.as_deref(),
|
||||
params.search.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(logs) => Ok(Json(logs)),
|
||||
Err(e) => {
|
||||
) -> Result<Json<PaginatedRequestLogs>, (StatusCode, Json<ApiError>)> {
|
||||
match (
|
||||
db::repositories::logs::list(
|
||||
&state.db,
|
||||
params.method.as_deref(),
|
||||
params.search.as_deref(),
|
||||
params.limit,
|
||||
params
|
||||
.limit
|
||||
.map(|limit| limit * (params.page.unwrap_or(1) - 1)),
|
||||
)
|
||||
.await,
|
||||
db::repositories::logs::total(&state.db).await,
|
||||
) {
|
||||
(Ok(logs), Ok(total)) => Ok(Json(PaginatedRequestLogs { total, data: logs })),
|
||||
(Err(e), _) | (_, Err(e)) => {
|
||||
error!("Failed to list logs: {}", e);
|
||||
Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
@@ -344,3 +348,24 @@ pub async fn list_logs(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct PaginatedRequestLogs {
|
||||
data: Vec<RequestLog>,
|
||||
total: usize,
|
||||
}
|
||||
|
||||
pub async fn list_methods(
|
||||
State(state): State<Arc<AppContext>>,
|
||||
) -> Result<Json<Vec<String>>, (StatusCode, Json<ApiError>)> {
|
||||
db::repositories::logs::list_methods(&state.db)
|
||||
.await
|
||||
.map(Json)
|
||||
.map_err(|e| {
|
||||
error!("Failed to list logs' methods: {}", e);
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(ApiError::new("Failed to fetch logs' methods")),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ pub fn admin_routes(ctx: Arc<AppContext>) -> Router<Arc<AppContext>> {
|
||||
.route("/api/commands", get(handlers::list_commands))
|
||||
.route("/api/commands/{:id}", post(handlers::verify_command))
|
||||
.route("/api/logs", get(handlers::list_logs))
|
||||
.route("/api/logs/methods", get(handlers::list_methods))
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
ctx,
|
||||
auth_middleware::auth_middleware,
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
use crate::db::models::{InterceptionAction, RequestLog};
|
||||
use sqlx::{QueryBuilder, SqlitePool};
|
||||
|
||||
pub async fn total(pool: &SqlitePool) -> anyhow::Result<usize> {
|
||||
Ok(
|
||||
sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM request_logs;")
|
||||
.fetch_one(pool)
|
||||
.await? as usize,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn list(
|
||||
pool: &SqlitePool,
|
||||
method: Option<&str>,
|
||||
search: Option<&str>,
|
||||
limit: Option<usize>,
|
||||
offset: Option<usize>,
|
||||
) -> anyhow::Result<Vec<RequestLog>> {
|
||||
let mut builder: QueryBuilder<sqlx::Sqlite> =
|
||||
QueryBuilder::new("SELECT * FROM request_logs");
|
||||
let mut builder: QueryBuilder<sqlx::Sqlite> = QueryBuilder::new("SELECT * FROM request_logs");
|
||||
|
||||
if let Some(method_val) = method {
|
||||
builder.push(" WHERE method = ");
|
||||
@@ -27,12 +36,30 @@ pub async fn list(
|
||||
builder.push(")");
|
||||
}
|
||||
|
||||
if let Some(limit) = limit {
|
||||
builder.push("LIMIT ");
|
||||
builder.push(limit);
|
||||
}
|
||||
|
||||
if let Some(offset) = offset {
|
||||
builder.push("OFFSET ");
|
||||
builder.push(offset);
|
||||
}
|
||||
|
||||
builder.push(" ORDER BY created_at DESC");
|
||||
|
||||
let query = builder.build_query_as();
|
||||
Ok(query.fetch_all(pool).await?)
|
||||
}
|
||||
|
||||
pub async fn list_methods(pool: &SqlitePool) -> anyhow::Result<Vec<String>> {
|
||||
Ok(
|
||||
sqlx::query_scalar::<_, String>("SELECT DISTINCT method FROM request_logs ORDER BY method;")
|
||||
.fetch_all(pool)
|
||||
.await?
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
pool: &SqlitePool,
|
||||
method: String,
|
||||
|
||||
Reference in New Issue
Block a user