summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoriximeow <me@iximeow.net>2023-07-17 23:03:29 -0700
committeriximeow <me@iximeow.net>2023-07-17 23:03:29 -0700
commit128bc8d1146c6c9553ac6c5185e21e00ddcc6829 (patch)
tree902606b0471c3cf30f5c6c20ffd6e3e0f2a62cb4
parentcbe41fb33d06410b71a6ae716d6baf14bccaf103 (diff)
start factoring out web view rendering
-rw-r--r--Cargo.lock11
-rw-r--r--ci-lib-core/src/dbctx.rs44
-rw-r--r--ci-lib-web/Cargo.toml15
-rw-r--r--ci-lib-web/src/lib.rs287
-rw-r--r--ci-web-server/Cargo.toml1
-rw-r--r--ci-web-server/src/main.rs295
6 files changed, 348 insertions, 305 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 729b789..1ea5f9a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -367,6 +367,16 @@ dependencies = [
]
[[package]]
+name = "ci-lib-web"
+version = "0.0.1"
+dependencies = [
+ "chrono",
+ "ci-lib-core",
+ "rusqlite",
+ "serde",
+]
+
+[[package]]
name = "ci-runner"
version = "0.0.1"
dependencies = [
@@ -394,6 +404,7 @@ dependencies = [
"chrono",
"ci-lib-core",
"ci-lib-native",
+ "ci-lib-web",
"hex",
"hmac",
"http",
diff --git a/ci-lib-core/src/dbctx.rs b/ci-lib-core/src/dbctx.rs
index 7493030..eec5e72 100644
--- a/ci-lib-core/src/dbctx.rs
+++ b/ci-lib-core/src/dbctx.rs
@@ -1,6 +1,6 @@
use std::sync::Mutex;
// use futures_util::StreamExt;
-use rusqlite::{Connection, OptionalExtension};
+use rusqlite::{params, Connection, OptionalExtension};
use std::time::{SystemTime, UNIX_EPOCH};
// use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::path::Path;
@@ -40,16 +40,16 @@ impl DbCtx {
pub fn create_tables(&self) -> Result<(), ()> {
let conn = self.conn.lock().unwrap();
- conn.execute(sql::CREATE_ARTIFACTS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_JOBS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_METRICS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_COMMITS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_REPOS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_REPO_NAME_INDEX, ()).unwrap();
- conn.execute(sql::CREATE_REMOTES_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_REMOTES_INDEX, ()).unwrap();
- conn.execute(sql::CREATE_RUNS_TABLE, ()).unwrap();
- conn.execute(sql::CREATE_HOSTS_TABLE, ()).unwrap();
+ conn.execute(sql::CREATE_ARTIFACTS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_JOBS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_METRICS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_COMMITS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_REPOS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_REPO_NAME_INDEX, params![]).unwrap();
+ conn.execute(sql::CREATE_REMOTES_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_REMOTES_INDEX, params![]).unwrap();
+ conn.execute(sql::CREATE_RUNS_TABLE, params![]).unwrap();
+ conn.execute(sql::CREATE_HOSTS_TABLE, params![]).unwrap();
Ok(())
}
@@ -59,7 +59,7 @@ impl DbCtx {
conn
.execute(
"insert into metrics (run_id, name, value) values (?1, ?2, ?3) on conflict (run_id, name) do update set value=excluded.value",
- (run_id, name, value)
+ params![run_id, name, value]
)
.expect("can upsert");
Ok(())
@@ -96,7 +96,7 @@ impl DbCtx {
conn
.execute(
"update artifacts set completed_time=?1 where id=?2",
- (crate::now_ms(), artifact_id)
+ params![crate::now_ms(), artifact_id]
)
.map(|_| ())
.map_err(|e| {
@@ -245,7 +245,7 @@ impl DbCtx {
conn
.execute(
"insert into remotes (repo_id, remote_path, remote_api, remote_url, remote_git_url, notifier_config_path) values (?1, ?2, ?3, ?4, ?5, ?6);",
- (repo_id, remote_path, remote_api, remote_url, remote_git_url, config_path)
+ params![repo_id, remote_path, remote_api, remote_url, remote_git_url, config_path]
)
.expect("can insert");
@@ -267,7 +267,7 @@ impl DbCtx {
let rows_modified = conn.execute(
"insert into jobs (remote_id, commit_id, created_time, source, run_preferences) values (?1, ?2, ?3, ?4, ?5);",
- (remote_id, commit_id, created_time, pusher, repo_default_run_pref)
+ params![remote_id, commit_id, created_time, pusher, repo_default_run_pref]
).unwrap();
assert_eq!(1, rows_modified);
@@ -287,7 +287,7 @@ impl DbCtx {
let rows_modified = conn.execute(
"insert into runs (job_id, state, created_time, host_preference) values (?1, ?2, ?3, ?4);",
- (job_id, crate::sql::RunState::Pending as u64, created_time, host_preference)
+ params![job_id, crate::sql::RunState::Pending as u64, created_time, host_preference]
).unwrap();
assert_eq!(1, rows_modified);
@@ -511,7 +511,7 @@ impl DbCtx {
hostname=?1 and cpu_vendor_id=?2 and cpu_model_name=?3 and cpu_family=?4 and \
cpu_model=?5 and cpu_max_freq_khz=?6 and cpu_cores=?7 and mem_total=?8 and \
arch=?9);",
- (
+ params![
&host_info.hostname,
&host_info.cpu_info.vendor_id,
&host_info.cpu_info.model_name,
@@ -521,7 +521,7 @@ impl DbCtx {
&host_info.cpu_info.cores,
&host_info.memory_info.total,
&host_info.env_info.arch,
- ),
+ ],
|row| { row.get(0) }
)
.map_err(|e| e.to_string())
@@ -544,7 +544,7 @@ impl DbCtx {
?5, ?6, ?7, ?8, \
?9, ?10, ?11, ?12 \
);",
- (
+ params![
&host_info.hostname,
&host_info.cpu_info.vendor_id,
&host_info.cpu_info.model_name,
@@ -557,7 +557,7 @@ impl DbCtx {
&host_info.env_info.arch,
&host_info.env_info.family,
&host_info.env_info.os,
- )
+ ]
)
.expect("can insert");
@@ -567,7 +567,7 @@ impl DbCtx {
hostname=?1 and cpu_vendor_id=?2 and cpu_model_name=?3 and cpu_family=?4 and \
cpu_model=?5 and cpu_microcode=?6 and cpu_max_freq_khz=?7 and \
cpu_cores=?8 and mem_total=?9 and arch=?10 and family=?11 and os=?12;",
- (
+ params![
&host_info.hostname,
&host_info.cpu_info.vendor_id,
&host_info.cpu_info.model_name,
@@ -580,7 +580,7 @@ impl DbCtx {
&host_info.env_info.arch,
&host_info.env_info.family,
&host_info.env_info.os,
- ),
+ ],
|row| { row.get(0) }
)
.map_err(|e| e.to_string())
diff --git a/ci-lib-web/Cargo.toml b/ci-lib-web/Cargo.toml
new file mode 100644
index 0000000..d6c1acc
--- /dev/null
+++ b/ci-lib-web/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ci-lib-web"
+version = "0.0.1"
+authors = [ "iximeow <me@iximeow.net>" ]
+license = "0BSD"
+edition = "2021"
+description = "shared code supporting web views of state.db"
+
+[lib]
+
+[dependencies]
+ci-lib-core = { path = "../ci-lib-core" }
+serde = { version = "*", features = ["derive"] }
+rusqlite = { version = "*", features = ["bundled"] }
+chrono = "*"
diff --git a/ci-lib-web/src/lib.rs b/ci-lib-web/src/lib.rs
new file mode 100644
index 0000000..c6bb3b1
--- /dev/null
+++ b/ci-lib-web/src/lib.rs
@@ -0,0 +1,287 @@
+use std::sync::Arc;
+
+use chrono::{Utc, TimeZone};
+
+use ci_lib_core::dbctx::DbCtx;
+use ci_lib_core::sql::{Job, Run, RunState};
+
+/// return a duration rendered as the largest two non-zero units.
+///
+/// 60000ms -> 1m
+/// 60001ms -> 1m
+/// 61000ms -> 1m1s
+/// 1030ms -> 1.03s
+pub 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_min % 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
+ }
+}
+
+/// try producing a url for whatever caused this job to be started, if possible
+pub fn commit_url(job: &Job, commit_sha: &str, ctx: &Arc<DbCtx>) -> Option<String> {
+ let remote = ctx.remote_by_id(job.remote_id).expect("query succeeds").expect("existing job references existing remote");
+
+ match remote.remote_api.as_str() {
+ "github" => {
+ Some(format!("{}/commit/{}", remote.remote_url, commit_sha))
+ },
+ "email" => {
+ None
+ },
+ _ => {
+ None
+ }
+ }
+}
+
+/// produce a url to the ci.butactuallyin.space job details page
+pub fn job_url(job: &Job, commit_sha: &str, ctx: &Arc<DbCtx>) -> String {
+ let remote = ctx.remote_by_id(job.remote_id).expect("query succeeds").expect("existing job references existing remote");
+
+ if remote.remote_api != "github" {
+ eprintln!("job url for remote type {} can't be constructed, i think", &remote.remote_api);
+ }
+
+ format!("{}/{}", &remote.remote_path, commit_sha)
+}
+
+/// render how long a run took, or is taking, in a human-friendly way
+pub fn display_run_time(run: &Run) -> String {
+ if let Some(start_time) = run.start_time {
+ if let Some(complete_time) = run.complete_time {
+ if complete_time < start_time {
+ if run.state == RunState::Started {
+ // this run has been restarted. the completed time is stale.
+ // further, this is a currently active run.
+ let now_ms = ci_lib_core::now_ms();
+ let mut duration = duration_as_human_string(now_ms - start_time);
+ duration.push_str(" (ongoing)");
+ duration
+ } else {
+ "invalid data".to_string()
+ }
+ } else {
+ let duration_ms = complete_time - start_time;
+ let duration = duration_as_human_string(duration_ms);
+ duration
+ }
+ } else {
+ if run.state != RunState::Invalid {
+ let now_ms = ci_lib_core::now_ms();
+ let mut duration = duration_as_human_string(now_ms - start_time);
+ duration.push_str(" (ongoing)");
+ duration
+ } else {
+ "n/a".to_string()
+ }
+ }
+ } else {
+ "not yet run".to_owned()
+ }
+}
+
+pub fn build_repo_index(ctx: &Arc<DbCtx>) -> Result<String, String> {
+ let repos = match ctx.get_repos() {
+ Ok(repos) => repos,
+ Err(e) => {
+ eprintln!("failed to get repos: {:?}", e);
+ return Err("gonna feel that one tomorrow".to_string());
+ }
+ };
+
+ let mut response = String::new();
+
+ response.push_str("<html>\n");
+ response.push_str("<style>\n");
+ response.push_str(".build-table { font-family: monospace; border: 1px solid black; border-collapse: collapse; }\n");
+ response.push_str(".row-item { padding-left: 4px; padding-right: 4px; border-right: 1px solid black; }\n");
+ response.push_str(".odd-row { background: #eee; }\n");
+ response.push_str(".even-row { background: #ddd; }\n");
+ response.push_str("</style>\n");
+ response.push_str("<h1>builds and build accessories</h1>\n");
+
+ match repos.len() {
+ 0 => { response.push_str(&format!("<p>no repos configured, so there are no builds</p>\n")); },
+ 1 => { response.push_str("<p>1 repo configured</p>\n"); },
+ other => { response.push_str(&format!("<p>{} repos configured</p>\n", other)); },
+ }
+
+ response.push_str("<table class='build-table'>");
+ response.push_str("<tr>\n");
+ let headings = ["repo", "last build", "job", "build commit", "duration", "status", "result"];
+ for heading in headings {
+ response.push_str(&format!("<th class='row-item'>{}</th>", heading));
+ }
+ response.push_str("</tr>\n");
+
+ let mut row_num = 0;
+
+ for repo in repos {
+ let mut most_recent_run: Option<(Job, Run)> = 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 let Some(last_run) = ctx.last_run_for_job(last_job.id).expect("can query") {
+ if most_recent_run.as_ref().map(|run| run.1.create_time < last_run.create_time).unwrap_or(true) {
+ most_recent_run = Some((last_job, last_run));
+ }
+ }
+ }
+ }
+
+ let repo_html = format!("<a href=\"/{}\">{}</a>", &repo.name, &repo.name);
+
+ let row_html: String = match most_recent_run {
+ Some((job, run)) => {
+ let job_commit = ctx.commit_sha(job.commit_id).expect("job has a commit");
+ let commit_html = match commit_url(&job, &job_commit, &ctx) {
+ Some(url) => format!("<a href=\"{}\">{}</a>", url, &job_commit),
+ None => job_commit.clone()
+ };
+
+ let job_html = format!("<a href=\"{}\">{}</a>", job_url(&job, &job_commit, &ctx), job.id);
+
+ let last_build_time = Utc.timestamp_millis_opt(run.create_time as i64).unwrap().to_rfc2822();
+ let duration = display_run_time(&run);
+
+ let status = format!("{:?}", run.state).to_lowercase();
+
+ let result = match run.build_result {
+ Some(0) => "<span style='color:green;'>pass</span>",
+ Some(_) => "<span style='color:red;'>fail</span>",
+ None => match run.state {
+ RunState::Pending => { "unstarted" },
+ RunState::Started => { "<span style='color:darkgoldenrod;'>in progress</span>" },
+ _ => { "<span style='color:red;'>unreported</span>" }
+ }
+ };
+
+ let entries = [repo_html.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result];
+ let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
+
+ let mut row_html = String::new();
+ for entry in entries {
+ row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
+ }
+ row_html
+ }
+ None => {
+ let entries = [repo_html.as_str()];
+ let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
+
+ let mut row_html = String::new();
+ for entry in entries {
+ row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
+ }
+ row_html
+ }
+ };
+
+ let row_index = row_num % 2;
+ response.push_str(&format!("<tr class=\"{}\">", ["even-row", "odd-row"][row_index]));
+ response.push_str(&row_html);
+ response.push_str("</tr>");
+ response.push('\n');
+
+ row_num += 1;
+ }
+ response.push_str("</table>");
+
+ response.push_str("<h4>active tasks</h4>\n");
+
+ let runs = ctx.get_active_runs().expect("can query");
+ if runs.len() == 0 {
+ response.push_str("<p>(none)</p>\n");
+ } else {
+ response.push_str("<table class='build-table'>");
+ response.push_str("<tr>\n");
+ let headings = ["repo", "last build", "job", "build commit", "duration", "status", "result"];
+ for heading in headings {
+ response.push_str(&format!("<th class='row-item'>{}</th>", heading));
+ }
+ response.push_str("</tr>\n");
+
+ let mut row_num = 0;
+
+ for run in runs.iter() {
+ let row_index = row_num % 2;
+
+ let job = ctx.job_by_id(run.job_id).expect("query succeeds").expect("job id is valid");
+ let remote = ctx.remote_by_id(job.remote_id).expect("query succeeds").expect("remote id is valid");
+ let repo = ctx.repo_by_id(remote.repo_id).expect("query succeeds").expect("repo id is valid");
+
+ let repo_html = format!("<a href=\"/{}\">{}</a>", &repo.name, &repo.name);
+
+ let job_commit = ctx.commit_sha(job.commit_id).expect("job has a commit");
+ let commit_html = match commit_url(&job, &job_commit, &ctx) {
+ Some(url) => format!("<a href=\"{}\">{}</a>", url, &job_commit),
+ None => job_commit.clone()
+ };
+
+ let job_html = format!("<a href=\"{}\">{}</a>", job_url(&job, &job_commit, &ctx), job.id);
+
+ let last_build_time = Utc.timestamp_millis_opt(run.create_time as i64).unwrap().to_rfc2822();
+ let duration = display_run_time(&run);
+
+ let status = format!("{:?}", run.state).to_lowercase();
+
+ let result = match run.build_result {
+ Some(0) => "<span style='color:green;'>pass</span>",
+ Some(_) => "<span style='color:red;'>fail</span>",
+ None => match run.state {
+ RunState::Pending => { "unstarted" },
+ RunState::Started => { "<span style='color:darkgoldenrod;'>in progress</span>" },
+ _ => { "<span style='color:red;'>unreported</span>" }
+ }
+ };
+
+ let entries = [repo_html.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result];
+ let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
+
+ let mut row_html = String::new();
+ for entry in entries {
+ row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
+ }
+
+
+ response.push_str(&format!("<tr class=\"{}\">", ["even-row", "odd-row"][row_index]));
+ response.push_str(&row_html);
+ response.push_str("</tr>");
+ response.push('\n');
+
+ row_num += 1;
+ }
+
+ response.push_str("</table>\n");
+ }
+
+ response.push_str("</html>");
+ Ok(response)
+}
diff --git a/ci-web-server/Cargo.toml b/ci-web-server/Cargo.toml
index 2771e3a..bb0b57c 100644
--- a/ci-web-server/Cargo.toml
+++ b/ci-web-server/Cargo.toml
@@ -12,6 +12,7 @@ path = "src/main.rs"
[dependencies]
ci-lib-core = { path = "../ci-lib-core" }
ci-lib-native = { path = "../ci-lib-native" }
+ci-lib-web = { path = "../ci-lib-web" }
tokio = { features = ["full"] }
tokio-stream = "*"
diff --git a/ci-web-server/src/main.rs b/ci-web-server/src/main.rs
index e2be54f..31e7a8c 100644
--- a/ci-web-server/src/main.rs
+++ b/ci-web-server/src/main.rs
@@ -79,107 +79,6 @@ 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_min % 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
- }
-}
-
-/// try producing a url for whatever caused this job to be started, if possible
-fn commit_url(job: &Job, commit_sha: &str, ctx: &Arc<DbCtx>) -> Option<String> {
- let remote = ctx.remote_by_id(job.remote_id).expect("query succeeds").expect("existing job references existing remote");
-
- match remote.remote_api.as_str() {
- "github" => {
- Some(format!("{}/commit/{}", remote.remote_url, commit_sha))
- },
- "email" => {
- None
- },
- _ => {
- None
- }
- }
-}
-
-/// produce a url to the ci.butactuallyin.space job details page
-fn job_url(job: &Job, commit_sha: &str, ctx: &Arc<DbCtx>) -> String {
- let remote = ctx.remote_by_id(job.remote_id).expect("query succeeds").expect("existing job references existing remote");
-
- if remote.remote_api != "github" {
- eprintln!("job url for remote type {} can't be constructed, i think", &remote.remote_api);
- }
-
- format!("{}/{}", &remote.remote_path, commit_sha)
-}
-
-/// render how long a run took, or is taking, in a human-friendly way
-fn display_run_time(run: &Run) -> String {
- if let Some(start_time) = run.start_time {
- if let Some(complete_time) = run.complete_time {
- if complete_time < start_time {
- if run.state == RunState::Started {
- // this run has been restarted. the completed time is stale.
- // further, this is a currently active run.
- let now_ms = ci_lib_core::now_ms();
- let mut duration = duration_as_human_string(now_ms - start_time);
- duration.push_str(" (ongoing)");
- duration
- } else {
- "invalid data".to_string()
- }
- } else {
- let duration_ms = complete_time - start_time;
- let duration = duration_as_human_string(duration_ms);
- duration
- }
- } else {
- if run.state != RunState::Invalid {
- let now_ms = ci_lib_core::now_ms();
- let mut duration = duration_as_human_string(now_ms - start_time);
- duration.push_str(" (ongoing)");
- duration
- } else {
- "n/a".to_string()
- }
- }
- } else {
- "not yet run".to_owned()
- }
-}
-
fn parse_push_event(body: serde_json::Value) -> Result<GithubEvent, GithubHookError> {
let body = body.as_object()
.ok_or(GithubHookError::BodyNotObject)?;
@@ -304,184 +203,14 @@ async fn handle_github_event(ctx: Arc<DbCtx>, owner: String, repo: String, event
async fn handle_ci_index(State(ctx): State<WebserverState>) -> impl IntoResponse {
eprintln!("root index");
- let repos = match ctx.dbctx.get_repos() {
- Ok(repos) => repos,
- Err(e) => {
- eprintln!("failed to get repos: {:?}", e);
- return (StatusCode::INTERNAL_SERVER_ERROR, Html("gonna feel that one tomorrow".to_string()));
- }
- };
-
- let mut response = String::new();
-
- response.push_str("<html>\n");
- response.push_str("<style>\n");
- response.push_str(".build-table { font-family: monospace; border: 1px solid black; border-collapse: collapse; }\n");
- response.push_str(".row-item { padding-left: 4px; padding-right: 4px; border-right: 1px solid black; }\n");
- response.push_str(".odd-row { background: #eee; }\n");
- response.push_str(".even-row { background: #ddd; }\n");
- response.push_str("</style>\n");
- response.push_str("<h1>builds and build accessories</h1>\n");
-
- match repos.len() {
- 0 => { response.push_str(&format!("<p>no repos configured, so there are no builds</p>\n")); },
- 1 => { response.push_str("<p>1 repo configured</p>\n"); },
- other => { response.push_str(&format!("<p>{} repos configured</p>\n", other)); },
- }
-
- response.push_str("<table class='build-table'>");
- response.push_str("<tr>\n");
- let headings = ["repo", "last build", "job", "build commit", "duration", "status", "result"];
- for heading in headings {
- response.push_str(&format!("<th class='row-item'>{}</th>", heading));
- }
- response.push_str("</tr>\n");
-
- let mut row_num = 0;
-
- for repo in repos {
- let mut most_recent_run: Option<(Job, Run)> = None;
-
- for remote in ctx.dbctx.remotes_by_repo(repo.id).expect("remotes by repo works") {
- let last_job = ctx.dbctx.last_job_from_remote(remote.id).expect("job by remote works");
- if let Some(last_job) = last_job {
- if let Some(last_run) = ctx.dbctx.last_run_for_job(last_job.id).expect("can query") {
- if most_recent_run.as_ref().map(|run| run.1.create_time < last_run.create_time).unwrap_or(true) {
- most_recent_run = Some((last_job, last_run));
- }
- }
- }
- }
-
- let repo_html = format!("<a href=\"/{}\">{}</a>", &repo.name, &repo.name);
-
- let row_html: String = match most_recent_run {
- Some((job, run)) => {
- let job_commit = ctx.dbctx.commit_sha(job.commit_id).expect("job has a commit");
- let commit_html = match commit_url(&job, &job_commit, &ctx.dbctx) {
- Some(url) => format!("<a href=\"{}\">{}</a>", url, &job_commit),
- None => job_commit.clone()
- };
-
- let job_html = format!("<a href=\"{}\">{}</a>", job_url(&job, &job_commit, &ctx.dbctx), job.id);
-
- let last_build_time = Utc.timestamp_millis_opt(run.create_time as i64).unwrap().to_rfc2822();
- let duration = display_run_time(&run);
-
- let status = format!("{:?}", run.state).to_lowercase();
-
- let result = match run.build_result {
- Some(0) => "<span style='color:green;'>pass</span>",
- Some(_) => "<span style='color:red;'>fail</span>",
- None => match run.state {
- RunState::Pending => { "unstarted" },
- RunState::Started => { "<span style='color:darkgoldenrod;'>in progress</span>" },
- _ => { "<span style='color:red;'>unreported</span>" }
- }
- };
-
- let entries = [repo_html.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result];
- let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
-
- let mut row_html = String::new();
- for entry in entries {
- row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
- }
- row_html
- }
- None => {
- let entries = [repo_html.as_str()];
- let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
-
- let mut row_html = String::new();
- for entry in entries {
- row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
- }
- row_html
- }
- };
-
- let row_index = row_num % 2;
- response.push_str(&format!("<tr class=\"{}\">", ["even-row", "odd-row"][row_index]));
- response.push_str(&row_html);
- response.push_str("</tr>");
- response.push('\n');
-
- row_num += 1;
- }
- response.push_str("</table>");
-
- response.push_str("<h4>active tasks</h4>\n");
-
- let runs = ctx.dbctx.get_active_runs().expect("can query");
- if runs.len() == 0 {
- response.push_str("<p>(none)</p>\n");
- } else {
- response.push_str("<table class='build-table'>");
- response.push_str("<tr>\n");
- let headings = ["repo", "last build", "job", "build commit", "duration", "status", "result"];
- for heading in headings {
- response.push_str(&format!("<th class='row-item'>{}</th>", heading));
+ match ci_lib_web::build_repo_index(&ctx.dbctx) {
+ Ok(html) => {
+ (StatusCode::OK, Html(html))
}
- response.push_str("</tr>\n");
-
- let mut row_num = 0;
-
- for run in runs.iter() {
- let row_index = row_num % 2;
-
- let job = ctx.dbctx.job_by_id(run.job_id).expect("query succeeds").expect("job id is valid");
- let remote = ctx.dbctx.remote_by_id(job.remote_id).expect("query succeeds").expect("remote id is valid");
- let repo = ctx.dbctx.repo_by_id(remote.repo_id).expect("query succeeds").expect("repo id is valid");
-
- let repo_html = format!("<a href=\"/{}\">{}</a>", &repo.name, &repo.name);
-
- let job_commit = ctx.dbctx.commit_sha(job.commit_id).expect("job has a commit");
- let commit_html = match commit_url(&job, &job_commit, &ctx.dbctx) {
- Some(url) => format!("<a href=\"{}\">{}</a>", url, &job_commit),
- None => job_commit.clone()
- };
-
- let job_html = format!("<a href=\"{}\">{}</a>", job_url(&job, &job_commit, &ctx.dbctx), job.id);
-
- let last_build_time = Utc.timestamp_millis_opt(run.create_time as i64).unwrap().to_rfc2822();
- let duration = display_run_time(&run);
-
- let status = format!("{:?}", run.state).to_lowercase();
-
- let result = match run.build_result {
- Some(0) => "<span style='color:green;'>pass</span>",
- Some(_) => "<span style='color:red;'>fail</span>",
- None => match run.state {
- RunState::Pending => { "unstarted" },
- RunState::Started => { "<span style='color:darkgoldenrod;'>in progress</span>" },
- _ => { "<span style='color:red;'>unreported</span>" }
- }
- };
-
- let entries = [repo_html.as_str(), last_build_time.as_str(), job_html.as_str(), commit_html.as_str(), &duration, &status, &result];
- let entries = entries.iter().chain(std::iter::repeat(&"")).take(headings.len());
-
- let mut row_html = String::new();
- for entry in entries {
- row_html.push_str(&format!("<td class='row-item'>{}</td>", entry));
- }
-
-
- response.push_str(&format!("<tr class=\"{}\">", ["even-row", "odd-row"][row_index]));
- response.push_str(&row_html);
- response.push_str("</tr>");
- response.push('\n');
-
- row_num += 1;
+ Err(e) => {
+ (StatusCode::INTERNAL_SERVER_ERROR, Html(e))
}
-
- response.push_str("</table>\n");
}
-
- response.push_str("</html>");
-
- (StatusCode::OK, Html(response))
}
async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(ctx): State<WebserverState>) -> impl IntoResponse {
@@ -560,7 +289,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
short_sha,
path.0, path.1,
status_desc,
- display_run_time(&run)
+ ci_lib_web::display_run_time(&run)
);
head.push_str(&format!("<meta name=\"description\" content=\"{}\"\n>", build_og_description));
head.push_str(&format!("<meta property=\"og:description\" content=\"{}\"\n>", build_og_description));
@@ -589,7 +318,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
for artifact in old_artifacts.iter() {
let created_time_str = Utc.timestamp_millis_opt(artifact.created_time as i64).unwrap().to_rfc2822();
artifacts_fragment.push_str(&format!("<div><pre style='display:inline;'>{}</pre> step: <pre style='display:inline;'>{}</pre></div>\n", created_time_str, &artifact.name));
- let duration_str = duration_as_human_string(artifact.completed_time.unwrap_or_else(ci_lib_core::now_ms) - artifact.created_time);
+ let duration_str = ci_lib_web::duration_as_human_string(artifact.completed_time.unwrap_or_else(ci_lib_core::now_ms) - artifact.created_time);
let size_str = (std::fs::metadata(&format!("./artifacts/{}/{}", artifact.run_id, artifact.id)).expect("metadata exists").len() / 1024).to_string();
artifacts_fragment.push_str(&format!("<pre> {}kb in {} </pre>\n", size_str, duration_str));
}
@@ -602,7 +331,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
artifacts_fragment.push_str(&std::fs::read_to_string(format!("./artifacts/{}/{}", artifact.run_id, artifact.id)).unwrap());
artifacts_fragment.push_str("</pre>\n");
} else {
- let duration_str = duration_as_human_string(artifact.completed_time.unwrap_or_else(ci_lib_core::now_ms) - artifact.created_time);
+ let duration_str = ci_lib_web::duration_as_human_string(artifact.completed_time.unwrap_or_else(ci_lib_core::now_ms) - artifact.created_time);
let size_str = std::fs::metadata(&format!("./artifacts/{}/{}", artifact.run_id, artifact.id)).map(|md| {
(md.len() / 1024).to_string()
}).unwrap_or_else(|e| format!("[{}]", e));
@@ -619,7 +348,7 @@ async fn handle_commit_status(Path(path): Path<(String, String, String)>, State(
html.push_str(" <pre>\n");
html.push_str(&format!("repo: {}\n", repo_html));
html.push_str(&format!("commit: {}, run: {}\n", remote_commit_elem, run.id));
- html.push_str(&format!("status: {} in {}\n", status_elem, display_run_time(&run)));
+ html.push_str(&format!("status: {} in {}\n", status_elem, ci_lib_web::display_run_time(&run)));
if let Some(desc) = run.final_text.as_ref() {
html.push_str(&format!(" description: {}\n ", desc));
}
@@ -827,15 +556,15 @@ async fn handle_repo_summary(Path(path): Path<String>, State(ctx): State<Webserv
for job in last_builds.iter().take(10) {
let run = ctx.dbctx.last_run_for_job(job.id).expect("query succeeds").expect("TODO: run exists if job exists (small race if querying while creating job ....");
let job_commit = ctx.dbctx.commit_sha(job.commit_id).expect("job has a commit");
- let commit_html = match commit_url(&job, &job_commit, &ctx.dbctx) {
+ let commit_html = match ci_lib_web::commit_url(&job, &job_commit, &ctx.dbctx) {
Some(url) => format!("<a href=\"{}\">{}</a>", url, &job_commit),
None => job_commit.clone()
};
- let job_html = format!("<a href=\"{}\">{}</a>", job_url(&job, &job_commit, &ctx.dbctx), job.id);
+ let job_html = format!("<a href=\"{}\">{}</a>", ci_lib_web::job_url(&job, &job_commit, &ctx.dbctx), job.id);
let last_build_time = Utc.timestamp_millis_opt(run.create_time as i64).unwrap().to_rfc2822();
- let duration = display_run_time(&run);
+ let duration = ci_lib_web::display_run_time(&run);
let status = format!("{:?}", run.state).to_lowercase();