logs stub
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::{
|
|||||||
quotes::QuoteError,
|
quotes::QuoteError,
|
||||||
tags::TagError,
|
tags::TagError,
|
||||||
users::{UserError, auth::AuthError, sessions::SessionError},
|
users::{UserError, auth::AuthError, sessions::SessionError},
|
||||||
|
web::RedirectViaError,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
@@ -83,5 +84,6 @@ composite_from!(
|
|||||||
TagError,
|
TagError,
|
||||||
PersonError,
|
PersonError,
|
||||||
QuoteError,
|
QuoteError,
|
||||||
DatabaseError
|
DatabaseError,
|
||||||
|
RedirectViaError
|
||||||
);
|
);
|
||||||
|
|||||||
26
src/logs.rs
Normal file
26
src/logs.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::users::User;
|
||||||
|
|
||||||
|
pub struct LogEntry {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub actor: User,
|
||||||
|
pub data: LogAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LogAction {
|
||||||
|
Initialize,
|
||||||
|
RegenInfradmin,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LogAction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
LogAction::Initialize => write!(f, "Initialized Mnemosyne."),
|
||||||
|
LogAction::RegenInfradmin => write!(f, "Regenerated the Infradmin account."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use tokio::net::TcpListener;
|
|||||||
mod api;
|
mod api;
|
||||||
mod config;
|
mod config;
|
||||||
mod database;
|
mod database;
|
||||||
|
mod logs;
|
||||||
mod persons;
|
mod persons;
|
||||||
mod quotes;
|
mod quotes;
|
||||||
mod tags;
|
mod tags;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pub enum Permission {
|
|||||||
RenameTags,
|
RenameTags,
|
||||||
DeleteTags,
|
DeleteTags,
|
||||||
ChangePersonPrimaryName,
|
ChangePersonPrimaryName,
|
||||||
|
BrowseServerLogs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const LINKS: &[(&str, &str, &str, bool)] = &[
|
|||||||
("Persons", "/persons", icons::CONTACT, false),
|
("Persons", "/persons", icons::CONTACT, false),
|
||||||
("Tags", "/tags", icons::TAG, false),
|
("Tags", "/tags", icons::TAG, false),
|
||||||
("Users", "/users", icons::USERS, true),
|
("Users", "/users", icons::USERS, true),
|
||||||
("Logs", "#logs", icons::CLIPBOARD_CLOCK, true),
|
("Logs", "/logs", icons::CLIPBOARD_CLOCK, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn nav(user: Option<&User>, uri: &str) -> Markup {
|
pub fn nav(user: Option<&User>, uri: &str) -> Markup {
|
||||||
|
|||||||
1
src/web/icons/clock.svg
Normal file
1
src/web/icons/clock.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-clock-icon lucide-clock"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||||
|
After Width: | Height: | Size: 289 B |
@@ -2,6 +2,7 @@
|
|||||||
pub const ARROW_RIGHT: &str = include_str!("arrow-right.svg");
|
pub const ARROW_RIGHT: &str = include_str!("arrow-right.svg");
|
||||||
pub const CALENDAR_1: &str = include_str!("calendar-1.svg");
|
pub const CALENDAR_1: &str = include_str!("calendar-1.svg");
|
||||||
pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg");
|
pub const CLIPBOARD_CLOCK: &str = include_str!("clipboard-clock.svg");
|
||||||
|
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");
|
||||||
@@ -9,6 +10,7 @@ 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 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 QUOTE: &str = include_str!("quote.svg");
|
pub const QUOTE: &str = include_str!("quote.svg");
|
||||||
pub const SCROLL_TEXT: &str = include_str!("scroll-text.svg");
|
pub const SCROLL_TEXT: &str = include_str!("scroll-text.svg");
|
||||||
pub const SERVER: &str = include_str!("server.svg");
|
pub const SERVER: &str = include_str!("server.svg");
|
||||||
|
|||||||
1
src/web/icons/pen.svg
Normal file
1
src/web/icons/pen.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-pen-icon lucide-pen"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/></svg>
|
||||||
|
After Width: | Height: | Size: 370 B |
@@ -1,4 +1,7 @@
|
|||||||
use axum::Router;
|
use axum::{
|
||||||
|
Router,
|
||||||
|
response::{IntoResponse, Redirect, Response},
|
||||||
|
};
|
||||||
use tower_http::services::ServeFile;
|
use tower_http::services::ServeFile;
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
@@ -10,3 +13,10 @@ pub fn web_router() -> Router {
|
|||||||
.route_service("/styles.css", ServeFile::new("src/web/styles.css"))
|
.route_service("/styles.css", ServeFile::new("src/web/styles.css"))
|
||||||
.merge(pages::pages())
|
.merge(pages::pages())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RedirectViaError(Redirect);
|
||||||
|
impl IntoResponse for RedirectViaError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
self.0.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
83
src/web/pages/logs.rs
Normal file
83
src/web/pages/logs.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::Request,
|
||||||
|
response::{IntoResponse, Redirect, Response},
|
||||||
|
};
|
||||||
|
use maud::{PreEscaped, html};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::CompositeError,
|
||||||
|
logs::{LogAction, LogEntry},
|
||||||
|
users::{User, auth::UserAuthenticate, permissions::Permission},
|
||||||
|
web::{RedirectViaError, components::nav::nav, icons, pages::base},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn page(req: Request) -> Result<Response, CompositeError> {
|
||||||
|
let u = User::authenticate(req.headers())?
|
||||||
|
.ok_or(RedirectViaError(Redirect::to("/login?re=/logs")))?;
|
||||||
|
let logs: Vec<LogEntry> = vec![
|
||||||
|
LogEntry {
|
||||||
|
id: Uuid::now_v7(),
|
||||||
|
actor: User::get_by_id(Uuid::nil()).unwrap(),
|
||||||
|
data: LogAction::Initialize,
|
||||||
|
},
|
||||||
|
LogEntry {
|
||||||
|
id: Uuid::now_v7(),
|
||||||
|
actor: User::get_by_id(Uuid::nil()).unwrap(),
|
||||||
|
data: LogAction::RegenInfradmin,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(base(
|
||||||
|
"Persons | Mnemosyne",
|
||||||
|
html!(
|
||||||
|
(nav(Some(&u), req.uri().path()))
|
||||||
|
|
||||||
|
@if let Ok(true) = u.has_permission(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"}
|
||||||
|
}
|
||||||
|
p class="text-neutral-500 text-sm font-light" {
|
||||||
|
"Work in progress."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)}
|
||||||
|
}
|
||||||
|
@if true {
|
||||||
|
div class="p-2 col-span-3 text-center font-light text-neutral-400" {"You've reached the end of all logs."}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
p class="text-center p-2" {"You must have permission to view this page."}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use maud::{DOCTYPE, Markup, html};
|
|||||||
pub mod dashboard;
|
pub mod dashboard;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod logs;
|
||||||
pub mod persons;
|
pub mod persons;
|
||||||
pub mod tags;
|
pub mod tags;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
@@ -28,6 +29,7 @@ pub fn pages() -> Router {
|
|||||||
.route("/tags/create", post(tags::create))
|
.route("/tags/create", post(tags::create))
|
||||||
.route("/persons", get(persons::page))
|
.route("/persons", get(persons::page))
|
||||||
.route("/persons/create", post(persons::create))
|
.route("/persons/create", post(persons::create))
|
||||||
|
.route("/logs", get(logs::page))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base(title: &str, inner: Markup) -> Markup {
|
pub fn base(title: &str, inner: Markup) -> Markup {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user