use crate::{style::*, InspectorModel, KeyCode, PAGE_SCROLL_STEP};
use nodo_runtime::proto::nodo as nodo_pb;
use ratatui::{
prelude::{Buffer, Constraint, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Cell, Row, StatefulWidget, Table, Widget},
};
use std::{cell::RefCell, sync::Arc, time::Duration};
pub struct MonitorsController {
pub model: Arc<RefCell<InspectorModel>>,
}
impl MonitorsController {
pub fn on_key(&mut self, key: KeyCode) {
match key {
KeyCode::Down => self.select_next(),
KeyCode::Up => self.select_previous(),
KeyCode::PageDown => self.on_page_down(),
KeyCode::PageUp => self.on_page_up(),
KeyCode::Enter => {}
_ => {}
}
}
fn select_next(&mut self) {
self.model.borrow_mut().monitors_table_state.select_next();
}
fn select_previous(&mut self) {
self.model
.borrow_mut()
.monitors_table_state
.select_previous();
}
fn on_page_down(&mut self) {
for _ in 0..PAGE_SCROLL_STEP {
self.select_next()
}
}
fn on_page_up(&mut self) {
for _ in 0..PAGE_SCROLL_STEP {
self.select_previous()
}
}
}
pub struct MonitorsView {
pub model: Arc<RefCell<InspectorModel>>,
}
impl Widget for &MonitorsView {
fn render(self, area: Rect, buf: &mut Buffer) {
let mut model = self.model.borrow_mut();
let mut entries = model.tree.monitors.clone();
entries.sort_by(|(_, _, m1), (_, _, m2)| {
let status_to_priority = |status: i32| match nodo_pb::MonitorStatus::try_from(status) {
Err(_) => 0,
Ok(nodo_pb::MonitorStatus::Unspecified) => 1,
Ok(nodo_pb::MonitorStatus::Critical) => 2,
Ok(nodo_pb::MonitorStatus::Warning) => 3,
Ok(nodo_pb::MonitorStatus::Nominal) => 4,
};
let p1 = status_to_priority(m1.status);
let p2 = status_to_priority(m2.status);
p1.cmp(&p2)
});
let combined_rows: Vec<_> = entries
.into_iter()
.map(|(node_name, meta, mon)| {
Row::new(vec![
{
let (label, color) = match nodo_pb::MonitorStatus::try_from(mon.status) {
Ok(nodo_pb::MonitorStatus::Unspecified) => {
("N/A", STYLE_COLOR_UNAVAILABLE)
}
Ok(nodo_pb::MonitorStatus::Nominal) => ("Nominal", STYLE_COLOR_OK),
Ok(nodo_pb::MonitorStatus::Warning) => ("Warning", STYLE_COLOR_WARN),
Ok(nodo_pb::MonitorStatus::Critical) => ("Critical", STYLE_COLOR_ERR),
_ => ("(error)", STYLE_COLOR_UNAVAILABLE),
};
Cell::from(Span::styled(label.to_string(), Style::default().fg(color)))
},
Cell::from(Span::styled(meta.info, Style::default().fg(Color::Gray))),
Cell::from(Span::styled(
node_name,
Style::default().fg(STYLE_COLOR_NODE_NAME),
)),
{
meta.key
.as_ref()
.map(|key| format_gauge_key(key))
.unwrap_or_else(|| {
Cell::from(Span::styled(
"N/A",
Style::default().fg(STYLE_COLOR_ERR),
))
})
},
{
let label_opt =
mon.value.as_ref().and_then(|cell| cell.value.as_ref()).map(
|v| match v {
nodo_pb::gauge_value::Value::Bool(v) => format!("{v}"),
nodo_pb::gauge_value::Value::Int64(v) => format!("{v}"),
nodo_pb::gauge_value::Value::Usize(v) => format!("{v}"),
nodo_pb::gauge_value::Value::Float64(v) => format!("{v}"),
nodo_pb::gauge_value::Value::String(v) => format!("{v}"),
nodo_pb::gauge_value::Value::Pubtime(v) => format_time(v),
nodo_pb::gauge_value::Value::Acqtime(v) => format_time(v),
},
);
let (label, color) = match label_opt {
Some(label) => (label, STYLE_COLOR_VALUE_DEFAULT),
None => ("N/A".into(), STYLE_COLOR_UNAVAILABLE),
};
Cell::from(Span::styled(label.to_string(), Style::default().fg(color)))
},
Cell::from(match &mon.pubtime {
Some(cell) => Span::styled(
format_time(cell),
Style::default().fg(STYLE_COLOR_VALUE_DEFAULT),
),
None => Span::styled(
String::from("N/A"),
Style::default().fg(STYLE_COLOR_UNAVAILABLE),
),
}),
])
})
.collect();
let table = Table::new(
combined_rows,
&[
Constraint::Fill(1),
Constraint::Fill(5),
Constraint::Fill(2),
Constraint::Fill(2),
Constraint::Fill(2),
Constraint::Fill(1),
],
)
.header(
Row::new(vec![
"Status".into(),
"Info".into(),
"Node".to_string(),
"Gauge".into(),
"Value".into(),
"Time".into(),
])
.style(
Style::default()
.add_modifier(Modifier::BOLD)
.add_modifier(Modifier::REVERSED),
),
)
.style(Style::new().fg(Color::White))
.row_highlight_style(Style::new().add_modifier(Modifier::REVERSED));
StatefulWidget::render(table, area, buf, &mut model.monitors_table_state);
}
}
fn format_gauge_key(key: &nodo_pb::GaugeKey) -> Cell<'static> {
nodo_pb::GaugeKeyKind::try_from(key.kind)
.ok()
.map(|kind| match kind {
nodo_pb::GaugeKeyKind::Unspecified => Cell::from(Span::styled(
"(error)",
Style::default().fg(STYLE_COLOR_ERR),
)),
nodo_pb::GaugeKeyKind::SignalValue => Cell::from(Line::from(vec![
Span::styled(
key.name.clone(),
Style::default().fg(STYLE_COLOR_DETAIL_NAME),
),
Span::raw(" "),
Span::styled("[VALUE]", Style::default().fg(STYLE_COLOR_TYPENAME)),
])),
nodo_pb::GaugeKeyKind::SignalPubtime => Cell::from(Line::from(vec![
Span::styled(
key.name.clone(),
Style::default().fg(STYLE_COLOR_DETAIL_NAME),
),
Span::raw(" "),
Span::styled("[PUBTIME]", Style::default().fg(STYLE_COLOR_TYPENAME)),
])),
nodo_pb::GaugeKeyKind::RxAvailable => Cell::from(Line::from(vec![
Span::styled(
key.name.clone(),
Style::default().fg(STYLE_COLOR_DETAIL_NAME),
),
Span::raw(" "),
Span::styled("[RX_AVAILABLE]", Style::default().fg(STYLE_COLOR_TYPENAME)),
])),
nodo_pb::GaugeKeyKind::TxTotal => Cell::from(Line::from(vec![
Span::styled(
key.name.clone(),
Style::default().fg(STYLE_COLOR_DETAIL_NAME),
),
Span::raw(" "),
Span::styled("[TX_TOTAL]", Style::default().fg(STYLE_COLOR_TYPENAME)),
])),
nodo_pb::GaugeKeyKind::TxPubtime => Cell::from(Line::from(vec![
Span::styled(
key.name.clone(),
Style::default().fg(STYLE_COLOR_DETAIL_NAME),
),
Span::raw(" "),
Span::styled("[TX_PUBTIME]", Style::default().fg(STYLE_COLOR_TYPENAME)),
])),
})
.unwrap_or_else(move || {
Cell::from(Span::styled(
"(invalid)",
Style::default().fg(STYLE_COLOR_ERR),
))
})
}
fn format_time(cell: &nodo_pb::Duration) -> String {
format!(
"{:0.03}",
Duration::new(cell.secs as u64, cell.nanos).as_secs_f32()
)
}