diff --git a/.sqlx/query-f7d4cee8d4ff00232bcf7d56c4c8d246f25c0b0e3c54c54c43c51b59372c14fb.json b/.sqlx/query-f7d4cee8d4ff00232bcf7d56c4c8d246f25c0b0e3c54c54c43c51b59372c14fb.json new file mode 100644 index 0000000..7c2e916 --- /dev/null +++ b/.sqlx/query-f7d4cee8d4ff00232bcf7d56c4c8d246f25c0b0e3c54c54c43c51b59372c14fb.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "SELECT message FROM commits WHERE hash = ?", + "describe": { + "columns": [ + { + "name": "message", + "ordinal": 0, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "f7d4cee8d4ff00232bcf7d56c4c8d246f25c0b0e3c54c54c43c51b59372c14fb" +} diff --git a/scripts/queue/main.ts b/scripts/queue/main.ts index 07c22d5..05780f2 100644 --- a/scripts/queue/main.ts +++ b/scripts/queue/main.ts @@ -1,14 +1,13 @@ const COUNT = document.getElementById("count")!; -const QUEUE = document.getElementById("queue")!; +const INNER = document.getElementById("inner")!; const REFRESH_SECONDS = 10; function update() { - fetch("table") + fetch("inner") .then(response => response.text()) .then(text => { - QUEUE.innerHTML = text; - let count = QUEUE.querySelectorAll("tbody tr").length; - COUNT.textContent = String(count); + INNER.innerHTML = text; + let count = INNER.querySelector("#queue")?.dataset["count"]!; document.title = document.title.replace(/^queue \(\d+\)/, `queue (${count})`); }); } diff --git a/src/server/runners.rs b/src/server/runners.rs index 1196dee..29f1988 100644 --- a/src/server/runners.rs +++ b/src/server/runners.rs @@ -86,4 +86,8 @@ impl Runners { pub fn get(&self, name: &str) -> Option { self.runners.get(name).cloned() } + + pub fn get_all(&self) -> HashMap { + self.runners.clone() + } } diff --git a/src/server/web.rs b/src/server/web.rs index e1ff2be..b2586b6 100644 --- a/src/server/web.rs +++ b/src/server/web.rs @@ -48,7 +48,7 @@ pub async fn run(server: Server) -> somehow::Result<()> { .route("/commit/:hash", get(commit::get)) .route("/runner/:name", get(runner::get)) .route("/queue/", get(queue::get)) - .route("/queue/table", get(queue::get_table)) + .route("/queue/inner", get(queue::get_inner)) .merge(api::router(&server)) .fallback(get(r#static::static_handler)) .with_state(server.clone()); diff --git a/src/server/web/link.rs b/src/server/web/link.rs index 823d91c..5875b17 100644 --- a/src/server/web/link.rs +++ b/src/server/web/link.rs @@ -7,9 +7,8 @@ use super::Base; #[derive(Template)] #[template( ext = "html", - source = " + source = "\ {% import \"util.html\" as util %} - @@ -34,3 +33,50 @@ impl CommitLink { } } } + +#[derive(Template)] +#[template( + ext = "html", + source = "\ + + Run of {{ short }} + +" +)] +pub struct RunLink { + root: String, + id: String, + short: String, +} + +impl RunLink { + pub fn new(base: &Base, id: String, hash: &str, message: &str) -> Self { + Self { + root: base.root.clone(), + id, + short: util::format_commit_short(hash, message), + } + } +} +#[derive(Template)] +#[template( + ext = "html", + source = "\ + + {{ name }} + +" +)] +pub struct RunnerLink { + root: String, + name: String, +} + +impl RunnerLink { + pub fn new(base: &Base, name: String) -> Self { + Self { + root: base.root.clone(), + name, + } + } +} diff --git a/src/server/web/queue.rs b/src/server/web/queue.rs index cdbe7fa..96b8677 100644 --- a/src/server/web/queue.rs +++ b/src/server/web/queue.rs @@ -1,22 +1,101 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + use askama::Template; use axum::{extract::State, response::IntoResponse}; use futures::TryStreamExt; use sqlx::SqlitePool; -use crate::{config::Config, server::util, somehow}; +use crate::{ + config::Config, + server::{ + runners::{RunnerInfo, Runners}, + util, + }, + shared::RunnerStatus, + somehow, +}; -use super::{Base, Tab}; +use super::{ + link::{CommitLink, RunLink, RunnerLink}, + Base, Tab, +}; + +enum Status { + Idle, + Busy, + Working(RunLink), +} + +struct Runner { + link: RunnerLink, + status: Status, +} struct Task { - hash: String, - short: String, - reachable: i64, + commit: CommitLink, since: String, priority: i64, + runners: Vec, odd: bool, } -async fn get_queue(db: &SqlitePool) -> somehow::Result> { +fn sorted_runners(runners: &Mutex) -> Vec<(String, RunnerInfo)> { + let mut runners = runners + .lock() + .unwrap() + .get_all() + .into_iter() + .collect::>(); + runners.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + runners +} + +async fn get_runners( + db: &SqlitePool, + runners: &[(String, RunnerInfo)], + base: &Base, +) -> somehow::Result> { + let mut result = vec![]; + for (name, info) in runners { + let status = match &info.status { + RunnerStatus::Idle => Status::Idle, + RunnerStatus::Busy => Status::Busy, + RunnerStatus::Working { id, hash, .. } => { + let message = + sqlx::query_scalar!("SELECT message FROM commits WHERE hash = ?", hash) + .fetch_one(db) + .await?; + Status::Working(RunLink::new(base, id.clone(), hash, &message)) + } + }; + + result.push(Runner { + link: RunnerLink::new(base, name.clone()), + status, + }) + } + Ok(result) +} + +async fn get_queue( + db: &SqlitePool, + runners: &[(String, RunnerInfo)], + base: &Base, +) -> somehow::Result> { + // Group runners by commit hash + let mut runners_by_commit: HashMap> = HashMap::new(); + for (name, info) in runners { + if let RunnerStatus::Working { hash, .. } = &info.status { + runners_by_commit + .entry(hash.clone()) + .or_default() + .push(RunnerLink::new(base, name.clone())); + } + } + let mut tasks = sqlx::query!( "\ SELECT \ @@ -32,9 +111,8 @@ async fn get_queue(db: &SqlitePool) -> somehow::Result> { ) .fetch(db) .map_ok(|r| Task { - short: util::format_commit_short(&r.hash, &r.message), - hash: r.hash, - reachable: r.reachable, + runners: runners_by_commit.remove(&r.hash).unwrap_or_default(), + commit: CommitLink::new(base, r.hash, &r.message, r.reachable), since: util::format_delta_from_now(r.date), priority: r.priority, odd: false, @@ -56,37 +134,41 @@ async fn get_queue(db: &SqlitePool) -> somehow::Result> { } #[derive(Template)] -#[template(path = "queue_table.html")] -struct QueueTableTemplate { - base: Base, +#[template(path = "queue_inner.html")] +struct QueueInnerTemplate { + runners: Vec, tasks: Vec, } -pub async fn get_table( +pub async fn get_inner( State(config): State<&'static Config>, State(db): State, + State(runners): State>>, ) -> somehow::Result { - let tasks = get_queue(&db).await?; - Ok(QueueTableTemplate { - base: Base::new(config, Tab::Queue), - tasks, - }) + let base = Base::new(config, Tab::Queue); + let sorted_runners = sorted_runners(&runners); + let runners = get_runners(&db, &sorted_runners, &base).await?; + let tasks = get_queue(&db, &sorted_runners, &base).await?; + Ok(QueueInnerTemplate { runners, tasks }) } #[derive(Template)] #[template(path = "queue.html")] struct QueueTemplate { base: Base, - table: QueueTableTemplate, + inner: QueueInnerTemplate, } pub async fn get( State(config): State<&'static Config>, State(db): State, + State(runners): State>>, ) -> somehow::Result { let base = Base::new(config, Tab::Queue); - let tasks = get_queue(&db).await?; + let sorted_runners = sorted_runners(&runners); + let runners = get_runners(&db, &sorted_runners, &base).await?; + let tasks = get_queue(&db, &sorted_runners, &base).await?; Ok(QueueTemplate { - base: base.clone(), - table: QueueTableTemplate { base, tasks }, + base, + inner: QueueInnerTemplate { runners, tasks }, }) } diff --git a/static/base.css b/static/base.css index 5aab179..143a577 100644 --- a/static/base.css +++ b/static/base.css @@ -146,15 +146,15 @@ nav a:hover { /* Queue */ -.queue td:nth-child(2), -.queue td:nth-child(3) { +#queue td:nth-child(2), +#queue td:nth-child(3) { text-align: right; } -.queue .odd { +#queue .odd { background-color: #eee; } -.queue .odd:hover { +#queue .odd:hover { background-color: #ddd; } diff --git a/templates/queue.html b/templates/queue.html index 8d8cab4..7310050 100644 --- a/templates/queue.html +++ b/templates/queue.html @@ -1,12 +1,11 @@ {% extends "base.html" %} -{% block title %}queue ({{ table.tasks.len() }}){% endblock %} +{% block title %}queue ({{ inner.tasks.len() }}){% endblock %} {% block head %} {% endblock %} {% block body %} -

Queue ({{ table.tasks.len() }})

-
{{ table|safe }}
+
{{ inner|safe }}
{% endblock %} diff --git a/templates/queue_inner.html b/templates/queue_inner.html new file mode 100644 index 0000000..9d68c9a --- /dev/null +++ b/templates/queue_inner.html @@ -0,0 +1,56 @@ +{% import "util.html" as util %} + +

Runners

+{% if runners.is_empty() %} +

No runners connected

+{% else %} + + + + + + + + + {% for runner in runners %} + + + {% match runner.status %} + {% when Status::Idle %} + + {% when Status::Busy %} + + {% when Status::Working with (link) %} + + {% endmatch %} + + {% endfor %} + +
runnerstatus
{{ runner.link|safe }}idlebusy{{ link|safe }}
+{% endif %} + +

Queue ({{ tasks.len() }})

+ + + + + + + + + + + {% for task in tasks %} + + + + + {% if task.runners.is_empty() %} + + {% else %} + + {% endif %} + + {% endfor %} + +
commitsincepriorunner
{{ task.commit|safe }}{{ task.since }}{{ task.priority }}-{{ task.runners|join(", ")|safe }}
diff --git a/templates/queue_table.html b/templates/queue_table.html deleted file mode 100644 index 1b031d4..0000000 --- a/templates/queue_table.html +++ /dev/null @@ -1,24 +0,0 @@ -{% import "util.html" as util %} - - - - - - - - - - - {% for task in tasks %} - - - - - - {% endfor %} - -
commitsinceprio
- - {# {% call util::commit_short(task.short, task.reachable) %} #} - - {{ task.since }}{{ task.priority }}