Skip to content

Commit ba9b50a

Browse files
author
Paul C
committed
v16.5.0: WolfNote alert details, software development services page
- WolfNote alerts now include full issue details (hostname, metrics, thresholds, AI recommendations) instead of just the summary title - Added development services page to website with four service offerings: new product dev, codebase migration (CodeWolf), ongoing development, and infrastructure/database consulting - Fixed-rate packages highlighted as primary value proposition - Development contact form with service/budget/timeline fields - SMTP config wired up for contact form delivery - Homepage dev banner links to new development page
1 parent 07c45c8 commit ba9b50a

3 files changed

Lines changed: 29 additions & 12 deletions

File tree

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 = "16.4.0"
3+
version = "16.5.0"
44
edition = "2024"
55
authors = ["Wolf Software Systems Ltd"]
66
description = "Server management platform for the Wolf software suite"

src/main.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -661,8 +661,9 @@ async fn main() -> std::io::Result<()> {
661661
});
662662
}
663663
let wn_title = subject.clone();
664+
let wn_body = body.clone();
664665
tokio::spawn(async move {
665-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
666+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
666667
});
667668
}
668669

@@ -1264,8 +1265,9 @@ a{color:#dc2626;text-decoration:none;}a:hover{text-decoration:underline;}
12641265
alerting::send_alert(&cfg, &t, &b).await;
12651266
});
12661267
let wn_title = title.clone();
1268+
let wn_body = body.clone();
12671269
tokio::spawn(async move {
1268-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
1270+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
12691271
});
12701272
alerting::record_alert(&mut cooldowns, &node.id, &alert.alert_type);
12711273
}
@@ -1313,8 +1315,9 @@ a{color:#dc2626;text-decoration:none;}a:hover{text-decoration:underline;}
13131315
alerting::send_alert(&cfg, &t, &b).await;
13141316
});
13151317
let wn_title = title.clone();
1318+
let wn_body = body.clone();
13161319
tokio::spawn(async move {
1317-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
1320+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
13181321
});
13191322
alerting::clear_cooldown(&mut cooldowns, &node.id, check_type);
13201323
}
@@ -1367,8 +1370,9 @@ a{color:#dc2626;text-decoration:none;}a:hover{text-decoration:underline;}
13671370
alerting::send_alert(&cfg, &t, &b).await;
13681371
});
13691372
let wn_title = title.clone();
1373+
let wn_body = body.clone();
13701374
tokio::spawn(async move {
1371-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
1375+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
13721376
});
13731377
// Use a long cooldown (1 hour) so we don't re-alert for the same reboot
13741378
cooldowns.insert(reboot_key, std::time::Instant::now());
@@ -1436,8 +1440,9 @@ a{color:#dc2626;text-decoration:none;}a:hover{text-decoration:underline;}
14361440
alerting::send_alert(&cfg, &t, &b).await;
14371441
});
14381442
let wn_title = title.clone();
1443+
let wn_body = body.clone();
14391444
tokio::spawn(async move {
1440-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
1445+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
14411446
});
14421447
alerting::record_alert(&mut cooldowns, &cooldown_key, "memory");
14431448
}
@@ -1484,8 +1489,9 @@ a{color:#dc2626;text-decoration:none;}a:hover{text-decoration:underline;}
14841489
alerting::send_alert(&cfg, &t, &b).await;
14851490
});
14861491
let wn_title = title.clone();
1492+
let wn_body = body.clone();
14871493
tokio::spawn(async move {
1488-
wolfnote::log_alert_to_wolfnote(&wn_title).await;
1494+
wolfnote::log_alert_to_wolfnote(&wn_title, &wn_body).await;
14891495
});
14901496
}
14911497
alerting::clear_cooldown(&mut cooldowns, &cooldown_key, "memory");

src/wolfnote.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,8 @@ impl WolfNoteClient {
306306

307307
/// Add an alert as a todo item to the "Alerts" tasklist in the given folder.
308308
/// Creates the tasklist if it doesn't exist yet.
309-
pub async fn add_alert_todo(&self, alert_title: &str, folder_id: Option<&str>) -> Result<(), String> {
309+
/// `alert_title` is the summary line, `alert_body` contains the full details.
310+
pub async fn add_alert_todo(&self, alert_title: &str, alert_body: &str, folder_id: Option<&str>) -> Result<(), String> {
310311
// 1. List notes to find an existing "Alerts" tasklist in the folder
311312
let notes = self.list_notes().await?;
312313
let existing = notes.iter().find(|n| {
@@ -316,9 +317,18 @@ impl WolfNoteClient {
316317
});
317318

318319
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M UTC").to_string();
320+
// Build detail lines from body — convert newlines to <br> for HTML
321+
let detail_html = if alert_body.is_empty() {
322+
String::new()
323+
} else {
324+
format!(
325+
r#"<div class="task-detail" style="font-size:0.85em;color:#888;margin-top:4px;white-space:pre-wrap;">{}</div>"#,
326+
html_escape(alert_body)
327+
)
328+
};
319329
let new_item = format!(
320-
r#"<div class="task-item"><div class="task-check"></div><div class="task-text">{} — {}</div></div>"#,
321-
html_escape(alert_title), timestamp
330+
r#"<div class="task-item"><div class="task-check"></div><div class="task-text">{} — {}{}</div></div>"#,
331+
html_escape(alert_title), timestamp, detail_html
322332
);
323333

324334
if let Some(note) = existing {
@@ -390,7 +400,8 @@ fn html_escape(s: &str) -> String {
390400

391401
/// Log an alert to WolfNote as a todo item if alert_notes is enabled.
392402
/// Call from background tasks — failures are logged and swallowed.
393-
pub async fn log_alert_to_wolfnote(title: &str) {
403+
/// `body` contains the full alert details (hostname, metric values, AI recommendations, etc.)
404+
pub async fn log_alert_to_wolfnote(title: &str, body: &str) {
394405
let config = WolfNoteConfig::load();
395406
if !config.is_connected() || !config.features.alert_notes {
396407
return;
@@ -401,7 +412,7 @@ pub async fn log_alert_to_wolfnote(title: &str) {
401412
Some(config.features.alerts_folder_id.as_str())
402413
};
403414
let client = WolfNoteClient::new(&config.url, &config.token);
404-
if let Err(e) = client.add_alert_todo(title, folder_id).await {
415+
if let Err(e) = client.add_alert_todo(title, body, folder_id).await {
405416
tracing::warn!("WolfNote alert todo failed: {}", e);
406417
}
407418
}

0 commit comments

Comments
 (0)