Files
mnemosyne/src/web/pages/logs.rs
2026-04-22 00:09:52 +02:00

105 lines
4.5 KiB
Rust

use axum::{
extract::{Query, Request, State},
response::{IntoResponse, Redirect, Response},
};
use maud::{PreEscaped, html};
use serde::Deserialize;
use crate::{
MnemoState,
error::CompositeError,
logs::LogEntry,
users::{User, auth::UserAuthenticate},
web::{components::nav::nav, icons, pages::base},
};
#[derive(Deserialize)]
pub struct PageQuery {
page: Option<i64>,
}
pub async fn page(
State(state): State<MnemoState>,
Query(query): Query<PageQuery>,
req: Request,
) -> Result<Response, CompositeError> {
let mut tx = state.pool.acquire().await?;
let u = match User::authenticate(&mut *tx, req.headers()).await? {
Some(u) => u,
None => return Ok(Redirect::to(&format!("/login?r={}", req.uri().path())).into_response()),
};
let page = query.page.unwrap_or(1).max(1);
let per_page = 20;
let offset = (page - 1) * per_page;
let logs = LogEntry::get_chronological_offset(&mut *tx, offset, per_page).await?;
let total_logs = LogEntry::total_count(&mut *tx).await?;
let total_pages = (total_logs as f64 / per_page as f64).ceil() as i64;
Ok(base(
"Logs | Mnemosyne",
html!(
(nav(Some(&u), req.uri().path()))
@if true {//let Ok(true) = u.has_permission(&mut *tx, Permission::BrowseServerLogs) {
div class="max-w-4xl mx-auto px-2" {
div class="my-4" {
p class="flex items-center gap-2" {
span class="text-neutral-500" {(PreEscaped(icons::CLIPBOARD_CLOCK))}
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]" {
@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))}
(txt)
}
}
@for (idx, log) in logs.iter().enumerate() {
@let s = if idx % 2 == 0 {"background-color: #e5e5e50b"} else {""};
div class="p-2 font-light" style=(s) {
(log.id.get_timestamp()
.map(|ts| {
let (secs, nanos) = ts.to_unix();
chrono::DateTime::from_timestamp(secs as i64, nanos)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "invalid date".to_string())
})
.unwrap_or_else(|| "no timestamp".to_string()))
}
div class="p-2 font-light" style=(s) {(log.actor.handle)}
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" {
@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" {
"Previous"
}
} @else {
div {}
}
span {
"Page " (page) " of " (total_pages)
}
@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" {
"Next"
}
} @else {
div {}
}
}
}
} @else {
p class="text-center p-2" {"You must have permission to view logs."}
}
),
)
.into_response())
}