Skip to content

Commit 69a12ae

Browse files
author
Paul C
committed
Merge beta: v25.12.4 — Settings > Diagnostics, one button for the numbers support actually needs
2 parents e0c6963 + e760439 commit 69a12ae

7 files changed

Lines changed: 1190 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "wolfstack"
3-
version = "25.12.3"
3+
version = "25.12.4"
44
edition = "2024"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Server management platform for the Wolf software suite"

src/api/mod.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44768,6 +44768,67 @@ fn serde_qs_from_map(
4476844768
})
4476944769
}
4477044770

44771+
/// Strip a hostname down to what is safe inside a `Content-Disposition`
44772+
/// filename. A hostname is operator-controlled, and CR/LF or a quote in a
44773+
/// response header is header injection — so this allows only characters
44774+
/// that cannot terminate or escape the header value.
44775+
pub(crate) fn sanitise_filename_part(raw: &str) -> String {
44776+
let cleaned: String = raw
44777+
.chars()
44778+
.map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '-' })
44779+
.collect();
44780+
let trimmed = cleaned.trim_matches('-').to_string();
44781+
if trimmed.is_empty() {
44782+
"wolfstack".to_string()
44783+
} else {
44784+
trimmed.chars().take(64).collect()
44785+
}
44786+
}
44787+
44788+
/// GET /api/diagnostics — collect a full self-diagnostic.
44789+
///
44790+
/// Returns the structured report for on-screen display AND the rendered
44791+
/// text bundle in the same response, so what the operator reads and what
44792+
/// they send us are the same snapshot. Rendering server-side also keeps
44793+
/// the one formatter in one place rather than duplicating it in JS.
44794+
///
44795+
/// Admin-only: the report exposes peer addresses, thread layout and
44796+
/// journal lines. It deliberately contains nothing from /etc/wolfstack,
44797+
/// so no cluster secret or credential can ride along.
44798+
pub async fn diagnostics_collect(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
44799+
let caller = match require_auth(&req, &state) { Ok(u) => u, Err(resp) => return resp };
44800+
if !crate::auth::session_user_is_admin(&caller) {
44801+
return HttpResponse::Forbidden().json(serde_json::json!({
44802+
"error": "Only admin users can run diagnostics"
44803+
}));
44804+
}
44805+
44806+
// Blocking: samples CPU for ~1.5s and shells out to journalctl once.
44807+
// Never run that on a runtime worker.
44808+
let report = match web::block(crate::diagnostics::collect).await {
44809+
Ok(r) => r,
44810+
Err(e) => {
44811+
error!("diagnostics collection failed: {e}");
44812+
return HttpResponse::InternalServerError().json(serde_json::json!({
44813+
"error": format!("Diagnostic collection failed: {e}")
44814+
}));
44815+
}
44816+
};
44817+
44818+
let text = crate::diagnostics::render_text(&report);
44819+
let filename = format!(
44820+
"wolfstack-diagnostic-{}-{}.txt",
44821+
sanitise_filename_part(&report.context.hostname),
44822+
chrono::Utc::now().format("%Y%m%d-%H%M%S"),
44823+
);
44824+
44825+
HttpResponse::Ok().json(serde_json::json!({
44826+
"report": report,
44827+
"text": text,
44828+
"filename": filename,
44829+
}))
44830+
}
44831+
4477144832
/// Configure all API routes
4477244833
pub fn configure(cfg: &mut web::ServiceConfig) {
4477344834
cfg
@@ -44845,6 +44906,8 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
4484544906
.route("/api/auth/smtp-configured", web::get().to(smtp_configured))
4484644907
.route("/api/auth/forgot-password", web::post().to(forgot_password))
4484744908
.route("/api/auth/reset-password", web::post().to(reset_password))
44909+
// Self-diagnostics (Settings > Diagnostics). Admin-only.
44910+
.route("/api/diagnostics", web::get().to(diagnostics_collect))
4484844911
// Support tickets — proxied to wolfstack.org, auth = dashboard session
4484944912
.route("/api/support/entitlement", web::get().to(support_entitlement))
4485044913
.route("/api/support/tickets", web::get().to(support_tickets_list))

0 commit comments

Comments
 (0)