use crate::{ State, helper::{Css, Javascript}, }; use axum::extract::State as S; use axum::response::Html; use markup::doctype; use serde_json::{Map, Value}; use std::{collections::HashSet, sync::Arc}; use tokio::{fs::read_to_string, sync::RwLock}; pub(crate) async fn webui_style() -> Css { Css(if cfg!(debug_assertions) { read_to_string("src/style.css").await.unwrap() } else { include_str!("style.css").to_string() }) } pub(crate) async fn webui_script() -> Javascript { Javascript(if cfg!(debug_assertions) { read_to_string("src/webui_live.js").await.unwrap() } else { include_str!("webui_live.js").to_string() }) } pub(crate) async fn webui(S(state): S>>) -> Html { let g = state.read().await; let default = &Map::new(); let g = &g; let doc = markup::new! { @doctype() html { head { meta[charset="UTF-8"]; link[rel="stylesheet", href="/style.css"]; script[src="/webui_live.js", defer] {} title { "Queue-Server" } } body { section.workers { h2 { "Workers"} ul { @for (id, w) in &g.workers { li { @Worker { id: *id, w } } }} } section.tasks { @Taskbin {title: "Queued", state: "queue", set: &g.queue, default, g } @Taskbin {title: "Loading", state: "loading", set: &g.loading, default, g } @Taskbin {title: "Completed", state: "complete", set: &g.complete, default, g } } } } }; Html(doc.to_string()) } markup::define!( Taskbin<'a>(title: &'a str, state: &'a str, set: &'a HashSet, g: &'a State, default: &'a Map) { div[id=format!("bin-{state}")] { h2 { @title } p.count { @set.len() " tasks" } @let class = format!("task {state}"); ul { @for key in set.iter().take(128) { li { @Task { key, data: g.metadata.get(key).unwrap_or(&default), class: &class } } }} } } Task<'a>(key: &'a str, data: &'a Map, class: &'a str) { div[class=class, id=key] { // @if let Some(url) = data.get("thumbnail").and_then(Value::as_str) { // img[src=url, loading="lazy"]; // } h3 { @data.get("title").and_then(Value::as_str).unwrap_or(key) } @if let Some(s) = data.get("subtitle").and_then(Value::as_str) { span.subtitle { @s } br; } span.key { @key } } } Worker<'a>(id: u64, w: &'a crate::Worker) { div[class=worker_class(w), id=format!("worker-{id}")] { h3 { @w.name } span { "ID: " @id } ", " @if !w.assigned_tasks.is_empty() { span { "Busy (" @w.assigned_tasks.len() ")" } } else if w.accept > 0 { span { "Accepting Tasks (" @w.accept ")" } } else { span { "Idle" } } } } ); fn worker_class(w: &crate::Worker) -> &'static str { if w.accept > 0 { "worker accepting" } else if w.assigned_tasks.is_empty() { "worker idle" } else { "worker busy" } }