From 5caf1f1db721d6dee2ddb5d0613e8c9914ccf879 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Fri, 20 Feb 2026 01:23:38 +0100 Subject: admin log --- ui/src/components/admin_log.rs | 117 +++++++++++++++++++++++++++++++++++++ ui/src/components/mod.rs | 1 + ui/src/lib.rs | 5 +- ui/src/old/admin/log.rs | 127 ----------------------------------------- ui/src/scaffold.rs | 4 +- 5 files changed, 124 insertions(+), 130 deletions(-) create mode 100644 ui/src/components/admin_log.rs delete mode 100644 ui/src/old/admin/log.rs (limited to 'ui/src') diff --git a/ui/src/components/admin_log.rs b/ui/src/components/admin_log.rs new file mode 100644 index 0000000..abec7fe --- /dev/null +++ b/ui/src/components/admin_log.rs @@ -0,0 +1,117 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin +*/ + +use jellycommon::{ + internal::{LogLevel, LogLine}, + routes::u_admin_log, +}; +use markup::raw; +use std::fmt::Write; + +markup::define! { + ServerLogPage<'a>(warnonly: bool, messages: &'a [String]) { + h1 { "Server Log" } + a[href=u_admin_log(!warnonly)] { @if *warnonly { "Show everything" } else { "Show only warnings" }} + code.log[id="log"] { + table { @for e in *messages { + @raw(e) + }} + } + } + ServerLogLine<'a>(e: &'a LogLine) { + tr[class=format!("level-{}", e.level).to_ascii_lowercase()] { + td.time { @e.time } + td.loglevel { @format_level(e.level) } + td.module { @e.module } + td { @markup::raw(vt100_to_html(&e.message)) } + } + } +} + +pub fn render_log_line(line: &LogLine) -> String { + ServerLogLine { e: line }.to_string() +} + +fn vt100_to_html(s: &str) -> String { + let mut out = HtmlOut::default(); + let mut st = vte::Parser::new(); + st.advance(&mut out, s.as_bytes()); + out.s +} + +fn format_level(level: LogLevel) -> impl markup::Render { + let (s, c) = match level { + LogLevel::Debug => ("DEBUG", "blue"), + LogLevel::Error => ("ERROR", "red"), + LogLevel::Warn => ("WARN", "yellow"), + LogLevel::Info => ("INFO", "green"), + LogLevel::Trace => ("TRACE", "lightblue"), + }; + markup::new! { span[style=format!("color:{c}")] {@s} } +} + +#[derive(Default)] +pub struct HtmlOut { + s: String, + color: bool, +} +impl HtmlOut { + pub fn set_color(&mut self, [r, g, b]: [u8; 3]) { + self.reset_color(); + self.color = true; + write!(self.s, "", r, g, b).unwrap() + } + pub fn reset_color(&mut self) { + if self.color { + write!(self.s, "").unwrap(); + self.color = false; + } + } +} +impl vte::Perform for HtmlOut { + fn print(&mut self, c: char) { + match c { + 'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' => self.s.push(c), + x => write!(self.s, "&#{};", x as u32).unwrap(), + } + } + fn execute(&mut self, _byte: u8) {} + fn hook(&mut self, _params: &vte::Params, _i: &[u8], _ignore: bool, _a: char) {} + fn put(&mut self, _byte: u8) {} + fn unhook(&mut self) {} + fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {} + fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} + fn csi_dispatch( + &mut self, + params: &vte::Params, + _intermediates: &[u8], + _ignore: bool, + action: char, + ) { + let mut k = params.iter(); + #[allow(clippy::single_match)] + match action { + 'm' => match k.next().unwrap_or(&[0]).first().unwrap_or(&0) { + c @ (30..=37 | 40..=47) => { + let c = if *c >= 40 { *c - 10 } else { *c }; + self.set_color(match c { + 30 => [0, 0, 0], + 31 => [255, 0, 0], + 32 => [0, 255, 0], + 33 => [255, 255, 0], + 34 => [0, 0, 255], + 35 => [255, 0, 255], + 36 => [0, 255, 255], + 37 => [255, 255, 255], + _ => unreachable!(), + }); + } + _ => (), + }, + _ => (), + } + } +} diff --git a/ui/src/components/mod.rs b/ui/src/components/mod.rs index c3ba9b0..15b2ae2 100644 --- a/ui/src/components/mod.rs +++ b/ui/src/components/mod.rs @@ -12,6 +12,7 @@ pub mod node_list; pub mod node_page; pub mod props; pub mod stats; +pub mod admin_log; use crate::{ RenderInfo, diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 63a2d07..f32657d 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -10,9 +10,12 @@ mod scaffold; pub use jellyui_client_scripts::*; pub use jellyui_client_style::*; +pub use components::admin_log::ServerLogPage; +pub use components::admin_log::render_log_line; pub use jellyui_locale::tr; +pub use scaffold::Scaffold; -use crate::{components::View, scaffold::Scaffold}; +use crate::components::View; use jellycommon::{jellyobject::Object, *}; use serde::{Deserialize, Serialize}; diff --git a/ui/src/old/admin/log.rs b/ui/src/old/admin/log.rs deleted file mode 100644 index 637158f..0000000 --- a/ui/src/old/admin/log.rs +++ /dev/null @@ -1,127 +0,0 @@ -/* - This file is part of jellything (https://codeberg.org/metamuffin/jellything) - which is licensed under the GNU Affero General Public License (version 3); see /COPYING. - Copyright (C) 2026 metamuffin -*/ - -use crate::Page; -use jellycommon::routes::u_admin_log; -use markup::raw; -use std::fmt::Write; - -impl Page for ServerLogPage<'_> { - fn title(&self) -> String { - "Server Log".to_string() - } - fn class(&self) -> Option<&'static str> { - Some("admin_log") - } - fn to_render(&self) -> markup::DynRender<'_> { - markup::new!(@self) - } -} - -markup::define! { - ServerLogPage<'a>(warnonly: bool, messages: &'a [String]) { - h1 { "Server Log" } - a[href=u_admin_log(!warnonly)] { @if *warnonly { "Show everything" } else { "Show only warnings" }} - code.log[id="log"] { - table { @for e in *messages { - @raw(e) - }} - } - } - ServerLogLine<'a>(e: &'a LogLine) { - tr[class=format!("level-{}", e.level).to_ascii_lowercase()] { - td.time { @e.time.to_rfc3339() } - td.loglevel { @format_level(e.level) } - td.module { @e.module } - td { @markup::raw(vt100_to_html(&e.message)) } - } - } -} - -pub fn render_log_line(line: &LogLine) -> String { - ServerLogLine { e: line }.to_string() -} - -fn vt100_to_html(s: &str) -> String { - let mut out = HtmlOut::default(); - let mut st = vte::Parser::new(); - st.advance(&mut out, s.as_bytes()); - out.s -} - -fn format_level(level: LogLevel) -> impl markup::Render { - let (s, c) = match level { - LogLevel::Debug => ("DEBUG", "blue"), - LogLevel::Error => ("ERROR", "red"), - LogLevel::Warn => ("WARN", "yellow"), - LogLevel::Info => ("INFO", "green"), - LogLevel::Trace => ("TRACE", "lightblue"), - }; - markup::new! { span[style=format!("color:{c}")] {@s} } -} - -#[derive(Default)] -pub struct HtmlOut { - s: String, - color: bool, -} -impl HtmlOut { - pub fn set_color(&mut self, [r, g, b]: [u8; 3]) { - self.reset_color(); - self.color = true; - write!(self.s, "", r, g, b).unwrap() - } - pub fn reset_color(&mut self) { - if self.color { - write!(self.s, "").unwrap(); - self.color = false; - } - } -} -impl vte::Perform for HtmlOut { - fn print(&mut self, c: char) { - match c { - 'a'..='z' | 'A'..='Z' | '0'..='9' | ' ' => self.s.push(c), - x => write!(self.s, "&#{};", x as u32).unwrap(), - } - } - fn execute(&mut self, _byte: u8) {} - fn hook(&mut self, _params: &vte::Params, _i: &[u8], _ignore: bool, _a: char) {} - fn put(&mut self, _byte: u8) {} - fn unhook(&mut self) {} - fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {} - fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} - fn csi_dispatch( - &mut self, - params: &vte::Params, - _intermediates: &[u8], - _ignore: bool, - action: char, - ) { - let mut k = params.iter(); - #[allow(clippy::single_match)] - match action { - 'm' => match k.next().unwrap_or(&[0]).first().unwrap_or(&0) { - c @ (30..=37 | 40..=47) => { - let c = if *c >= 40 { *c - 10 } else { *c }; - self.set_color(match c { - 30 => [0, 0, 0], - 31 => [255, 0, 0], - 32 => [0, 255, 0], - 33 => [255, 255, 0], - 34 => [0, 0, 255], - 35 => [255, 0, 255], - 36 => [0, 255, 255], - 37 => [255, 255, 255], - _ => unreachable!(), - }); - } - _ => (), - }, - _ => (), - } - } -} diff --git a/ui/src/scaffold.rs b/ui/src/scaffold.rs index 5a70460..f24550d 100644 --- a/ui/src/scaffold.rs +++ b/ui/src/scaffold.rs @@ -7,8 +7,8 @@ use crate::RenderInfo; use jellycommon::{ routes::{ - u_account_login, u_account_logout, u_account_register, u_account_settings, - u_admin_dashboard, u_home, u_items, u_node_slug, u_search, u_stats, + u_account_login, u_account_logout, u_account_settings, u_admin_dashboard, u_home, u_items, + u_node_slug, u_search, u_stats, }, user::{USER_ADMIN, USER_NAME}, }; -- cgit v1.3