Remove old link and show number of builds

In the users locale of course. Without any annoying Accept-Language
parsing or anything like that, just with the beauty of JavaScript.
This commit is contained in:
nora 2025-07-04 22:06:40 +02:00
parent 3f302c7180
commit f6fac25c6f
4 changed files with 94 additions and 35 deletions

View file

@ -82,6 +82,17 @@ pub struct FinishedNightlyWithBroken {
pub broken_error: Option<String>, pub broken_error: Option<String>,
} }
pub struct BuildStats {
pub pass_count: u32,
pub error_count: u32,
}
impl BuildStats {
pub fn total(&self) -> u32 {
self.pass_count + self.error_count
}
}
impl Db { impl Db {
pub async fn open(path: &str) -> Result<Self> { pub async fn open(path: &str) -> Result<Self> {
let db_opts = SqliteConnectOptions::from_str(path) let db_opts = SqliteConnectOptions::from_str(path)
@ -168,6 +179,34 @@ impl Db {
.map(|elems| elems.into_iter().map(|elem| elem.nightly).collect()) .map(|elems| elems.into_iter().map(|elem| elem.nightly).collect())
} }
pub async fn build_count(&self) -> Result<BuildStats> {
#[derive(sqlx::FromRow)]
struct BuildStat {
build_count: u32,
status: Status,
}
let results = sqlx::query_as::<_, BuildStat>(
"SELECT COUNT(status) as build_count, status FROM build_info GROUP BY status",
)
.fetch_all(&self.conn)
.await
.wrap_err("getting list of all targets")?;
let count = |status| {
results
.iter()
.find(|row| row.status == status)
.map(|row| row.build_count)
.unwrap_or(0)
};
Ok(BuildStats {
pass_count: count(Status::Pass),
error_count: count(Status::Error),
})
}
pub async fn build_status_full( pub async fn build_status_full(
&self, &self,
nightly: &str, nightly: &str,

View file

@ -11,7 +11,7 @@ use color_eyre::{eyre::Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{error, info}; use tracing::{error, info};
use crate::db::{BuildInfo, BuildMode, Db, Status}; use crate::db::{BuildInfo, BuildMode, BuildStats, Db, Status};
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
@ -25,6 +25,7 @@ pub async fn webserver(db: Db) -> Result<()> {
.route("/target", get(web_target)) .route("/target", get(web_target))
.route("/nightly", get(web_nightly)) .route("/nightly", get(web_nightly))
.route("/index.css", get(index_css)) .route("/index.css", get(index_css))
.route("/index.js", get(index_js))
.with_state(AppState { db }); .with_state(AppState { db });
info!("Serving website on port 3000 (commit {})", crate::VERSION); info!("Serving website on port 3000 (commit {})", crate::VERSION);
@ -257,35 +258,37 @@ async fn web_nightly(State(state): State<AppState>, Query(query): Query<NightlyQ
async fn web_root(State(state): State<AppState>) -> impl IntoResponse { async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
use askama::Template; use askama::Template;
async fn render(state: AppState) -> Result<Response> {
#[derive(askama::Template)] #[derive(askama::Template)]
#[template(path = "index.html")] #[template(path = "index.html")]
struct RootPage { struct RootPage {
targets: Vec<String>, targets: Vec<String>,
nightlies: Vec<String>, nightlies: Vec<String>,
version: &'static str, version: &'static str,
build_count: BuildStats,
} }
match state.db.target_list().await { let targets = state.db.target_list().await?;
Ok(targets) => match state.db.nightly_list().await { let nightlies = state.db.nightly_list().await?;
Ok(nightlies) => { let build_count = state.db.build_count().await?;
let page = RootPage { let page = RootPage {
targets, targets,
nightlies, nightlies,
version: crate::VERSION, version: crate::VERSION,
build_count,
}; };
Html(page.render().unwrap()).into_response() Ok(Html(page.render().unwrap()).into_response())
} }
Err(err) => {
error!(?err, "Error loading nightly state"); render(state)
.await
.unwrap_or_else(|err: color_eyre::eyre::Error| {
error!(?err, "Error loading data for root page");
StatusCode::INTERNAL_SERVER_ERROR.into_response() StatusCode::INTERNAL_SERVER_ERROR.into_response()
} })
},
Err(err) => {
error!(?err, "Error loading target state");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
} }
async fn index_css() -> impl IntoResponse { async fn index_css() -> impl IntoResponse {
@ -298,6 +301,16 @@ async fn index_css() -> impl IntoResponse {
) )
} }
async fn index_js() -> impl IntoResponse {
(
[(
axum::http::header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("application/javascript; charset=utf-8"),
)],
include_str!("../static/index.js"),
)
}
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct TriggerBuildBody { struct TriggerBuildBody {
nightly: String, nightly: String,

4
static/index.js Normal file
View file

@ -0,0 +1,4 @@
// Do some fancy number formatting in your locale, so you can't blame me if the numbers look ass.
document.querySelectorAll(".number").forEach((elem) => {
elem.textContent = new Intl.NumberFormat().format(Number(elem.textContent));
});

View file

@ -5,21 +5,24 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Does it build?</title> <title>Does it build?</title>
<link rel="stylesheet" href="index.css" /> <link rel="stylesheet" href="index.css" />
<style></style> <script type="module" defer src="index.js"></script>
</head> </head>
<body> <body>
<h1>Does it build?</h1> <h1>Does it build?</h1>
<p> <p>
This website builds every rustc target on many nightlies to check which This website builds the standard libary for every rustc target on every
ones work and which ones do not. nightly to check whether it builds or not.
</p>
<p>
There are currently
<span class="number">{{build_count.total()}}</span> build results stored,
with <span class="number">{{build_count.pass_count}}</span> passing and
<span class="number">{{build_count.error_count}}</span> erroring.
</p> </p>
<p>You can select a target from the list below and check its history.</p>
<p> <p>
If that is not what you want, you can visit the You can select a nightly or target from the list below and check its
<a href="full-table">full table</a> to see history.
<strong>all</strong> information at once (which will really stress test
your browser).
</p> </p>
<h1>Nightlies</h1> <h1>Nightlies</h1>