Implement hysteresis for metric status changes to prevent flapping
Add comprehensive hysteresis support to prevent status oscillation near threshold boundaries while maintaining responsive alerting. Key Features: - HysteresisThresholds with configurable upper/lower limits - StatusTracker for per-metric status history - Default gaps: CPU load 10%, memory 5%, disk temp 5°C Updated Components: - CPU load collector (5-minute average with hysteresis) - Memory usage collector (percentage-based thresholds) - Disk temperature collector (SMART data monitoring) - All collectors updated to support StatusTracker interface Cache Interval Adjustments: - Service status: 60s → 10s (faster response) - Disk usage: 300s → 60s (more frequent checks) - Backup status: 900s → 60s (quicker updates) - SMART data: moved to 600s tier (10 minutes) Architecture: - Individual metric status calculation in collectors - Centralized StatusTracker in MetricCollectionManager - Status aggregation preserved in dashboard widgets
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use ratatui::style::{Color, Style, Modifier};
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
use cm_dashboard_shared::Status;
|
||||
use ratatui::style::{Color, Modifier, Style};
|
||||
use ratatui::widgets::{Block, Borders};
|
||||
|
||||
/// Complete terminal color palette matching your configuration
|
||||
#[allow(dead_code)]
|
||||
@@ -10,7 +10,7 @@ pub struct TerminalColors {
|
||||
pub dim_foreground: Color,
|
||||
pub bright_foreground: Color,
|
||||
pub background: Color,
|
||||
|
||||
|
||||
// Normal colors
|
||||
pub normal_black: Color,
|
||||
pub normal_red: Color,
|
||||
@@ -20,7 +20,7 @@ pub struct TerminalColors {
|
||||
pub normal_magenta: Color,
|
||||
pub normal_cyan: Color,
|
||||
pub normal_white: Color,
|
||||
|
||||
|
||||
// Bright colors
|
||||
pub bright_black: Color,
|
||||
pub bright_red: Color,
|
||||
@@ -30,7 +30,7 @@ pub struct TerminalColors {
|
||||
pub bright_magenta: Color,
|
||||
pub bright_cyan: Color,
|
||||
pub bright_white: Color,
|
||||
|
||||
|
||||
// Dim colors
|
||||
pub dim_black: Color,
|
||||
pub dim_red: Color,
|
||||
@@ -46,40 +46,40 @@ impl Default for TerminalColors {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// Primary colors
|
||||
foreground: Color::Rgb(198, 198, 198), // #c6c6c6
|
||||
dim_foreground: Color::Rgb(112, 112, 112), // #707070
|
||||
foreground: Color::Rgb(198, 198, 198), // #c6c6c6
|
||||
dim_foreground: Color::Rgb(112, 112, 112), // #707070
|
||||
bright_foreground: Color::Rgb(255, 255, 255), // #ffffff
|
||||
background: Color::Rgb(38, 38, 38), // #262626
|
||||
|
||||
background: Color::Rgb(38, 38, 38), // #262626
|
||||
|
||||
// Normal colors
|
||||
normal_black: Color::Rgb(0, 0, 0), // #000000
|
||||
normal_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
normal_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
normal_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
normal_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
normal_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
normal_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
normal_white: Color::Rgb(238, 238, 238), // #eeeeee
|
||||
|
||||
normal_black: Color::Rgb(0, 0, 0), // #000000
|
||||
normal_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
normal_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
normal_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
normal_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
normal_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
normal_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
normal_white: Color::Rgb(238, 238, 238), // #eeeeee
|
||||
|
||||
// Bright colors
|
||||
bright_black: Color::Rgb(48, 48, 48), // #303030
|
||||
bright_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
bright_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
bright_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
bright_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
bright_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
bright_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
bright_white: Color::Rgb(255, 255, 255), // #ffffff
|
||||
|
||||
bright_black: Color::Rgb(48, 48, 48), // #303030
|
||||
bright_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
bright_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
bright_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
bright_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
bright_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
bright_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
bright_white: Color::Rgb(255, 255, 255), // #ffffff
|
||||
|
||||
// Dim colors
|
||||
dim_black: Color::Rgb(0, 0, 0), // #000000
|
||||
dim_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
dim_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
dim_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
dim_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
dim_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
dim_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
dim_white: Color::Rgb(221, 221, 221), // #dddddd
|
||||
dim_black: Color::Rgb(0, 0, 0), // #000000
|
||||
dim_red: Color::Rgb(215, 84, 0), // #d75400
|
||||
dim_green: Color::Rgb(175, 215, 135), // #afd787
|
||||
dim_yellow: Color::Rgb(215, 175, 95), // #d7af5f
|
||||
dim_blue: Color::Rgb(135, 175, 215), // #87afd7
|
||||
dim_magenta: Color::Rgb(215, 215, 175), // #d7d7af
|
||||
dim_cyan: Color::Rgb(160, 160, 160), // #a0a0a0
|
||||
dim_white: Color::Rgb(221, 221, 221), // #dddddd
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,52 +93,52 @@ impl Theme {
|
||||
static COLORS: std::sync::OnceLock<TerminalColors> = std::sync::OnceLock::new();
|
||||
COLORS.get_or_init(TerminalColors::default)
|
||||
}
|
||||
|
||||
|
||||
// Semantic color mapping using the terminal color struct
|
||||
pub fn primary_text() -> Color {
|
||||
Self::colors().normal_white
|
||||
}
|
||||
|
||||
|
||||
pub fn secondary_text() -> Color {
|
||||
Self::colors().foreground
|
||||
}
|
||||
|
||||
|
||||
pub fn muted_text() -> Color {
|
||||
Self::colors().dim_foreground
|
||||
}
|
||||
|
||||
|
||||
pub fn border() -> Color {
|
||||
Self::colors().dim_foreground
|
||||
}
|
||||
|
||||
|
||||
pub fn border_title() -> Color {
|
||||
Self::colors().bright_white
|
||||
}
|
||||
|
||||
|
||||
pub fn background() -> Color {
|
||||
Self::colors().background
|
||||
}
|
||||
|
||||
|
||||
pub fn success() -> Color {
|
||||
Self::colors().normal_green
|
||||
}
|
||||
|
||||
|
||||
pub fn warning() -> Color {
|
||||
Self::colors().normal_yellow
|
||||
}
|
||||
|
||||
|
||||
pub fn error() -> Color {
|
||||
Self::colors().normal_red
|
||||
}
|
||||
|
||||
|
||||
pub fn info() -> Color {
|
||||
Self::colors().normal_cyan
|
||||
}
|
||||
|
||||
|
||||
pub fn highlight() -> Color {
|
||||
Self::colors().normal_blue
|
||||
}
|
||||
|
||||
|
||||
/// Get color for status level
|
||||
pub fn status_color(status: Status) -> Color {
|
||||
match status {
|
||||
@@ -148,12 +148,12 @@ impl Theme {
|
||||
Status::Unknown => Self::muted_text(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get style for status level
|
||||
pub fn status_style(status: Status) -> Style {
|
||||
Style::default().fg(Self::status_color(status))
|
||||
}
|
||||
|
||||
|
||||
/// CPU usage colors using terminal color struct
|
||||
pub fn cpu_color(percentage: u16) -> Color {
|
||||
match percentage {
|
||||
@@ -164,7 +164,7 @@ impl Theme {
|
||||
_ => Self::colors().normal_red, // Over 100%
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Memory usage colors using terminal color struct
|
||||
pub fn memory_color(percentage: u16) -> Color {
|
||||
match percentage {
|
||||
@@ -175,7 +175,7 @@ impl Theme {
|
||||
_ => Self::colors().normal_red, // Over 100%
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get gauge color based on percentage
|
||||
pub fn gauge_color(percentage: u16, warning_threshold: u16, critical_threshold: u16) -> Color {
|
||||
if percentage >= critical_threshold {
|
||||
@@ -186,25 +186,31 @@ impl Theme {
|
||||
Self::success()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Widget border style
|
||||
pub fn widget_border_style() -> Style {
|
||||
Style::default().fg(Self::border()).bg(Self::background())
|
||||
}
|
||||
|
||||
|
||||
/// Inactive widget border style
|
||||
pub fn widget_border_inactive_style() -> Style {
|
||||
Style::default().fg(Self::muted_text()).bg(Self::background())
|
||||
Style::default()
|
||||
.fg(Self::muted_text())
|
||||
.bg(Self::background())
|
||||
}
|
||||
|
||||
|
||||
/// Title style
|
||||
pub fn title_style() -> Style {
|
||||
Style::default().fg(Self::border_title()).bg(Self::background())
|
||||
Style::default()
|
||||
.fg(Self::border_title())
|
||||
.bg(Self::background())
|
||||
}
|
||||
|
||||
|
||||
/// Status bar style
|
||||
pub fn status_bar_style() -> Style {
|
||||
Style::default().fg(Self::muted_text()).bg(Self::background())
|
||||
Style::default()
|
||||
.fg(Self::muted_text())
|
||||
.bg(Self::background())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,29 +251,29 @@ impl StatusIcons {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Create spans with status icon colored and text in foreground color
|
||||
pub fn create_status_spans(status: Status, text: &str) -> Vec<ratatui::text::Span<'static>> {
|
||||
let icon = Self::get_icon(status);
|
||||
let status_color = match status {
|
||||
Status::Ok => Theme::success(), // Green
|
||||
Status::Warning => Theme::warning(), // Yellow
|
||||
Status::Critical => Theme::error(), // Red
|
||||
Status::Ok => Theme::success(), // Green
|
||||
Status::Warning => Theme::warning(), // Yellow
|
||||
Status::Critical => Theme::error(), // Red
|
||||
Status::Unknown => Theme::muted_text(), // Gray
|
||||
};
|
||||
|
||||
|
||||
vec![
|
||||
ratatui::text::Span::styled(
|
||||
format!("{} ", icon),
|
||||
Style::default().fg(status_color).bg(Theme::background())
|
||||
Style::default().fg(status_color).bg(Theme::background()),
|
||||
),
|
||||
ratatui::text::Span::styled(
|
||||
text.to_string(),
|
||||
Style::default().fg(Theme::secondary_text()).bg(Theme::background())
|
||||
Style::default()
|
||||
.fg(Theme::secondary_text())
|
||||
.bg(Theme::background()),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Components {
|
||||
@@ -277,9 +283,12 @@ impl Components {
|
||||
.title(title)
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Theme::border()).bg(Theme::background()))
|
||||
.title_style(Style::default().fg(Theme::border_title()).bg(Theme::background()))
|
||||
.title_style(
|
||||
Style::default()
|
||||
.fg(Theme::border_title())
|
||||
.bg(Theme::background()),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Typography {
|
||||
@@ -289,7 +298,7 @@ impl Typography {
|
||||
.fg(Theme::primary_text())
|
||||
.bg(Theme::background())
|
||||
}
|
||||
|
||||
|
||||
/// Widget title style (panel headers) - bold bright white
|
||||
pub fn widget_title() -> Style {
|
||||
Style::default()
|
||||
@@ -297,14 +306,14 @@ impl Typography {
|
||||
.bg(Theme::background())
|
||||
.add_modifier(Modifier::BOLD)
|
||||
}
|
||||
|
||||
|
||||
/// Secondary content text
|
||||
pub fn secondary() -> Style {
|
||||
Style::default()
|
||||
.fg(Theme::secondary_text())
|
||||
.bg(Theme::background())
|
||||
}
|
||||
|
||||
|
||||
/// Muted text (inactive items, placeholders) - now bold bright white for headers
|
||||
pub fn muted() -> Style {
|
||||
Style::default()
|
||||
@@ -312,5 +321,4 @@ impl Typography {
|
||||
.bg(Theme::background())
|
||||
.add_modifier(Modifier::BOLD)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user