aboutsummaryrefslogtreecommitdiff
path: root/ui/src/admin/log.rs
blob: d68be3ec7b0ba5c8cca9ac4e74be3fd15d3472cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
    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) 2025 metamuffin <metamuffin.org>
*/

use crate::Page;
use jellycommon::{
    api::{LogLevel, LogLine},
    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, "<span style=color:#{:02x}{:02x}{:02x}>", r, g, b).unwrap()
    }
    pub fn reset_color(&mut self) {
        if self.color {
            write!(self.s, "</span>").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!(),
                    });
                }
                _ => (),
            },
            _ => (),
        }
    }
}