aboutsummaryrefslogtreecommitdiffstats
path: root/src/api/dashboard/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/dashboard/mod.rs')
-rw-r--r--src/api/dashboard/mod.rs30
1 files changed, 26 insertions, 4 deletions
diff --git a/src/api/dashboard/mod.rs b/src/api/dashboard/mod.rs
index 1de21cd..99a0b83 100644
--- a/src/api/dashboard/mod.rs
+++ b/src/api/dashboard/mod.rs
@@ -15,7 +15,7 @@
use std::collections::{BTreeMap, BTreeSet};
use std::convert::Infallible;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration;
use axum::http::{HeaderValue, StatusCode, header::CONTENT_TYPE};
@@ -221,6 +221,7 @@ async fn build(state: &AppState, graph: &Graph) -> JsonValue {
}
json!({
"node": { "id": local, "alias": state.alias },
+ "page": page().0,
"stats": stats,
"peers": peers,
"graph": { "members": members_json, "intended": intended, "measured": measured },
@@ -312,15 +313,36 @@ fn merge_patch(old: &JsonValue, new: &JsonValue) -> Option<JsonValue> {
/// `closing` turns true when the node stops (`Hub::closing`).
pub(crate) fn router(state: AppState, closing: watch::Receiver<bool>) -> Router<AppState> {
Router::new()
- .route("/admin/dashboard", get(page))
+ .route("/admin/dashboard", get(page_html))
.route("/admin/dashboard/state", get(current_state))
.route("/admin/dashboard/events", get(events))
.layer(Extension(Hub::new(state, closing)))
}
+/// The page as built into this binary.
+const PAGE: &str = include_str!("dashboard.html");
+/// Where the page says which version of it it is.
+const PAGE_VERSION_MARK: &str = "__PAGE_VERSION__";
+
+/// The page's version (a hash of it), and the page saying so: a page whose version
+/// differs from the snapshot's `page` was served by another build, and reloads.
+fn page() -> &'static (String, String) {
+ static PAGE_SERVED: OnceLock<(String, String)> = OnceLock::new();
+ PAGE_SERVED.get_or_init(|| {
+ use sha2::{Digest, Sha256};
+ let version: String = Sha256::digest(PAGE.as_bytes())
+ .iter()
+ .take(8)
+ .map(|byte| format!("{byte:02x}"))
+ .collect();
+ let served = PAGE.replace(PAGE_VERSION_MARK, &version);
+ (version, served)
+ })
+}
+
/// `GET /admin/dashboard`: the page.
-async fn page() -> Html<&'static str> {
- Html(include_str!("dashboard.html"))
+async fn page_html() -> Html<&'static str> {
+ Html(page().1.as_str())
}
/// `GET /admin/dashboard/state`: the latest snapshot, for a client that wants it whole.