Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc6b3424cf | |||
| 35e06c6734 | |||
| 783d233319 | |||
| 6509a2b91a | |||
| 52f8c40b86 | |||
| a86b5ba8f9 |
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -270,7 +270,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cm-dashboard"
|
name = "cm-dashboard"
|
||||||
version = "0.1.34"
|
version = "0.1.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -291,7 +291,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cm-dashboard-agent"
|
name = "cm-dashboard-agent"
|
||||||
version = "0.1.34"
|
version = "0.1.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -314,7 +314,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cm-dashboard-shared"
|
name = "cm-dashboard-shared"
|
||||||
version = "0.1.34"
|
version = "0.1.40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard-agent"
|
name = "cm-dashboard-agent"
|
||||||
version = "0.1.35"
|
version = "0.1.41"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -555,10 +555,8 @@ impl SystemdCollector {
|
|||||||
for (site_name, url) in &sites {
|
for (site_name, url) in &sites {
|
||||||
match self.check_site_latency(url) {
|
match self.check_site_latency(url) {
|
||||||
Ok(latency_ms) => {
|
Ok(latency_ms) => {
|
||||||
let status = if latency_ms < 500.0 {
|
let status = if latency_ms < self.config.nginx_latency_critical_ms {
|
||||||
Status::Ok
|
Status::Ok
|
||||||
} else if latency_ms < 2000.0 {
|
|
||||||
Status::Warning
|
|
||||||
} else {
|
} else {
|
||||||
Status::Critical
|
Status::Critical
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ pub struct SystemdConfig {
|
|||||||
pub nginx_check_interval_seconds: u64,
|
pub nginx_check_interval_seconds: u64,
|
||||||
pub http_timeout_seconds: u64,
|
pub http_timeout_seconds: u64,
|
||||||
pub http_connect_timeout_seconds: u64,
|
pub http_connect_timeout_seconds: u64,
|
||||||
|
pub nginx_latency_critical_ms: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ pub fn validate_config(config: &AgentConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate systemd configuration
|
||||||
|
if config.collectors.systemd.enabled {
|
||||||
|
if config.collectors.systemd.nginx_latency_critical_ms <= 0.0 {
|
||||||
|
bail!("Nginx latency critical threshold must be positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate SMTP configuration
|
// Validate SMTP configuration
|
||||||
if config.notifications.enabled {
|
if config.notifications.enabled {
|
||||||
if config.notifications.smtp_host.is_empty() {
|
if config.notifications.smtp_host.is_empty() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard"
|
name = "cm-dashboard"
|
||||||
version = "0.1.35"
|
version = "0.1.41"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use app::Dashboard;
|
|||||||
|
|
||||||
/// Get hardcoded version
|
/// Get hardcoded version
|
||||||
fn get_version() -> &'static str {
|
fn get_version() -> &'static str {
|
||||||
"v0.1.33"
|
"v0.1.41"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if running inside tmux session
|
/// Check if running inside tmux session
|
||||||
|
|||||||
@@ -244,16 +244,23 @@ impl TuiApp {
|
|||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
// System rebuild command - works on any panel for current host
|
// System rebuild command - works on any panel for current host
|
||||||
if let Some(hostname) = self.current_host.clone() {
|
if let Some(hostname) = self.current_host.clone() {
|
||||||
// Launch tmux popup with SSH using config values
|
// Create command that shows CM Dashboard logo and then rebuilds
|
||||||
let ssh_command = format!(
|
let logo_and_rebuild = format!(
|
||||||
"ssh -tt {}@{} 'bash -ic {}'",
|
r"cat << 'EOF'
|
||||||
|
NixOS System Rebuild
|
||||||
|
Target: {}
|
||||||
|
|
||||||
|
EOF
|
||||||
|
ssh -tt {}@{} 'bash -ic {}'",
|
||||||
|
hostname,
|
||||||
self.config.ssh.rebuild_user,
|
self.config.ssh.rebuild_user,
|
||||||
hostname,
|
hostname,
|
||||||
self.config.ssh.rebuild_alias
|
self.config.ssh.rebuild_alias
|
||||||
);
|
);
|
||||||
|
|
||||||
std::process::Command::new("tmux")
|
std::process::Command::new("tmux")
|
||||||
.arg("display-popup")
|
.arg("display-popup")
|
||||||
.arg(&ssh_command)
|
.arg(&logo_and_rebuild)
|
||||||
.spawn()
|
.spawn()
|
||||||
.ok(); // Ignore errors, tmux will handle them
|
.ok(); // Ignore errors, tmux will handle them
|
||||||
}
|
}
|
||||||
@@ -537,24 +544,34 @@ impl TuiApp {
|
|||||||
if self.available_hosts.is_empty() {
|
if self.available_hosts.is_empty() {
|
||||||
let title_text = "cm-dashboard • no hosts discovered";
|
let title_text = "cm-dashboard • no hosts discovered";
|
||||||
let title = Paragraph::new(title_text)
|
let title = Paragraph::new(title_text)
|
||||||
.style(Style::default().fg(Theme::background()).bg(Theme::highlight()));
|
.style(Style::default().fg(Theme::background()).bg(Theme::status_color(Status::Unknown)));
|
||||||
frame.render_widget(title, area);
|
frame.render_widget(title, area);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate worst-case status across all hosts
|
||||||
|
let mut worst_status = Status::Ok;
|
||||||
|
for host in &self.available_hosts {
|
||||||
|
let host_status = self.calculate_host_status(host, metric_store);
|
||||||
|
worst_status = Status::aggregate(&[worst_status, host_status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the worst status color as background
|
||||||
|
let background_color = Theme::status_color(worst_status);
|
||||||
|
|
||||||
// Split the title bar into left and right sections
|
// Split the title bar into left and right sections
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([Constraint::Min(0), Constraint::Min(0)])
|
.constraints([Constraint::Length(15), Constraint::Min(0)])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
// Left side: "cm-dashboard" text
|
// Left side: "cm-dashboard" text
|
||||||
let left_span = Span::styled(
|
let left_span = Span::styled(
|
||||||
" cm-dashboard",
|
" cm-dashboard",
|
||||||
Style::default().fg(Theme::background()).bg(Theme::highlight())
|
Style::default().fg(Theme::background()).bg(background_color).add_modifier(Modifier::BOLD)
|
||||||
);
|
);
|
||||||
let left_title = Paragraph::new(Line::from(vec![left_span]))
|
let left_title = Paragraph::new(Line::from(vec![left_span]))
|
||||||
.style(Style::default().bg(Theme::highlight()));
|
.style(Style::default().bg(background_color));
|
||||||
frame.render_widget(left_title, chunks[0]);
|
frame.render_widget(left_title, chunks[0]);
|
||||||
|
|
||||||
// Right side: hosts with status indicators
|
// Right side: hosts with status indicators
|
||||||
@@ -564,7 +581,7 @@ impl TuiApp {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
host_spans.push(Span::styled(
|
host_spans.push(Span::styled(
|
||||||
" ",
|
" ",
|
||||||
Style::default().fg(Theme::background()).bg(Theme::highlight())
|
Style::default().fg(Theme::background()).bg(background_color)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,33 +589,39 @@ impl TuiApp {
|
|||||||
let host_status = self.calculate_host_status(host, metric_store);
|
let host_status = self.calculate_host_status(host, metric_store);
|
||||||
let status_icon = StatusIcons::get_icon(host_status);
|
let status_icon = StatusIcons::get_icon(host_status);
|
||||||
|
|
||||||
// Add status icon with background color as foreground against blue background
|
// Add status icon with background color as foreground against status background
|
||||||
host_spans.push(Span::styled(
|
host_spans.push(Span::styled(
|
||||||
format!("{} ", status_icon),
|
format!("{} ", status_icon),
|
||||||
Style::default().fg(Theme::background()).bg(Theme::highlight()),
|
Style::default().fg(Theme::background()).bg(background_color),
|
||||||
));
|
));
|
||||||
|
|
||||||
if Some(host) == self.current_host.as_ref() {
|
if Some(host) == self.current_host.as_ref() {
|
||||||
// Selected host in bold background color against blue background
|
// Selected host in bold background color against status background
|
||||||
host_spans.push(Span::styled(
|
host_spans.push(Span::styled(
|
||||||
host.clone(),
|
host.clone(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Theme::background())
|
.fg(Theme::background())
|
||||||
.bg(Theme::highlight())
|
.bg(background_color)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
// Other hosts in normal background color against blue background
|
// Other hosts in normal background color against status background
|
||||||
host_spans.push(Span::styled(
|
host_spans.push(Span::styled(
|
||||||
host.clone(),
|
host.clone(),
|
||||||
Style::default().fg(Theme::background()).bg(Theme::highlight()),
|
Style::default().fg(Theme::background()).bg(background_color),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add right padding
|
||||||
|
host_spans.push(Span::styled(
|
||||||
|
" ",
|
||||||
|
Style::default().fg(Theme::background()).bg(background_color)
|
||||||
|
));
|
||||||
|
|
||||||
let host_line = Line::from(host_spans);
|
let host_line = Line::from(host_spans);
|
||||||
let host_title = Paragraph::new(vec![host_line])
|
let host_title = Paragraph::new(vec![host_line])
|
||||||
.style(Style::default().bg(Theme::highlight()))
|
.style(Style::default().bg(background_color))
|
||||||
.alignment(ratatui::layout::Alignment::Right);
|
.alignment(ratatui::layout::Alignment::Right);
|
||||||
frame.render_widget(host_title, chunks[1]);
|
frame.render_widget(host_title, chunks[1]);
|
||||||
}
|
}
|
||||||
@@ -685,7 +708,7 @@ impl TuiApp {
|
|||||||
host_widgets.system_scroll_offset
|
host_widgets.system_scroll_offset
|
||||||
};
|
};
|
||||||
let host_widgets = self.get_or_create_host_widgets(&hostname);
|
let host_widgets = self.get_or_create_host_widgets(&hostname);
|
||||||
host_widgets.system_widget.render_with_scroll(frame, inner_area, scroll_offset);
|
host_widgets.system_widget.render_with_scroll(frame, inner_area, scroll_offset, &hostname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -292,12 +292,6 @@ impl Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Typography {
|
impl Typography {
|
||||||
/// Main title style (dashboard header)
|
|
||||||
pub fn title() -> Style {
|
|
||||||
Style::default()
|
|
||||||
.fg(Theme::primary_text())
|
|
||||||
.bg(Theme::background())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Widget title style (panel headers) - bold bright white
|
/// Widget title style (panel headers) - bold bright white
|
||||||
pub fn widget_title() -> Style {
|
pub fn widget_title() -> Style {
|
||||||
|
|||||||
@@ -439,12 +439,12 @@ impl Widget for SystemWidget {
|
|||||||
|
|
||||||
impl SystemWidget {
|
impl SystemWidget {
|
||||||
/// Render with scroll offset support
|
/// Render with scroll offset support
|
||||||
pub fn render_with_scroll(&mut self, frame: &mut Frame, area: Rect, scroll_offset: usize) {
|
pub fn render_with_scroll(&mut self, frame: &mut Frame, area: Rect, scroll_offset: usize, hostname: &str) {
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
|
|
||||||
// NixOS section
|
// NixOS section
|
||||||
lines.push(Line::from(vec![
|
lines.push(Line::from(vec![
|
||||||
Span::styled("NixOS:", Typography::widget_title())
|
Span::styled(format!("NixOS {}:", hostname), Typography::widget_title())
|
||||||
]));
|
]));
|
||||||
|
|
||||||
let build_text = self.nixos_build.as_deref().unwrap_or("unknown");
|
let build_text = self.nixos_build.as_deref().unwrap_or("unknown");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cm-dashboard-shared"
|
name = "cm-dashboard-shared"
|
||||||
version = "0.1.35"
|
version = "0.1.41"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user