diff --git a/src/logs.rs b/src/logs.rs index 9f395f2..940bf69 100644 --- a/src/logs.rs +++ b/src/logs.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use sqlx::{PgConnection, Row}; -use strum::{EnumDiscriminants, IntoStaticStr, VariantNames}; +use strum::{EnumDiscriminants, EnumIter, IntoStaticStr, VariantNames}; use uuid::Uuid; use crate::{database::DatabaseError, users::User, web::icons}; @@ -38,10 +38,25 @@ impl LogEntry { Ok(log) } - pub async fn total_count(conn: &mut PgConnection) -> Result { - Ok(sqlx::query_scalar("SELECT COUNT(*) FROM logs") - .fetch_one(&mut *conn) - .await?) + pub async fn count( + conn: &mut PgConnection, + action_type: Option, + ) -> Result { + let count = match action_type { + Some(at) => { + let atstr: &'static str = at.into(); + sqlx::query_scalar("SELECT COUNT(*) FROM logs WHERE actiontype = $1") + .bind(atstr) + .fetch_one(&mut *conn) + .await? + } + None => { + sqlx::query_scalar("SELECT COUNT(*) FROM logs") + .fetch_one(&mut *conn) + .await? + } + }; + Ok(count) } pub async fn get_chronological_offset( @@ -81,7 +96,7 @@ impl LogEntry { } #[derive(Debug, IntoStaticStr, Serialize, Deserialize, VariantNames, EnumDiscriminants)] -#[strum_discriminants(derive(IntoStaticStr, Serialize, Deserialize))] +#[strum_discriminants(derive(EnumIter, IntoStaticStr, Serialize, Deserialize))] #[strum_discriminants(name(LogActionDiscriminant))] pub enum LogAction { Initialize, @@ -209,7 +224,7 @@ impl LogActionDiscriminant { LAD::Initialize => "Mnemosyne Initialization", LAD::RegenInfradmin => "Infradmin Regeneration", LAD::CreateUser => "User Creation", - LAD::ManuallyChangeUsersPassword => "Another User's Password Change", + LAD::ManuallyChangeUsersPassword => "Password Override", LAD::CreateTag => "Tag Creation", LAD::RenameTag => "Tag Rename", LAD::DeleteTag => "Tag Deletion", diff --git a/src/web/pages/logs.rs b/src/web/pages/logs.rs index 5c963ca..97179a9 100644 --- a/src/web/pages/logs.rs +++ b/src/web/pages/logs.rs @@ -4,6 +4,7 @@ use axum::{ }; use maud::{PreEscaped, html}; use serde::Deserialize; +use strum::IntoEnumIterator; use crate::{ MnemoState, @@ -35,7 +36,7 @@ pub async fn page( let offset = (page - 1) * per_page; 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::count(&mut *tx, query.action).await?; let total_pages = (total_logs as f64 / per_page as f64).ceil() as i64; Ok(base( @@ -52,7 +53,22 @@ pub async fn page( } } // abcdefghijklmnopqrstuvwxyz - div class="w-full border border-neutral-200/25 rounded grid grid-cols-[auto_auto_1fr]" { + div class="mb-4 flex flex-wrap gap-2 items-center justify-between" { + div class="text-sm text-neutral-400" { + "Showing " (total_logs) " logs" + } + select + class="bg-neutral-900 border border-neutral-200/25 rounded px-2 py-1 text-sm text-neutral-200" + onchange="window.location.search = this.value ? '?action=' + this.value : ''" + { + option value="" { "All Actions" } + @for action in LogActionDiscriminant::iter() { + @let act_str: &'static str = action.into(); + option value=(act_str) selected[query.action == Some(action)] { (action.human_readable()) } + } + } + } + div class="w-full overflow-x-auto 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)] { div class="p-2 flex gap-1 font-semibold border-b border-neutral-200/25" { span class="text-neutral-500 scale-[.8]" {(PreEscaped(ico))} @@ -75,9 +91,10 @@ pub async fn page( div class="p-2 font-light" style=(s) {(log.data.get_humanreadable_payload())} } } - div class="flex justify-between items-center my-4 text-neutral-400" { + @let action_q = query.action.map(|a| { let s: &'static str = a.into(); format!("&action={s}") }).unwrap_or_default(); + div class="flex flex-wrap gap-2 justify-between items-center my-4 text-neutral-400" { @if page > 1 { - a href=(format!("/logs?page={}", (page - 1).max(1))) class="px-4 py-2 border border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-200/5 hover:bg-neutral-200/15 rounded" { + a href=(format!("/logs?page={}{}", (page - 1).max(1), action_q)) class="px-4 py-2 border border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-200/5 hover:bg-neutral-200/15 rounded" { "Previous" } } @else { @@ -89,7 +106,7 @@ pub async fn page( } @if page < total_pages { - a href=(format!("/logs?page={}", page + 1)) class="px-4 py-2 border border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-200/5 hover:bg-neutral-200/15 rounded" { + a href=(format!("/logs?page={}{}", page + 1, action_q)) class="px-4 py-2 border border-neutral-200/25 hover:border-neutral-200/45 bg-neutral-200/5 hover:bg-neutral-200/15 rounded" { "Next" } } @else {