use crate::state::ArchStatusColor;
use std::fmt::Write;
use super::utils::{severity_max, today_ymd_utc};
pub(super) fn parse_uptimerobot_api(v: &serde_json::Value) -> Option<(String, ArchStatusColor)> {
let data = v.get("data")?.as_array()?;
let (year, month, day) = today_ymd_utc()?;
let today_str = format!("{year}-{month:02}-{day:02}");
let monitor_names = ["AUR", "Forum", "Website", "Wiki"];
let mut monitor_statuses: Vec<(String, f64, &str, &str)> = Vec::new();
for monitor in data {
let name = monitor.get("name")?.as_str()?;
if !monitor_names.iter().any(|&n| n.eq_ignore_ascii_case(name)) {
continue;
}
let daily_ratios = monitor.get("dailyRatios")?.as_array()?;
if let Some(today_data) = daily_ratios.iter().find(|d| {
d.get("date")
.and_then(|date| date.as_str())
.is_some_and(|date| date == today_str)
}) {
let ratio_str = today_data.get("ratio")?.as_str()?;
if let Ok(ratio) = ratio_str.parse::<f64>() {
let color_str = today_data.get("color")?.as_str()?;
let label = today_data.get("label")?.as_str()?;
monitor_statuses.push((name.to_string(), ratio, color_str, label));
}
}
}
if monitor_statuses.is_empty() {
return None;
}
let worst = monitor_statuses.iter().min_by(|a, b| {
let ratio_cmp = a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal);
if ratio_cmp != std::cmp::Ordering::Equal {
return ratio_cmp;
}
let color_rank_a = match a.2 {
"red" => 3,
"yellow" | "blue" => 2,
"green" => 1,
_ => 0,
};
let color_rank_b = match b.2 {
"red" => 3,
"yellow" | "blue" => 2,
"green" => 1,
_ => 0,
};
color_rank_b.cmp(&color_rank_a) })?;
let aur_status = monitor_statuses
.iter()
.find(|s| s.0.eq_ignore_ascii_case("aur"));
let (name, ratio, color_str, label) = worst;
let color = match *color_str {
"green" => ArchStatusColor::Operational,
"yellow" | "blue" => ArchStatusColor::IncidentToday,
"red" => ArchStatusColor::IncidentSevereToday,
_ => ArchStatusColor::None,
};
let mut text = if *ratio < 90.0 {
format!("{name} outage (see status) — {name} today: {ratio:.1}%")
} else if *ratio < 95.0 {
format!("{name} degraded (see status) — {name} today: {ratio:.1}%")
} else if *label == "poor" || *color_str == "red" {
format!("{name} issues detected (see status) — {name} today: {ratio:.1}%")
} else {
format!("Arch systems nominal — {name} today: {ratio:.1}%")
};
if let Some((aur_name, aur_ratio, aur_color_str, _)) = aur_status
&& !aur_name.eq_ignore_ascii_case(name)
&& (*aur_ratio < 100.0 || *aur_color_str != "green")
{
let _ = write!(text, " (AUR: {aur_ratio:.1}%)");
}
Some((text, color))
}
pub(super) fn parse_status_api_summary(
v: &serde_json::Value,
) -> (String, ArchStatusColor, Option<String>) {
let indicator = v
.get("status")
.and_then(|s| s.get("indicator"))
.and_then(|i| i.as_str())
.unwrap_or("none");
let mut color = match indicator {
"none" => ArchStatusColor::Operational,
"minor" => ArchStatusColor::IncidentToday,
"major" | "critical" => ArchStatusColor::IncidentSevereToday,
_ => ArchStatusColor::None,
};
let suffix: Option<String> = None;
let mut aur_state: Option<&str> = None;
if let Some(components) = v.get("components").and_then(|c| c.as_array())
&& let Some(aur_comp) = components.iter().find(|c| {
c.get("name")
.and_then(|n| n.as_str())
.is_some_and(|n| n.to_lowercase().contains("aur"))
})
&& let Some(state) = aur_comp.get("status").and_then(|s| s.as_str())
{
aur_state = Some(state);
match state {
"degraded_performance" => {
color = severity_max(color, ArchStatusColor::IncidentToday);
}
"partial_outage" => {
color = severity_max(color, ArchStatusColor::IncidentToday);
}
"major_outage" => {
color = severity_max(color, ArchStatusColor::IncidentSevereToday);
}
"under_maintenance" => {
color = severity_max(color, ArchStatusColor::IncidentToday);
}
_ => {}
}
}
let text = aur_state.map_or_else(
|| {
if indicator == "none" {
"All systems operational".to_string()
} else {
"Arch systems nominal".to_string()
}
},
|state| match state {
"major_outage" => "AUR outage (see status)".to_string(),
"partial_outage" => "AUR partial outage".to_string(),
"degraded_performance" => "AUR RPC degraded".to_string(),
"under_maintenance" => "AUR maintenance ongoing".to_string(),
_ => {
if indicator == "none" {
"All systems operational".to_string()
} else {
"Arch systems nominal".to_string()
}
}
},
);
(text, color, suffix)
}