aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/helper.rs21
-rw-r--r--src/main.rs6
-rw-r--r--src/style.css63
-rw-r--r--src/webui.rs91
4 files changed, 147 insertions, 34 deletions
diff --git a/src/helper.rs b/src/helper.rs
new file mode 100644
index 0000000..22ccd0d
--- /dev/null
+++ b/src/helper.rs
@@ -0,0 +1,21 @@
+use axum::{
+ http::{HeaderValue, header},
+ response::{IntoResponse, Response},
+};
+
+#[derive(Clone, Copy, Debug)]
+#[must_use]
+pub struct Css<T>(pub T);
+
+impl<T> IntoResponse for Css<T>
+where
+ T: IntoResponse,
+{
+ fn into_response(self) -> Response {
+ (
+ [(header::CONTENT_TYPE, HeaderValue::from_static("text/css"))],
+ self.0,
+ )
+ .into_response()
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 5096ae8..354d303 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,4 @@
+pub mod helper;
pub mod webui;
pub mod worker_ws;
@@ -14,10 +15,10 @@ use tokio::{
net::TcpListener,
sync::{RwLock, mpsc::Sender},
};
-use webui::webui;
+use webui::{webui, webui_style};
use worker_ws::{WorkerID, WorkerResponse, worker_websocket};
-struct Worker {
+pub struct Worker {
accept: usize,
name: String,
sources: Vec<String>,
@@ -42,6 +43,7 @@ async fn main() -> Result<()> {
state.load().await?;
let router = Router::new()
.route("/", get(webui))
+ .route("/style.css", get(webui_style))
.route("/worker_ws", get(worker_websocket))
.with_state(Arc::new(RwLock::new(state)));
let listener = TcpListener::bind("127.0.0.1:8080").await?;
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000..b0baf42
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,63 @@
+:root {
+ background-color: #111111;
+ color: white;
+}
+
+.task {
+ margin: 2px;
+ padding: 2px;
+ border-radius: 10px;
+ border: 2px solid white;
+}
+.task.queue {
+ border-color: #6e6eff;
+}
+.task.loading {
+ border-color: #ffc36e;
+}
+.task.complete {
+ border-color: #6eff70;
+}
+
+.worker {
+ margin: 2px;
+ padding: 2px;
+ border-radius: 10px;
+ border: 2px solid white;
+}
+.worker.accepting {
+ border-color: #6eff70;
+}
+.worker.busy {
+ border-color: #ffc36e;
+}
+
+section.tasks {
+ display: flex;
+ width: 100%;
+}
+section h2 {
+ text-align: center;
+ margin-bottom: 5px;
+}
+section.tasks .count {
+ margin-top: 5px;
+ text-align: center;
+ color: #9b9b9b;
+}
+section.tasks > div {
+ flex: 1;
+}
+
+ul {
+ list-style: none;
+}
+
+span.key {
+ color: #9b9b9b;
+ font-size: small;
+}
+
+h3 {
+ margin: 0px;
+}
diff --git a/src/webui.rs b/src/webui.rs
index 9e8b9c3..9b21ab0 100644
--- a/src/webui.rs
+++ b/src/webui.rs
@@ -1,51 +1,59 @@
-use crate::State;
+use crate::{State, helper::Css};
use axum::extract::State as S;
use axum::response::Html;
+use markup::doctype;
use serde_json::{Map, Value};
use std::sync::Arc;
-use tokio::sync::RwLock;
+use tokio::{fs::read_to_string, sync::RwLock};
+
+pub(crate) async fn webui_style() -> Css<String> {
+ 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(S(state): S<Arc<RwLock<State>>>) -> Html<String> {
let g = state.read().await;
let doc = markup::new! {
+ @doctype()
html {
head {
meta[charset="UTF-8"];
- title { "" }
+ link[rel="stylesheet", href="/style.css"];
+ title { "Queue-Server" }
}
body {
section {
h2 { "Workers"}
ul { @for (id, w) in &g.workers {
- li { div {
- h3 { @w.name }
- span { "ID: " @id } " "
- @if w.accept > 0 {
- span { "Accepting Jobs (" @w.accept ")" }
- } else {
- span { "Idle" }
- }
- }}
- }}
- }
- section {
- h2 { "Queued" }
- ul { @for key in &g.queue {
- li { @key }
+ li { @Worker { id: *id, w } }
}}
}
- section {
- h2 { "Loading" }
- ul { @for key in &g.loading {
- li { @key }
- }}
- }
- section {
- h2 { "Completed" }
- ul { @for key in &g.complete {
- li { @key }
- }}
+ section.tasks {
+ div {
+ h2 { "Queued" }
+ p.count { @g.queue.len() " tasks" }
+ ul { @for key in &g.queue {
+ li { @Task { key, data: g.metadata.get(key).unwrap(), class: "task queue" } }
+ }}
+ }
+ div {
+ h2 { "Loading" }
+ p.count { @g.loading.len() " tasks" }
+ ul { @for key in &g.loading {
+ li { @Task { key, data: g.metadata.get(key).unwrap(), class: "task loading" } }
+ }}
+ }
+ div {
+ h2 { "Completed" }
+ p.count { @g.complete.len() " tasks" }
+ ul { @for key in &g.complete {
+ li { @Task { key, data: g.metadata.get(key).unwrap(), class: "task complete" } }
+ }}
+ }
}
}
}
@@ -54,10 +62,29 @@ pub(crate) async fn webui(S(state): S<Arc<RwLock<State>>>) -> Html<String> {
}
markup::define!(
- Task<'a>(key: &'a str, data: &'a Map<String, Value>) {
- div.task {
+ Worker<'a>(id: u64, w: &'a crate::Worker) {
+ div[class=worker_class(w)] {
+ h3 { @w.name }
+ span { "ID: " @id } " "
+ @if w.accept > 0 {
+ span { "Accepting Jobs (" @w.accept ")" }
+ } else {
+ span { "Idle" }
+ }
+ }
+ }
+ Task<'a>(key: &'a str, data: &'a Map<String, Value>, class: &'a str) {
+ div[class=class] {
h3 { @data["title"].as_str().unwrap_or(key) }
- spawn.key { @key }
+ span.key { @key }
}
}
);
+
+fn worker_class(w: &crate::Worker) -> &'static str {
+ if w.accept > 0 {
+ "worker accepting"
+ } else {
+ "worker busy"
+ }
+}