This commit is contained in:
2025-10-12 22:31:46 +02:00
parent 4d8bacef50
commit 9e344fb66d
17 changed files with 283 additions and 297 deletions

View File

@@ -259,7 +259,7 @@ impl App {
if service_metrics.timestamp != timestamp {
service_metrics.timestamp = timestamp;
}
let mut snapshot = service_metrics.clone();
let snapshot = service_metrics.clone();
// No more need for dashboard-side description caching since agent handles it

View File

@@ -71,6 +71,8 @@ pub struct ServiceSummary {
#[serde(default)]
pub cpu_temp_c: Option<f32>,
#[serde(default)]
pub cpu_temp_status: Option<String>,
#[serde(default)]
pub gpu_load_percent: Option<f32>,
#[serde(default)]
pub gpu_temp_c: Option<f32>,
@@ -100,7 +102,7 @@ pub enum ServiceStatus {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupMetrics {
pub overall_status: BackupStatus,
pub overall_status: String,
pub backup: BackupInfo,
pub service: BackupServiceInfo,
#[serde(default)]

View File

@@ -1,6 +1,5 @@
use chrono::{DateTime, Utc};
use ratatui::layout::{Constraint, Rect};
use ratatui::style::Color;
use ratatui::layout::Rect;
use ratatui::Frame;
use crate::app::HostDisplayData;
@@ -8,17 +7,7 @@ use crate::ui::system::{evaluate_performance, PerfSeverity};
use crate::ui::widget::{render_widget_data, WidgetData, WidgetStatus, StatusLevel};
pub fn render(frame: &mut Frame, hosts: &[HostDisplayData], area: Rect) {
let (severity, ok_count, warn_count, fail_count) = classify_hosts(hosts);
let mut color = match severity {
AlertSeverity::Critical => Color::Red,
AlertSeverity::Warning => Color::Yellow,
AlertSeverity::Healthy => Color::Green,
AlertSeverity::Unknown => Color::Gray,
};
if hosts.is_empty() {
color = Color::Gray;
}
let (severity, _ok_count, _warn_count, _fail_count) = classify_hosts(hosts);
let title = "Alerts".to_string();
@@ -140,9 +129,9 @@ fn host_severity(host: &HostDisplayData) -> AlertSeverity {
}
if let Some(backup) = host.backup.as_ref() {
match backup.overall_status {
crate::data::metrics::BackupStatus::Failed => return AlertSeverity::Critical,
crate::data::metrics::BackupStatus::Warning => return AlertSeverity::Warning,
match backup.overall_status.as_str() {
"critical" => return AlertSeverity::Critical,
"warning" => return AlertSeverity::Warning,
_ => {}
}
}
@@ -211,15 +200,15 @@ fn host_status(host: &HostDisplayData) -> (String, AlertSeverity, bool) {
}
if let Some(backup) = host.backup.as_ref() {
match backup.overall_status {
crate::data::metrics::BackupStatus::Failed => {
match backup.overall_status.as_str() {
"critical" => {
return (
"critical: backup failed".to_string(),
AlertSeverity::Critical,
true,
);
}
crate::data::metrics::BackupStatus::Warning => {
"warning" => {
return (
"warning: backup warning".to_string(),
AlertSeverity::Warning,
@@ -243,14 +232,6 @@ fn host_status(host: &HostDisplayData) -> (String, AlertSeverity, bool) {
("ok".to_string(), AlertSeverity::Healthy, false)
}
fn severity_color(severity: AlertSeverity) -> Color {
match severity {
AlertSeverity::Critical => Color::Red,
AlertSeverity::Warning => Color::Yellow,
AlertSeverity::Healthy => Color::Green,
AlertSeverity::Unknown => Color::Gray,
}
}
fn latest_timestamp(host: &HostDisplayData) -> Option<DateTime<Utc>> {
let mut latest = host.last_success;
@@ -279,11 +260,3 @@ fn latest_timestamp(host: &HostDisplayData) -> Option<DateTime<Utc>> {
latest
}
fn severity_symbol(severity: AlertSeverity) -> &'static str {
match severity {
AlertSeverity::Critical => "",
AlertSeverity::Warning => "!",
AlertSeverity::Healthy => "",
AlertSeverity::Unknown => "?",
}
}

View File

@@ -1,10 +1,9 @@
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::Frame;
use crate::app::HostDisplayData;
use crate::data::metrics::{BackupMetrics, BackupStatus};
use crate::ui::widget::{render_placeholder, render_widget_data, WidgetData, WidgetStatus, StatusLevel};
use crate::data::metrics::BackupMetrics;
use crate::ui::widget::{render_placeholder, render_widget_data, status_level_from_agent_status, WidgetData, WidgetStatus, StatusLevel};
pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
match host {
@@ -25,12 +24,7 @@ pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
}
fn render_metrics(frame: &mut Frame, _host: &HostDisplayData, metrics: &BackupMetrics, area: Rect) {
let widget_status = match metrics.overall_status {
BackupStatus::Failed => StatusLevel::Error,
BackupStatus::Warning => StatusLevel::Warning,
BackupStatus::Unknown => StatusLevel::Unknown,
BackupStatus::Healthy => StatusLevel::Ok,
};
let widget_status = status_level_from_agent_status(Some(&metrics.overall_status));
let mut data = WidgetData::new(
"Backups",
@@ -93,46 +87,4 @@ fn render_metrics(frame: &mut Frame, _host: &HostDisplayData, metrics: &BackupMe
render_widget_data(frame, area, data);
}
fn backup_status_color(status: &BackupStatus) -> Color {
match status {
BackupStatus::Failed => Color::Red,
BackupStatus::Warning => Color::Yellow,
BackupStatus::Unknown => Color::LightYellow,
BackupStatus::Healthy => Color::Green,
}
}
fn format_timestamp(timestamp: Option<&chrono::DateTime<chrono::Utc>>) -> String {
timestamp
.map(|ts| ts.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "".to_string())
}
fn repo_status_level(metrics: &BackupMetrics) -> StatusLevel {
match metrics.overall_status {
BackupStatus::Failed => StatusLevel::Error,
BackupStatus::Warning => StatusLevel::Warning,
_ => {
if metrics.backup.snapshot_count > 0 {
StatusLevel::Ok
} else {
StatusLevel::Warning
}
}
}
}
fn service_status_level(metrics: &BackupMetrics) -> StatusLevel {
match metrics.overall_status {
BackupStatus::Failed => StatusLevel::Error,
BackupStatus::Warning => StatusLevel::Warning,
BackupStatus::Unknown => StatusLevel::Unknown,
BackupStatus::Healthy => {
if metrics.service.enabled {
StatusLevel::Ok
} else {
StatusLevel::Warning
}
}
}
}

View File

@@ -1,9 +1,8 @@
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::Frame;
use crate::app::HostDisplayData;
use crate::data::metrics::{ServiceStatus, ServiceSummary};
use crate::data::metrics::ServiceStatus;
use crate::ui::widget::{render_placeholder, render_widget_data, status_level_from_agent_status, WidgetData, WidgetStatus, StatusLevel};
pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
@@ -31,7 +30,6 @@ fn render_metrics(
area: Rect,
) {
let summary = &metrics.summary;
let color = summary_color(summary);
let title = "Services".to_string();
// Use agent-calculated services status
@@ -105,24 +103,6 @@ fn status_weight(status: &ServiceStatus) -> i32 {
}
}
fn status_symbol(status: &ServiceStatus) -> (&'static str, Color) {
match status {
ServiceStatus::Running => ("", Color::Green),
ServiceStatus::Degraded => ("!", Color::Yellow),
ServiceStatus::Restarting => ("", Color::Yellow),
ServiceStatus::Stopped => ("", Color::Red),
}
}
fn summary_color(summary: &ServiceSummary) -> Color {
if summary.failed > 0 {
Color::Red
} else if summary.degraded > 0 {
Color::Yellow
} else {
Color::Green
}
}
fn format_memory_value(used: f32, quota: f32) -> String {
let used_gb = used / 1000.0;

View File

@@ -1,10 +1,9 @@
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::Frame;
use crate::app::HostDisplayData;
use crate::data::metrics::SmartMetrics;
use crate::ui::widget::{render_placeholder, render_widget_data, WidgetData, WidgetStatus, StatusLevel};
use crate::ui::widget::{render_placeholder, render_widget_data, status_level_from_agent_status, WidgetData, WidgetStatus, StatusLevel};
pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
match host {
@@ -25,16 +24,9 @@ pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
}
fn render_metrics(frame: &mut Frame, _host: &HostDisplayData, metrics: &SmartMetrics, area: Rect) {
let color = smart_status_color(&metrics.status);
let title = "Storage".to_string();
let widget_status = if metrics.summary.critical > 0 {
StatusLevel::Error
} else if metrics.summary.warning > 0 {
StatusLevel::Warning
} else {
StatusLevel::Ok
};
let widget_status = status_level_from_agent_status(Some(&metrics.status));
let mut data = WidgetData::new(
title,
@@ -95,13 +87,6 @@ fn render_metrics(frame: &mut Frame, _host: &HostDisplayData, metrics: &SmartMet
render_widget_data(frame, area, data);
}
fn smart_status_color(status: &str) -> Color {
match status.to_uppercase().as_str() {
"CRITICAL" => Color::Red,
"WARNING" => Color::Yellow,
_ => Color::Green,
}
}
fn format_temperature(value: f32) -> String {
if value.abs() < f32::EPSILON {

View File

@@ -1,12 +1,11 @@
use ratatui::layout::Rect;
use ratatui::style::Color;
use ratatui::Frame;
use crate::app::HostDisplayData;
use crate::data::metrics::{ServiceMetrics, ServiceSummary};
use crate::ui::widget::{
combined_color, render_placeholder, render_combined_widget_data, status_color_for_cpu_load, status_color_from_metric,
status_color_from_percentage, status_level_from_agent_status, WidgetDataSet, WidgetStatus, StatusLevel,
render_placeholder, render_combined_widget_data,
status_level_from_agent_status, WidgetDataSet, WidgetStatus, StatusLevel,
};
pub fn render(frame: &mut Frame, host: Option<&HostDisplayData>, area: Rect) {
@@ -44,33 +43,19 @@ fn render_metrics(
} else {
summary.memory_used_mb
};
let usage_ratio = if system_total > 0.0 {
let _usage_ratio = if system_total > 0.0 {
(system_used / system_total) * 100.0
} else {
0.0
};
let (perf_severity, _reason) = evaluate_performance(summary);
let border_color = match perf_severity {
PerfSeverity::Critical => Color::Red,
PerfSeverity::Warning => Color::Yellow,
PerfSeverity::Ok => Color::Green,
};
// Dashboard should NOT calculate border colors - agent is the source of truth
// Use agent-calculated statuses instead of dashboard calculations
let memory_status = status_level_from_agent_status(summary.memory_status.as_ref());
let cpu_status = status_level_from_agent_status(summary.cpu_status.as_ref());
let cpu_temp_color = status_color_from_metric(summary.cpu_temp_c, 80.0, 90.0);
let gpu_load_color = summary
.gpu_load_percent
.map(|value| status_color_from_percentage(value, 85.0, 95.0))
.unwrap_or(Color::Green);
let gpu_temp_color = summary
.gpu_temp_c
.map(|value| status_color_from_metric(Some(value), 75.0, 85.0))
.unwrap_or(Color::Green);
let gpu_icon_color = combined_color(&[gpu_load_color, gpu_temp_color]);
// Dashboard should NOT calculate colors - agent is the source of truth
// Memory dataset - use agent-calculated status
let mut memory_dataset = WidgetDataSet::new(vec!["Memory usage".to_string()], Some(WidgetStatus::new(memory_status)));
@@ -156,7 +141,8 @@ fn render_metrics(
}
// GPU dataset
let gpu_status = status_level_from_color(gpu_icon_color);
// GPU status should come from agent when available
let gpu_status = StatusLevel::Unknown; // Default until agent provides gpu_status
let mut gpu_dataset = WidgetDataSet::new(vec!["GPU load".to_string(), "GPU temp".to_string()], Some(WidgetStatus::new(gpu_status)));
gpu_dataset.add_row(
Some(WidgetStatus::new(gpu_status)),
@@ -206,13 +192,6 @@ fn format_optional_percent(value: Option<f32>) -> String {
}
}
fn status_level_from_color(color: Color) -> StatusLevel {
match color {
Color::Red => StatusLevel::Error,
Color::Yellow => StatusLevel::Warning,
_ => StatusLevel::Ok,
}
}
pub(crate) fn evaluate_performance(summary: &ServiceSummary) -> (PerfSeverity, Option<String>) {
let mem_percent = if summary.system_memory_total_mb > 0.0 {
@@ -233,43 +212,38 @@ pub(crate) fn evaluate_performance(summary: &ServiceSummary) -> (PerfSeverity, O
}
};
if mem_percent >= 95.0 {
consider(PerfSeverity::Critical, format!("RAM {:.0}%", mem_percent));
} else if mem_percent >= 80.0 {
consider(PerfSeverity::Warning, format!("RAM {:.0}%", mem_percent));
}
let load = summary.cpu_load_5;
if load >= 4.0 {
consider(PerfSeverity::Critical, format!("CPU load {:.2}", load));
} else if load >= 2.0 {
consider(PerfSeverity::Warning, format!("CPU load {:.2}", load));
}
if let Some(temp) = summary.cpu_temp_c {
if temp >= 90.0 {
consider(PerfSeverity::Critical, format!("CPU temp {:.0}°C", temp));
} else if temp >= 80.0 {
consider(PerfSeverity::Warning, format!("CPU temp {:.0}°C", temp));
// Use agent's memory status instead of hardcoded thresholds
if let Some(memory_status) = &summary.memory_status {
match memory_status.as_str() {
"critical" => consider(PerfSeverity::Critical, format!("RAM {:.0}%", mem_percent)),
"warning" => consider(PerfSeverity::Warning, format!("RAM {:.0}%", mem_percent)),
_ => {} // "ok" - no alert needed
}
}
if let Some(load) = summary.gpu_load_percent {
if load >= 95.0 {
consider(PerfSeverity::Critical, format!("GPU load {:.0}%", load));
} else if load >= 85.0 {
consider(PerfSeverity::Warning, format!("GPU load {:.0}%", load));
// Use agent's CPU status instead of hardcoded thresholds
if let Some(cpu_status) = &summary.cpu_status {
match cpu_status.as_str() {
"critical" => consider(PerfSeverity::Critical, format!("CPU load {:.2}", summary.cpu_load_5)),
"warning" => consider(PerfSeverity::Warning, format!("CPU load {:.2}", summary.cpu_load_5)),
_ => {} // "ok" - no alert needed
}
}
if let Some(temp) = summary.gpu_temp_c {
if temp >= 85.0 {
consider(PerfSeverity::Critical, format!("GPU temp {:.0}°C", temp));
} else if temp >= 75.0 {
consider(PerfSeverity::Warning, format!("GPU temp {:.0}°C", temp));
// Use agent's CPU temperature status instead of hardcoded thresholds
if let Some(cpu_temp_status) = &summary.cpu_temp_status {
if let Some(temp) = summary.cpu_temp_c {
match cpu_temp_status.as_str() {
"critical" => consider(PerfSeverity::Critical, format!("CPU temp {:.0}°C", temp)),
"warning" => consider(PerfSeverity::Warning, format!("CPU temp {:.0}°C", temp)),
_ => {} // "ok" - no alert needed
}
}
}
// TODO: GPU status should come from agent, not calculated here with hardcoded thresholds
// For now, remove hardcoded GPU thresholds until agent provides gpu_status
if severity == PerfSeverity::Ok {
(PerfSeverity::Ok, None)
} else {

View File

@@ -24,33 +24,8 @@ fn neutral_border_style(color: Color) -> Style {
Style::default().fg(color)
}
pub fn status_color_from_percentage(value: f32, warn: f32, crit: f32) -> Color {
if value >= crit {
Color::Red
} else if value >= warn {
Color::Yellow
} else {
Color::Green
}
}
pub fn status_color_from_metric(value: Option<f32>, warn: f32, crit: f32) -> Color {
match value {
Some(v) if v >= crit => Color::Red,
Some(v) if v >= warn => Color::Yellow,
_ => Color::Green,
}
}
pub fn status_color_for_cpu_load(load: f32) -> Color {
if load >= 8.0 {
Color::Red
} else if load >= 5.0 {
Color::Yellow
} else {
Color::Green
}
}
pub fn status_level_from_agent_status(agent_status: Option<&String>) -> StatusLevel {
match agent_status.map(|s| s.as_str()) {
@@ -62,15 +37,6 @@ pub fn status_level_from_agent_status(agent_status: Option<&String>) -> StatusLe
}
}
pub fn combined_color(colors: &[Color]) -> Color {
if colors.iter().any(|&c| c == Color::Red) {
Color::Red
} else if colors.iter().any(|&c| c == Color::Yellow) {
Color::Yellow
} else {
Color::Green
}
}
pub fn render_placeholder(frame: &mut Frame, area: Rect, title: &str, message: &str) {