From 7b08cb229de7b0c131bc8c7e3405ad548ff5e0eb Mon Sep 17 00:00:00 2001 From: iximeow Date: Sat, 24 Jun 2023 18:09:13 -0700 Subject: add a repos index page --- Cargo.lock | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/dbctx.rs | 116 ++++++++++++++++++++++++++++---- src/main.rs | 145 +++++++++++++++++++++++++++++++++++++++- src/sql.rs | 7 ++ 5 files changed, 464 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c1ad8d..7eaa568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,21 @@ dependencies = [ ] [[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] name = "anyhow" version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -262,6 +277,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] name = "clap" version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -347,6 +377,16 @@ dependencies = [ ] [[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] name = "color-eyre" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -500,6 +540,50 @@ dependencies = [ ] [[package]] +name = "cxx" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "digest" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -760,7 +844,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -992,6 +1076,30 @@ dependencies = [ ] [[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] name = "idna" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1088,6 +1196,7 @@ dependencies = [ "axum-macros", "axum-server", "base64", + "chrono", "clap 4.0.29", "console-subscriber", "futures-util", @@ -1169,6 +1278,15 @@ dependencies = [ ] [[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] name = "linux-raw-sys" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1268,7 +1386,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.42.0", ] @@ -1910,6 +2028,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] name = "sct" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2174,6 +2298,17 @@ dependencies = [ ] [[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2602,6 +2737,12 @@ dependencies = [ [[package]] name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" @@ -2724,6 +2865,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] name = "windows-sys" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2742,22 +2892,43 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.0", "windows_aarch64_msvc 0.42.0", "windows_i686_gnu 0.42.0", "windows_i686_msvc 0.42.0", "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.0", "windows_x86_64_msvc 0.42.0", ] [[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2770,6 +2941,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2782,6 +2959,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2794,6 +2977,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2806,12 +2995,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" [[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2824,6 +3025,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] name = "winreg" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 6ea03b7..03e5f77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ sha2 = "*" reqwest = { version = "*", features = ["rustls-tls-native-roots"] } clap = { version = "*", features = ["derive"] } rlua = "*" +chrono = "*" [[bin]] name = "ci_webserver" diff --git a/src/dbctx.rs b/src/dbctx.rs index 67740e8..476c3fc 100644 --- a/src/dbctx.rs +++ b/src/dbctx.rs @@ -20,6 +20,41 @@ pub struct DbCtx { } #[derive(Debug, Clone)] +pub struct Repo { + pub id: u64, + pub name: String, +} + +#[derive(Debug)] +pub struct Remote { + pub id: u64, + pub repo_id: u64, + pub remote_path: String, + pub remote_api: String, + pub remote_url: String, + pub remote_git_url: String, + pub notifier_config_path: String, +} + +#[derive(Debug, Clone)] +pub struct Job { + pub id: u64, + pub artifacts_path: Option, + pub state: sql::JobState, + pub run_host: String, + pub remote_id: u64, + pub commit_id: u64, + pub created_time: u64, + pub start_time: Option, + pub complete_time: Option, + pub build_token: Option, + pub job_timeout: Option, + pub source: Option, + pub build_result: Option, + pub final_text: Option, +} + +#[derive(Debug, Clone)] pub struct PendingJob { pub id: u64, pub artifacts: Option, @@ -131,6 +166,17 @@ impl DbCtx { ArtifactDescriptor::new(job_id, artifact_id).await } + pub fn commit_sha(&self, commit_id: u64) -> Result { + self.conn.lock() + .unwrap() + .query_row( + "select sha from commits where id=?1", + [commit_id], + |row| { row.get(0) } + ) + .map_err(|e| e.to_string()) + } + pub fn job_for_commit(&self, sha: &str) -> Result, String> { self.conn.lock() .unwrap() @@ -268,6 +314,54 @@ impl DbCtx { Ok(artifacts) } + pub fn get_repos(&self) -> Result, String> { + let conn = self.conn.lock().unwrap(); + + let mut repos_query = conn.prepare(sql::ALL_REPOS).unwrap(); + let mut repos = repos_query.query([]).unwrap(); + let mut result = Vec::new(); + + while let Some(row) = repos.next().unwrap() { + let (id, repo_name) = row.try_into().unwrap(); + result.push(Repo { + id, + name: repo_name, + }); + } + + Ok(result) + } + + pub fn last_job_from_remote(&self, id: u64) -> Result, String> { + let conn = self.conn.lock().unwrap(); + + let mut job_query = conn.prepare(sql::LAST_JOB_FROM_REMOTE).unwrap(); + let mut result = job_query.query([id]).unwrap(); + + let job = result.next().expect("can get next row, which may be None").map(|row| { + let (id, artifacts_path, state, run_host, remote_id, commit_id, created_time, start_time, complete_time, build_token, job_timeout, source, build_result, final_text) = row.try_into().unwrap(); + let state: u8 = state; + Job { + id, + artifacts_path, + state: state.try_into().unwrap(), + run_host, + remote_id, + commit_id, + created_time, + start_time, + complete_time, + build_token, + job_timeout, + source, + build_result, + final_text, + } + }); + + Ok(job) + } + pub fn get_pending_jobs(&self) -> Result, String> { let conn = self.conn.lock().unwrap(); @@ -289,17 +383,7 @@ impl DbCtx { Ok(pending) } - pub fn notifiers_by_repo(&self, repo_id: u64) -> Result, String> { - #[derive(Debug)] - #[allow(dead_code)] - struct Remote { - id: u64, - repo_id: u64, - remote_path: String, - remote_api: String, - notifier_config_path: String, - } - + pub fn remotes_by_repo(&self, repo_id: u64) -> Result, String> { let mut remotes: Vec = Vec::new(); let conn = self.conn.lock().unwrap(); @@ -308,11 +392,15 @@ impl DbCtx { while let Some(row) = remote_results.next().unwrap() { let (id, repo_id, remote_path, remote_api, remote_url, remote_git_url, notifier_config_path) = row.try_into().unwrap(); - let _: String = remote_url; - let _: String = remote_git_url; - remotes.push(Remote { id, repo_id, remote_path, remote_api, notifier_config_path }); + remotes.push(Remote { id, repo_id, remote_path, remote_api, remote_url, remote_git_url, notifier_config_path }); } + Ok(remotes) + } + + pub fn notifiers_by_repo(&self, repo_id: u64) -> Result, String> { + let remotes = self.remotes_by_repo(repo_id)?; + let mut notifiers: Vec = Vec::new(); for remote in remotes.into_iter() { diff --git a/src/main.rs b/src/main.rs index 9c16762..7f89ced 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ #![allow(unused_variables)] #![allow(unused_imports)] +use chrono::{Utc, TimeZone}; use lazy_static::lazy_static; use std::sync::RwLock; use serde_derive::{Deserialize, Serialize}; @@ -33,7 +34,7 @@ mod dbctx; use sql::JobState; -use dbctx::DbCtx; +use dbctx::{DbCtx, Job}; use rusqlite::OptionalExtension; @@ -71,6 +72,44 @@ enum GithubEvent { Other {} } +/// return a duration rendered as the largest two non-zero units. +/// +/// 60000ms -> 1m +/// 60001ms -> 1m +/// 61000ms -> 1m1s +/// 1030ms -> 1.03s +fn duration_as_human_string(duration_ms: u64) -> String { + let duration_sec = duration_ms / 1000; + let duration_min = duration_sec / 60; + let duration_hours = duration_min / 60; + + let duration_ms = duration_ms % 1000; + let duration_sec = duration_sec & 60; + let duration_min = duration_hours & 60; + // no need to clamp hours, we're gonna just hope that it's a reasonable number of hours + + if duration_hours != 0 { + let mut res = format!("{}h", duration_hours); + if duration_min != 0 { + res.push_str(&format!("{}m", duration_min)); + } + res + } else if duration_min != 0 { + let mut res = format!("{}m", duration_min); + if duration_min != 0 { + res.push_str(&format!("{}s", duration_sec)); + } + res + } else { + let mut res = format!("{}", duration_sec); + if duration_ms != 0 { + res.push_str(&format!(".{:03}", duration_ms)); + } + res.push('s'); + res + } +} + fn parse_push_event(body: serde_json::Value) -> Result { let body = body.as_object() .ok_or(GithubHookError::BodyNotObject)?; @@ -187,7 +226,109 @@ async fn handle_github_event(ctx: Arc, owner: String, repo: String, event } async fn handle_ci_index(State(ctx): State>) -> impl IntoResponse { - "hello and welcome to my websight" + eprintln!("root index"); + let repos = match ctx.get_repos() { + Ok(repos) => repos, + Err(e) => { + eprintln!("failed to get repos: {:?}", e); + return (StatusCode::INTERNAL_SERVER_ERROR, "gonna feel that one tomorrow".to_string()); + } + }; + + let mut response = String::new(); + + response.push_str("\n"); + response.push_str("

builds and build accessories

\n"); + + match repos.len() { + 0 => { response.push_str(&format!("

no repos configured, so there are no builds

\n")); }, + 1 => { response.push_str("

1 repo configured

\n"); }, + other => { response.push_str(&format!("

{} repos configured

\n", other)); }, + } + + response.push_str("\n"); + response.push_str("\n"); + let headings = ["repo", "last build", "build commit", "duration", "status", "result"]; + for heading in headings { + response.push_str(&format!("", heading)); + } + response.push_str("\n"); + + for repo in repos { + let mut most_recent_job: Option = None; + + for remote in ctx.remotes_by_repo(repo.id).expect("remotes by repo works") { + let last_job = ctx.last_job_from_remote(remote.id).expect("job by remote works"); + if let Some(last_job) = last_job { + if most_recent_job.as_ref().map(|job| job.created_time < last_job.created_time).unwrap_or(true) { + most_recent_job = Some(last_job); + } + } + } + + let row_html: String = match most_recent_job { + Some(job) => { + let job_commit = ctx.commit_sha(job.commit_id).expect("job has a commit"); + + let last_build_time = Utc.timestamp_millis_opt(job.created_time as i64).unwrap().to_rfc2822(); + let duration = if let Some(start_time) = job.start_time { + if let Some(complete_time) = job.complete_time { + let duration_ms = complete_time - start_time; + let duration = duration_as_human_string(duration_ms); + duration + } else { + let now_ms = SystemTime::now().duration_since(UNIX_EPOCH).expect("now is after then").as_millis() as u64; + let mut duration = duration_as_human_string(now_ms - start_time); + duration.push_str(" (ongoing)"); + duration + } + } else { + "not yet run".to_owned() + }; + + let status = format!("{:?}", job.state).to_lowercase(); + + let result = match job.build_result { + Some(0) => "pass", + Some(_) => "fail", + None => match job.state { + JobState::Pending => { "unstarted" }, + JobState::Started => { "in progress" }, + _ => { "unreported" } + } + }; + + let entries = [repo.name.as_str(), last_build_time.as_str(), job_commit.as_str(), &duration, &status, &result]; + let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len()); + + let mut row_html = String::new(); + row_html.push_str(""); + for entry in entries { + row_html.push_str(&format!("", entry)); + } + row_html.push_str(""); + row_html + } + None => { + let entries = [repo.name.as_str()]; + let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len()); + + let mut row_html = String::new(); + row_html.push_str(""); + for entry in entries { + row_html.push_str(&format!("", entry)); + } + row_html.push_str(""); + row_html + } + }; + + response.push_str(&row_html); + } + + response.push_str(""); + + (StatusCode::OK, response) } async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(ctx): State>) -> impl IntoResponse { diff --git a/src/sql.rs b/src/sql.rs index 7bd33fd..b262e9a 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -106,3 +106,10 @@ pub const COMMIT_TO_ID: &'static str = "\ pub const REMOTES_FOR_REPO: &'static str = "\ select * from remotes where repo_id=?1;"; + +pub const ALL_REPOS: &'static str = "\ + select * from repos;"; + +pub const LAST_JOB_FROM_REMOTE: &'static str = "\ + select * from jobs where remote_id=?1 order by created_time desc limit 1;"; + -- cgit v1.1
{}
{}
{}