prest 0.5.1

Progressive RESTful framework
Documentation
use crate::*;
use chrono::{TimeDelta, Utc};
use host::{system_info::SystemStat, LOGS_INFO_NAME, LOGS_TRACES_NAME};

pub(crate) async fn full() -> Result<Markup> {
    const MAX_COUNT: usize = 100;
    let max = Utc::now().naive_utc();
    let min = max - TimeDelta::try_hours(24).unwrap();
    let records = SystemStat::find_in_range_timestamp(&min, &max).await?;

    let count = records.len();

    let records = if count > MAX_COUNT {
        records
            .chunks(count.div_ceil(MAX_COUNT))
            .fold(vec![], |mut chunked, stats| {
                let chunk_size = stats.len();
                let chunk = stats.iter().fold(
                    (0.0, 0.0, 0, 0),
                    |(app_cpu, other_cpu, app_ram, other_ram), stat| {
                        (
                            app_cpu + stat.app_cpu,
                            other_cpu + stat.other_cpu,
                            app_ram + stat.app_ram,
                            other_ram + stat.other_ram,
                        )
                    },
                );
                chunked.push(SystemStat {
                    timestamp: stats[0].timestamp,
                    app_cpu: chunk.0 / chunk_size as f32,
                    other_cpu: chunk.1 / chunk_size as f32,
                    app_ram: chunk.2 / chunk_size as u32,
                    other_ram: chunk.3 / chunk_size as u32,
                });
                chunked
            })
    } else {
        records
    };

    let max_cpu = records
        .iter()
        .fold(1.0, |max, r| (r.app_cpu + r.other_cpu).max(max));

    let width = 100.0 / records.len() as f64;

    let mut max_ram: f32 = 0.0;
    let (ram, cpu): (Vec<_>, Vec<_>) = records
        .into_iter()
        .map(|r| {
            let app_ram_prct = r.app_ram as f32 / SYSTEM_INFO.ram as f32 * 100.0;
            let other_ram_prct = r.other_ram as f32 / SYSTEM_INFO.ram as f32 * 100.0;
            let app_cpu = r.app_cpu * (100.0 / max_cpu);
            let other_cpu = r.other_cpu * (100.0 / max_cpu);

            max_ram = max_ram.max(app_ram_prct + other_ram_prct);

            let ram_bar = html!(div $"flex flex-col-reverse" style=(format!("width: {width}%")) {
                div style=(format!("height: {other_ram_prct}%; background-color: rgba(255,255,255,0.3);")) {}
                div style=(format!("height: {app_ram_prct}%; background-color: #22c55e;")) {}
            });
            let cpu_bar = html!(div $"flex flex-col-reverse" style=(format!("width: {width}%")) {
                div style=(format!("height: {other_cpu}%; background-color: rgba(255,255,255,0.3);")) {}
                div style=(format!("height: {app_cpu}%; background-color: #22c55e;")) {}
            });

            (ram_bar, cpu_bar)
        })
        .unzip();

    let total_ram = SYSTEM_INFO.ram.div_ceil(100) as f64 / 10.0;
    let total_ram = format!("{total_ram:.1} GB");
    let max_ram = format!("{max_ram:.1}%");
    let max_cpu = format!("{:.1}", max_cpu);

    ok(html!(
        $"w-full" get="/admin/system_stats" trigger="load delay:30s" swap-this {
            $"w-full flex gap-4" {
                $"w-1/2 h-full flex flex-col" {
                    $"font-bold text-lg" {"CPU usage"}
                    $"text-xs lg:text-sm leading-tight lg:leading-snug flex flex-col lg:flex-row" {
                        span{"Cores: "(SYSTEM_INFO.cores)}
                        span $"hidden lg:block mr-1"{","}
                        span{"max used: "(max_cpu)"%"}
                    }
                    $"h-24 flex border border-neutral-700" {(cpu)}
                }
                $"w-1/2 h-full flex flex-col" {
                    $"font-bold text-lg" {"RAM usage"}
                    $"text-xs lg:text-sm leading-tight lg:leading-snug flex flex-col lg:flex-row" {
                        span{"Total: "(total_ram)}
                        span $"hidden lg:block mr-1"{","}
                        span{"max used: "(max_ram)}
                    }
                    $"h-24 flex border border-neutral-700" {(ram)}
                }
            }
            $"w-full h-6" {}
            (disk_stats().await?)
        }
    ))
}

async fn disk_stats() -> Result<Markup> {
    let used_disk = SYSTEM_INFO.used_disk.read().await;
    let total_disk = SYSTEM_INFO.total_disk;

    let data_dir = &APP_CONFIG.data_dir;

    use std::{fs, io, path::PathBuf};

    fn dir_size(path: impl Into<PathBuf>) -> io::Result<u64> {
        fn dir_size(mut dir: fs::ReadDir) -> io::Result<u64> {
            dir.try_fold(0, |acc, file| {
                let file = file?;
                let size = match file.metadata()? {
                    data if data.is_dir() => dir_size(fs::read_dir(file.path())?)?,
                    data => data.len(),
                };
                Ok(acc + size)
            })
        }

        dir_size(fs::read_dir(path.into())?)
    }

    let mut info_path = data_dir.clone();
    info_path.push(LOGS_INFO_NAME);
    let info_size = std::fs::metadata(info_path)?.len();

    let mut traces_path = data_dir.clone();
    traces_path.push(LOGS_TRACES_NAME);
    let traces_size = dir_size(traces_path)?;

    let logs_size = (info_size + traces_size) as f64 / 1_000_000.0;
    let logs_size = format!("{logs_size:.1} MB");

    let mut db_path = data_dir.clone();
    db_path.push(DB_DIRECTORY_NAME);
    let db_size = dir_size(db_path)?;
    let db_size = format!("{:.1} MB", db_size as f64 / 1_000_000.0);

    let used_disk = *used_disk as f64 / total_disk as f64 * 100.0;
    let used_disk = format!("{used_disk:.1}%");
    let total_disk = format!("{:.1}", total_disk as f64 / 1000.0);
    ok(html!(
        $"w-full flex items-center text-[0.6rem] md:text-sm lg:text-base" {
            $"hidden md:block font-bold" {"Disk usage: "} $"block md:hidden font-bold" {"Disk: "}
            $"w-1"{} div {(used_disk)" of "(total_disk)" GB (""DB: "(db_size)", logs: "(logs_size)")"}
        }
        $"hidden lg:block text-xs" {"App data path: " code $"bg-stone-900 p-1" {(data_dir.display())}}
    ))
}