diff --git a/src/server/web.rs b/src/server/web.rs index 2c510f0..6891133 100644 --- a/src/server/web.rs +++ b/src/server/web.rs @@ -1,7 +1,7 @@ mod admin; mod api; mod base; -mod link; +mod components; mod pages; pub mod paths; mod server_config_ext; diff --git a/src/server/web/components.rs b/src/server/web/components.rs new file mode 100644 index 0000000..33268ca --- /dev/null +++ b/src/server/web/components.rs @@ -0,0 +1,75 @@ +use maud::{html, Markup}; +use time::OffsetDateTime; + +use crate::{config::ServerConfig, server::util}; + +use super::{ + paths::{PathCommitByHash, PathRunById, PathWorkerByName}, + server_config_ext::ServerConfigExt, +}; + +pub fn join(sections: &[Markup], with: Markup) -> Markup { + html! { + @for (i, section) in sections.iter().enumerate() { + @if i > 0 { (with) } + (section) + } + } +} + +pub fn commit_class_and_title(reachable: i64) -> (&'static str, &'static str) { + if reachable == 0 { + ( + "commit-orphaned", + "This commit is orphaned. It can't be reached from any ref.", + ) + } else if reachable == -1 { + ( + "commit-reachable", + "This commit can only be reached from untracked refs.", + ) + } else { + ( + "commit-tracked", + "This commit can be reached from a tracked ref.", + ) + } +} + +pub fn link_commit(config: &ServerConfig, hash: String, message: &str, reachable: i64) -> Markup { + let short = util::truncate(&util::format_commit_short(&hash, message), 80); + let path = config.path(PathCommitByHash { hash }); + let (class, title) = commit_class_and_title(reachable); + + html! { + a href=(path) .(class) title=(title) { (short) } + } +} + +/// Link to a run by its commit's short message. +pub fn link_run_short(config: &ServerConfig, id: String, hash: &str, message: &str) -> Markup { + let short = util::truncate(&util::format_commit_short(hash, message), 80); + let path = config.path(PathRunById { id }); + + html! { + a href=(path) { "Run of " (short) } + } +} + +/// Link to a run by its start time. +pub fn link_run_date(config: &ServerConfig, id: String, start: OffsetDateTime) -> Markup { + let start = util::format_time(start); + let path = config.path(PathRunById { id }); + + html! { + a href=(path) { "Run from " (start) } + } +} + +pub fn link_worker(config: &ServerConfig, name: String) -> Markup { + let path = config.path(PathWorkerByName { name: name.clone() }); + + html! { + a href=(path) { (name) } + } +} diff --git a/src/server/web/link.rs b/src/server/web/link.rs deleted file mode 100644 index a92157e..0000000 --- a/src/server/web/link.rs +++ /dev/null @@ -1,114 +0,0 @@ -use maud::{html, Markup}; -use time::OffsetDateTime; - -use crate::server::util; - -use super::{ - base::Base, - paths::{PathCommitByHash, PathRunById, PathWorkerByName}, - server_config_ext::{AbsPath, ServerConfigExt}, -}; - -pub struct LinkCommit { - link: AbsPath, - short: String, - reachable: i64, -} - -impl LinkCommit { - pub fn new(base: &Base, hash: String, message: &str, reachable: i64) -> Self { - Self { - short: util::format_commit_short(&hash, message), - link: base.config.path(PathCommitByHash { hash }), - reachable, - } - } - - pub fn class_and_title(&self) -> (&'static str, &'static str) { - if self.reachable == 0 { - ( - "commit-orphaned", - "This commit is orphaned. It can't be reached from any ref.", - ) - } else if self.reachable == -1 { - ( - "commit-reachable", - "This commit can only be reached from untracked refs.", - ) - } else { - ( - "commit-tracked", - "This commit can be reached from a tracked ref.", - ) - } - } - - pub fn html(&self) -> Markup { - let (class, title) = self.class_and_title(); - let short = util::truncate(&self.short, 80); - - html! { - a href=(self.link) .(class) title=(title) { (short) } - } - } -} - -pub struct LinkRunShort { - link: AbsPath, - short: String, -} - -impl LinkRunShort { - pub fn new(base: &Base, id: String, hash: &str, message: &str) -> Self { - Self { - link: base.config.path(PathRunById { id }), - short: util::format_commit_short(hash, message), - } - } - - pub fn html(&self) -> Markup { - html! { - a href=(self.link) { "Run of " (util::truncate(&self.short, 80)) } - } - } -} - -pub struct LinkRunDate { - link: AbsPath, - date: String, // TODO base.date(...)? -} - -impl LinkRunDate { - pub fn new(base: &Base, id: String, start: OffsetDateTime) -> Self { - Self { - link: base.config.path(PathRunById { id }), - date: util::format_time(start), - } - } - - pub fn html(&self) -> Markup { - html! { - a href=(self.link) { "Run from " (self.date) } - } - } -} - -pub struct LinkWorker { - link: AbsPath, - name: String, -} - -impl LinkWorker { - pub fn new(base: &Base, name: String) -> Self { - Self { - link: base.config.path(PathWorkerByName { name: name.clone() }), - name, - } - } - - pub fn html(&self) -> Markup { - html! { - a href=(self.link) { (self.name) } - } - } -} diff --git a/src/server/web/pages/commit.rs b/src/server/web/pages/commit.rs index eb6560e..c5c84d3 100644 --- a/src/server/web/pages/commit.rs +++ b/src/server/web/pages/commit.rs @@ -13,7 +13,7 @@ use crate::{ util, web::{ base::{Base, Tab}, - link::{LinkCommit, LinkRunDate}, + components, paths::{PathAdminQueueAdd, PathCommitByHash}, server_config_ext::ServerConfigExt, }, @@ -59,7 +59,7 @@ pub async fn get_commit_by_hash( path.hash, ) .fetch(&db) - .map_ok(|r| LinkCommit::new(&base, r.hash, &r.message, r.reachable)) + .map_ok(|r| components::link_commit(config, r.hash, &r.message, r.reachable)) .try_collect::>() .await?; @@ -73,7 +73,7 @@ pub async fn get_commit_by_hash( path.hash, ) .fetch(&db) - .map_ok(|r| LinkCommit::new(&base, r.hash, &r.message, r.reachable)) + .map_ok(|r| components::link_commit(config, r.hash, &r.message, r.reachable)) .try_collect::>() .await?; @@ -87,18 +87,11 @@ pub async fn get_commit_by_hash( path.hash ) .fetch(&db) - .map_ok(|r| LinkRunDate::new(&base, r.id, r.start)) + .map_ok(|r| components::link_run_date(config, r.id, r.start)) .try_collect::>() .await?; - // TODO Somewhat inefficient to construct full LinkCommit for this - let (class, title) = LinkCommit::new( - &base, - commit.hash.clone(), - &commit.message, - commit.reachable, - ) - .class_and_title(); + let (class, title) = components::commit_class_and_title(commit.reachable); Ok(base .html( @@ -123,12 +116,12 @@ pub async fn get_commit_by_hash( @for commit in parents { dt { "Parent:" } - dd { (commit.html()) } + dd { (commit) } } @for commit in children { dt { "Child:" } - dd { (commit.html()) } + dd { (commit) } } } pre .(class) title=(title) { @@ -142,7 +135,7 @@ pub async fn get_commit_by_hash( } @else { ul { @for run in runs { - li { (run.html()) } + li { (run) } } } } diff --git a/src/server/web/pages/index.rs b/src/server/web/pages/index.rs index c50ee34..c1b81ed 100644 --- a/src/server/web/pages/index.rs +++ b/src/server/web/pages/index.rs @@ -1,13 +1,13 @@ use axum::{extract::State, response::IntoResponse}; use futures::TryStreamExt; -use maud::html; +use maud::{html, Markup}; use sqlx::SqlitePool; use crate::{ config::ServerConfig, server::web::{ base::{Base, Tab}, - link::LinkCommit, + components, paths::{PathAdminRefsTrack, PathAdminRefsUntrack, PathAdminRefsUpdate, PathIndex}, server_config_ext::ServerConfigExt, }, @@ -16,7 +16,7 @@ use crate::{ struct Ref { name: String, - commit: LinkCommit, + commit: Markup, tracked: bool, } @@ -38,7 +38,7 @@ pub async fn get_index( .fetch(&db) .map_ok(|r| Ref { name: r.name.clone(), - commit: LinkCommit::new(&base, r.hash, &r.message, r.reachable), + commit: components::link_commit(config, r.hash, &r.message, r.reachable), tracked: r.tracked != 0, }) .try_collect::>() @@ -69,7 +69,7 @@ pub async fn get_index( button .linkish name="ref" value=(r#ref.name) { "untrack" } "]" } - dd { (r#ref.commit.html()) } + dd { (r#ref.commit) } } } } @@ -84,7 +84,7 @@ pub async fn get_index( button .linkish name="ref" value=(r#ref.name) { "track" } "]" } - dd { (r#ref.commit.html()) } + dd { (r#ref.commit) } } } } diff --git a/src/server/web/pages/queue.rs b/src/server/web/pages/queue.rs index a11cf29..f510f0a 100644 --- a/src/server/web/pages/queue.rs +++ b/src/server/web/pages/queue.rs @@ -18,7 +18,7 @@ use crate::{ util, web::{ base::{Base, Tab}, - link::{LinkCommit, LinkRunShort, LinkWorker}, + components, paths::{ PathAdminQueueAddBatch, PathAdminQueueDecrease, PathAdminQueueDelete, PathAdminQueueIncrease, PathQueue, PathQueueDelete, PathQueueInner, @@ -35,11 +35,11 @@ use crate::{ enum Status { Idle, Busy, - Working(LinkRunShort), + Working(Markup), } struct Worker { - link: LinkWorker, + link: Markup, status: Status, } @@ -48,10 +48,10 @@ struct Task { link_increase: AbsPath, link_decrease: AbsPath, hash: String, - commit: LinkCommit, + commit: Markup, since: String, priority: i64, - workers: Vec, + workers: Vec, odd: bool, } @@ -68,9 +68,9 @@ fn sorted_workers(workers: &Mutex) -> Vec<(String, WorkerInfo)> { } async fn get_workers( + config: &ServerConfig, db: &SqlitePool, workers: &[(String, WorkerInfo)], - base: &Base, ) -> somehow::Result> { let mut result = vec![]; for (name, info) in workers { @@ -82,12 +82,17 @@ async fn get_workers( sqlx::query_scalar!("SELECT message FROM commits WHERE hash = ?", run.hash) .fetch_one(db) .await?; - Status::Working(LinkRunShort::new(base, run.id.clone(), &run.hash, &message)) + Status::Working(components::link_run_short( + config, + run.id.clone(), + &run.hash, + &message, + )) } }; result.push(Worker { - link: LinkWorker::new(base, name.clone()), + link: components::link_worker(config, name.clone()), status, }) } @@ -95,18 +100,18 @@ async fn get_workers( } async fn get_queue_data( + config: &ServerConfig, db: &SqlitePool, workers: &[(String, WorkerInfo)], - base: &Base, ) -> somehow::Result> { // Group workers by commit hash - let mut workers_by_commit: HashMap> = HashMap::new(); + let mut workers_by_commit: HashMap> = HashMap::new(); for (name, info) in workers { if let WorkerStatus::Working(run) = &info.status { workers_by_commit .entry(run.hash.clone()) .or_default() - .push(LinkWorker::new(base, name.clone())); + .push(components::link_worker(config, name.clone())); } } @@ -126,13 +131,13 @@ async fn get_queue_data( .fetch(db) .map_ok(|r| Task { workers: workers_by_commit.remove(&r.hash).unwrap_or_default(), - link_delete: base.config.path(PathQueueDelete { + link_delete: config.path(PathQueueDelete { hash: r.hash.clone(), }), - link_increase: base.config.path(PathAdminQueueIncrease {}), - link_decrease: base.config.path(PathAdminQueueDecrease {}), + link_increase: config.path(PathAdminQueueIncrease {}), + link_decrease: config.path(PathAdminQueueDecrease {}), hash: r.hash.clone(), - commit: LinkCommit::new(base, r.hash, &r.message, r.reachable), + commit: components::link_commit(config, r.hash, &r.message, r.reachable), since: util::format_delta_from_now(r.date), priority: r.priority, odd: false, @@ -168,11 +173,11 @@ fn page_inner(workers: Vec, tasks: Vec) -> Markup { } tbody { @for worker in workers { tr { - td { (worker.link.html()) } + td { (worker.link) } td { @match worker.status { Status::Idle => "idle", Status::Busy => "busy", - Status::Working(link) => (link.html()), + Status::Working(link) => (link), } } } } } @@ -191,7 +196,7 @@ fn page_inner(workers: Vec, tasks: Vec) -> Markup { } tbody { @for task in tasks { tr .odd[task.odd] { - td { (task.commit.html()) } + td { (task.commit) } td { (task.since) " [" a href=(task.link_delete) title="Delete from queue" { "del" } @@ -207,10 +212,7 @@ fn page_inner(workers: Vec, tasks: Vec) -> Markup { @if task.workers.is_empty() { "-" } - @for (i, worker) in task.workers.iter().enumerate() { - @if i > 0 { ", " } - (worker.html()) - } + (components::join(&task.workers, html! { ", " })) } } } } @@ -226,10 +228,9 @@ pub async fn get_queue_inner( State(db): State, State(workers): State>>, ) -> somehow::Result { - let base = Base::new(config, Tab::Queue); let sorted_workers = sorted_workers(&workers); - let workers = get_workers(&db, &sorted_workers, &base).await?; - let tasks = get_queue_data(&db, &sorted_workers, &base).await?; + let workers = get_workers(config, &db, &sorted_workers).await?; + let tasks = get_queue_data(config, &db, &sorted_workers).await?; Ok(page_inner(workers, tasks)) } @@ -241,8 +242,8 @@ pub async fn get_queue( ) -> somehow::Result { let base = Base::new(config, Tab::Queue); let sorted_workers = sorted_workers(&workers); - let workers = get_workers(&db, &sorted_workers, &base).await?; - let tasks = get_queue_data(&db, &sorted_workers, &base).await?; + let workers = get_workers(config, &db, &sorted_workers).await?; + let tasks = get_queue_data(config, &db, &sorted_workers).await?; Ok(base.html( &format!("queue ({})", tasks.len()), @@ -289,7 +290,7 @@ pub async fn get_queue_delete( return Ok(StatusCode::NOT_FOUND.into_response()); }; - let commit = LinkCommit::new(&base, r.hash.clone(), &r.message, r.reachable); + let commit = components::link_commit(config, r.hash.clone(), &r.message, r.reachable); Ok(base .html( @@ -298,7 +299,7 @@ pub async fn get_queue_delete( html! { h2 { "Delete commit from queue" } p { "You are about to delete this commit from the queue:" } - p { (commit.html()) } + p { (commit) } p { "All runs of this commit currently in progress will be aborted!" } form method="post" action=(config.path(PathAdminQueueDelete {})) { input name="hash" type="hidden" value=(r.hash); diff --git a/src/server/web/pages/run.rs b/src/server/web/pages/run.rs index 7eb8e87..2c4ead3 100644 --- a/src/server/web/pages/run.rs +++ b/src/server/web/pages/run.rs @@ -13,7 +13,7 @@ use crate::{ util, web::{ base::{Base, Tab}, - link::LinkCommit, + components, paths::PathRunById, }, }, @@ -98,7 +98,7 @@ async fn from_finished_run( let base = Base::new(config, Tab::None); - let commit = LinkCommit::new(&base, run.hash, &run.message, run.reachable); + let commit = components::link_commit(config, run.hash, &run.message, run.reachable); Ok(Some( base.html( @@ -110,7 +110,7 @@ async fn from_finished_run( span .title { "run " (run.id) } dl { dt { "Commit:" } - dd { (commit.html())} + dd { (commit)} dt { "Benchmark:" } dd { (run.bench_method) } diff --git a/src/server/web/pages/worker.rs b/src/server/web/pages/worker.rs index 0dc0751..056537b 100644 --- a/src/server/web/pages/worker.rs +++ b/src/server/web/pages/worker.rs @@ -5,7 +5,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use maud::html; +use maud::{html, Markup}; use sqlx::SqlitePool; use crate::{ @@ -14,7 +14,7 @@ use crate::{ util, web::{ base::{Base, Tab}, - link::LinkRunShort, + components, paths::PathWorkerByName, }, workers::Workers, @@ -26,10 +26,14 @@ use crate::{ enum Status { Idle, Busy, - Working { link: LinkRunShort, since: String }, + Working { link: Markup, since: String }, } -async fn status(status: &WorkerStatus, db: &SqlitePool, base: &Base) -> somehow::Result { +async fn status( + config: &ServerConfig, + status: &WorkerStatus, + db: &SqlitePool, +) -> somehow::Result { Ok(match status { WorkerStatus::Idle => Status::Idle, WorkerStatus::Busy => Status::Busy, @@ -39,7 +43,7 @@ async fn status(status: &WorkerStatus, db: &SqlitePool, base: &Base) -> somehow: .fetch_one(db) .await?; Status::Working { - link: LinkRunShort::new(base, run.id.clone(), &run.hash, &message), + link: components::link_run_short(config, run.id.clone(), &run.hash, &message), since: util::format_time(run.start.0), } } @@ -59,7 +63,7 @@ pub async fn get_worker_by_name( let base = Base::new(config, Tab::None); - let status = status(&info.status, &db, &base).await?; + let status = status(config, &info.status, &db).await?; Ok(base .html( @@ -84,7 +88,7 @@ pub async fn get_worker_by_name( } Status::Working { link, since } => { dt { "Working on:" } - dd { (link.html()) } + dd { (link) } dt { "Working since:" } dd { (since) }