logs filtering functionality
This commit is contained in:
76
src/logs.rs
76
src/logs.rs
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1
src/web/icons/git-commit-vertical.svg
Normal file
1
src/web/icons/git-commit-vertical.svg
Normal 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 |
1
src/web/icons/line-dot-right-horizontal.svg
Normal file
1
src/web/icons/line-dot-right-horizontal.svg
Normal 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 |
@@ -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");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use axum::{ Router, http::header, routing::get};
|
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"));
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
Reference in New Issue
Block a user