logs filtering functionality

This commit is contained in:
2026-04-27 16:11:35 +02:00
parent f239de1ca0
commit a282e4f445
6 changed files with 76 additions and 18 deletions

View File

@@ -1,9 +1,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{PgConnection, Row}; use sqlx::{PgConnection, Row};
use strum::IntoStaticStr; use strum::{EnumDiscriminants, IntoStaticStr, VariantNames};
use uuid::Uuid; use uuid::Uuid;
use crate::{database::DatabaseError, users::User}; use crate::{database::DatabaseError, users::User, web::icons};
#[derive(Debug)] #[derive(Debug)]
pub struct LogEntry { pub struct LogEntry {
@@ -46,15 +46,26 @@ impl LogEntry {
pub async fn get_chronological_offset( pub async fn get_chronological_offset(
conn: &mut PgConnection, conn: &mut PgConnection,
action_type: Option<LogActionDiscriminant>,
offset: i64, offset: i64,
limit: i64, limit: i64,
) -> Result<Vec<LogEntry>, DatabaseError> { ) -> Result<Vec<LogEntry>, DatabaseError> {
let rows = let mut qstr = String::from("SELECT id, actor, payload FROM logs ");
sqlx::query("SELECT id, actor, payload FROM logs ORDER BY id DESC LIMIT $1 OFFSET $2") if action_type.is_some() {
.bind(limit) qstr += "WHERE actiontype = $3 "
.bind(offset) }
.fetch_all(&mut *conn) qstr += "ORDER BY id DESC LIMIT $1 OFFSET $2";
.await?;
let q = sqlx::query(&qstr).bind(limit).bind(offset);
let rows = match action_type {
Some(at) => {
let atstr: &'static str = at.into();
q.bind(atstr).fetch_all(&mut *conn).await?
}
None => q.fetch_all(&mut *conn).await?,
};
let mut entries = Vec::new(); let mut entries = Vec::new();
for row in rows { for row in rows {
let payload: serde_json::Value = row.try_get("payload")?; let payload: serde_json::Value = row.try_get("payload")?;
@@ -69,10 +80,9 @@ impl LogEntry {
} }
} }
// #[derive(Debug, thiserror::Error)] #[derive(Debug, IntoStaticStr, Serialize, Deserialize, VariantNames, EnumDiscriminants)]
// pub enum LogError {} #[strum_discriminants(derive(IntoStaticStr, Serialize, Deserialize))]
#[strum_discriminants(name(LogActionDiscriminant))]
#[derive(Debug, IntoStaticStr, Serialize, Deserialize)]
pub enum LogAction { pub enum LogAction {
Initialize, Initialize,
RegenInfradmin, RegenInfradmin,
@@ -191,3 +201,45 @@ impl LogAction {
} }
} }
} }
impl LogActionDiscriminant {
pub fn human_readable(&self) -> &'static str {
use LogActionDiscriminant as LAD;
match self {
LAD::Initialize => "Mnemosyne Initialization",
LAD::RegenInfradmin => "Infradmin Regeneration",
LAD::CreateUser => "User Creation",
LAD::ManuallyChangeUsersPassword => "Another User's Password Change",
LAD::CreateTag => "Tag Creation",
LAD::RenameTag => "Tag Rename",
LAD::DeleteTag => "Tag Deletion",
LAD::CreatePerson => "Person Creation",
LAD::ChangeUserHandle => "User Handle Change",
LAD::AddPersonName => "Person Name Addition",
LAD::DeletePersonName => "Person Name Deletion",
LAD::SetPersonPrimaryName => "Person Primary Name Set",
LAD::CreateQuote => "Quote Creation",
LAD::ManuallyRevokeSession => "Manual Session Revocation",
}
}
pub fn icon(&self) -> &'static str {
use LogActionDiscriminant as LAD;
match self {
LAD::Initialize => icons::LINE_DOT_RIGHT_HORIZONTAL,
// LAD::RegenInfradmin =>
// LAD::CreateUser =>
// LAD::ManuallyChangeUsersPassword =>
// LAD::CreateTag =>
// LAD::RenameTag =>
// LAD::DeleteTag =>
// LAD::CreatePerson =>
// LAD::ChangeUserHandle =>
// LAD::AddPersonName =>
// LAD::DeletePersonName =>
// LAD::SetPersonPrimaryName =>
// LAD::CreateQuote =>
// LAD::ManuallyRevokeSession =>
_ => icons::GIT_COMMIT_VERTICAL,
}
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-git-commit-vertical-icon lucide-git-commit-vertical"><path d="M12 3v6"/><circle cx="12" cy="12" r="3"/><path d="M12 15v6"/></svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-line-dot-right-horizontal-icon lucide-line-dot-right-horizontal"><path d="M 3 12 L 15 12"/><circle cx="18" cy="12" r="3"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -8,8 +8,10 @@ pub const CLOCK: &str = include_str!("clock.svg");
pub const CONTACT: &str = include_str!("contact.svg"); pub const CONTACT: &str = include_str!("contact.svg");
pub const EYE: &str = include_str!("eye.svg"); pub const EYE: &str = include_str!("eye.svg");
pub const FILE_IMAGE: &str = include_str!("file-image.svg"); pub const FILE_IMAGE: &str = include_str!("file-image.svg");
pub const GIT_COMMIT_VERTICAL: &str = include_str!("git-commit-vertical.svg");
pub const INFO: &str = include_str!("info.svg"); pub const INFO: &str = include_str!("info.svg");
pub const LAYOUT_DASHBOARD: &str = include_str!("layout-dashboard.svg"); pub const LAYOUT_DASHBOARD: &str = include_str!("layout-dashboard.svg");
pub const LINE_DOT_RIGHT_HORIZONTAL: &str = include_str!("line-dot-right-horizontal.svg");
pub const LOG_OUT: &str = include_str!("log-out.svg"); pub const LOG_OUT: &str = include_str!("log-out.svg");
pub const MAP_PIN: &str = include_str!("map-pin.svg"); pub const MAP_PIN: &str = include_str!("map-pin.svg");
pub const PEN: &str = include_str!("pen.svg"); pub const PEN: &str = include_str!("pen.svg");

View File

@@ -3,7 +3,7 @@ use axum::{ Router, http::header, routing::get};
use crate::MnemoState; use crate::MnemoState;
mod components; mod components;
mod icons; pub mod icons;
mod pages; mod pages;
pub const CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/styles.css")); pub const CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/styles.css"));

View File

@@ -8,19 +8,20 @@ use serde::Deserialize;
use crate::{ use crate::{
MnemoState, MnemoState,
error::CompositeError, error::CompositeError,
logs::LogEntry, logs::{LogActionDiscriminant, LogEntry},
users::{User, auth::UserAuthenticate}, users::{User, auth::UserAuthenticate},
web::{components::nav::nav, icons, pages::base}, web::{components::nav::nav, icons, pages::base},
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct PageQuery { pub struct LogsPageQuery {
page: Option<i64>, page: Option<i64>,
action: Option<LogActionDiscriminant>,
} }
pub async fn page( pub async fn page(
State(state): State<MnemoState>, State(state): State<MnemoState>,
Query(query): Query<PageQuery>, Query(query): Query<LogsPageQuery>,
req: Request, req: Request,
) -> Result<Response, CompositeError> { ) -> Result<Response, CompositeError> {
let mut tx = state.pool.acquire().await?; let mut tx = state.pool.acquire().await?;
@@ -33,7 +34,7 @@ pub async fn page(
let per_page = 20; let per_page = 20;
let offset = (page - 1) * per_page; let offset = (page - 1) * per_page;
let logs = LogEntry::get_chronological_offset(&mut *tx, offset, per_page).await?; let logs = LogEntry::get_chronological_offset(&mut *tx, query.action, offset, per_page).await?;
let total_logs = LogEntry::total_count(&mut *tx).await?; let total_logs = LogEntry::total_count(&mut *tx).await?;
let total_pages = (total_logs as f64 / per_page as f64).ceil() as i64; let total_pages = (total_logs as f64 / per_page as f64).ceil() as i64;
@@ -50,6 +51,7 @@ pub async fn page(
span class="text-2xl font-semibold font-lora" {"Logs"} span class="text-2xl font-semibold font-lora" {"Logs"}
} }
} }
div class="w-full border border-neutral-200/25 rounded grid grid-cols-[auto_auto_1fr]" { div class="w-full border border-neutral-200/25 rounded grid grid-cols-[auto_auto_1fr]" {
@for (txt, ico) in [("Timestamp", icons::CLOCK), ("Actor", icons::USER), ("Action", icons::PEN)] { @for (txt, ico) in [("Timestamp", icons::CLOCK), ("Actor", icons::USER), ("Action", icons::PEN)] {
div class="p-2 flex gap-1 font-semibold border-b border-neutral-200/25" { div class="p-2 flex gap-1 font-semibold border-b border-neutral-200/25" {