feat(openwrt): add WAN diagnostics to get-status and UI

get_wan_status now returns: radio0_disabled, sta_iface (from iw dev),
sta_state (operstate), assoc_ssid (actually associated SSID vs
configured), and recent wifi_log lines from logread. The WAN panel
shows a diagnostic grid when configured but not connected so the user
can see exactly what's wrong without digging into server logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 17:12:57 +00:00
co-authored by Claude Sonnet 4.6
parent 33b96f4acf
commit a862877189
2 changed files with 111 additions and 8 deletions
+69 -5
View File
@@ -54,6 +54,25 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
let ssid = router.uci_get("wireless.wwan.ssid").unwrap_or_default();
let encryption = router.uci_get("wireless.wwan.encryption").unwrap_or_default();
let radio0_disabled = router
.uci_get("wireless.radio0.disabled")
.map(|v| v == "1")
.unwrap_or(false);
// Find the active sta-mode interface and its association state
let iw_out = router.run_ok("iw dev 2>/dev/null").unwrap_or_default();
let (sta_iface, assoc_ssid) = parse_sta_iface(&iw_out);
// Interface operstate (up / down / absent)
let sta_state = if !sta_iface.is_empty() {
router
.run_ok(&format!("cat /sys/class/net/{}/operstate 2>/dev/null", sta_iface))
.unwrap_or_else(|_| "unknown".into())
.trim()
.to_string()
} else {
"absent".to_string()
};
// Source IP for reaching 8.8.8.8 — empty if no default route yet
let ip = router
@@ -62,15 +81,60 @@ pub fn get_wan_status(router: &Router) -> serde_json::Value {
.trim()
.to_string();
// Recent wifi-related kernel/syslog lines for quick diagnosis
let wifi_log = router
.run_ok("logread 2>/dev/null | grep -iE 'wlan|wwan|wifi|assoc|deauth|auth fail|CTRL-EVENT|wpa_supplicant' | tail -8 2>/dev/null")
.unwrap_or_default()
.trim()
.to_string();
serde_json::json!({
"configured": configured,
"ssid": ssid,
"encryption": encryption,
"ip": ip,
"internet": !ip.is_empty(),
"configured": configured,
"ssid": ssid,
"assoc_ssid": assoc_ssid,
"encryption": encryption,
"ip": ip,
"internet": !ip.is_empty(),
"radio0_disabled": radio0_disabled,
"sta_iface": sta_iface,
"sta_state": sta_state,
"wifi_log": wifi_log,
})
}
fn parse_sta_iface(iw_out: &str) -> (String, String) {
let mut result_iface = String::new();
let mut result_ssid = String::new();
let mut current_iface = String::new();
let mut current_type = String::new();
let mut current_ssid = String::new();
for line in iw_out.lines() {
let line = line.trim();
if let Some(name) = line.strip_prefix("Interface ") {
// Save previous interface if it was a sta
if current_type == "managed" && result_iface.is_empty() {
result_iface = current_iface.clone();
result_ssid = current_ssid.clone();
}
current_iface = name.trim().to_string();
current_type.clear();
current_ssid.clear();
} else if let Some(t) = line.strip_prefix("type ") {
current_type = t.trim().to_string();
} else if let Some(s) = line.strip_prefix("ssid ") {
current_ssid = s.trim().to_string();
}
}
// Handle last block
if current_type == "managed" && result_iface.is_empty() {
result_iface = current_iface;
result_ssid = current_ssid;
}
(result_iface, result_ssid)
}
fn detect_radio(router: &Router) -> Result<String> {
// radio0 is universal; verify it exists
let out = router.uci_get("wireless.radio0").unwrap_or_default();