nodo_inspector 0.18.5

Telemetry terminal UI for NODO
// Copyright 2025 David Weikersdorfer

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::Span,
    widgets::{Cell, Row, StatefulWidget, Table, Widget},
};
use std::{cell::RefCell, sync::Arc, time::Duration};

pub struct SignalsController {
    pub model: Arc<RefCell<InspectorModel>>,
}

impl SignalsController {
    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().signals_table_state.select_next();
    }

    fn select_previous(&mut self) {
        self.model
            .borrow_mut()
            .signals_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 SignalsView {
    pub model: Arc<RefCell<InspectorModel>>,
}

impl Widget for &SignalsView {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let mut model = self.model.borrow_mut();

        let entries: Vec<_> = model.tree.iter_signals().collect();

        let combined_rows: Vec<_> = entries
            .iter()
            .map(
                |(_, _, n, (signal_name, nodo_pb::NodeSignal { pubtime, value }))| {
                    Row::new(vec![
                        Cell::from(Span::styled(
                            n.info.node_name.clone(),
                            Style::default().fg(STYLE_COLOR_NODE_NAME),
                        )),
                        Cell::from(Span::styled(
                            signal_name.clone(),
                            Style::default().fg(STYLE_COLOR_DETAIL_NAME),
                        )),
                        Cell::from(match &value {
                            Some(cell) => Span::styled(
                                format!(
                                    "{}",
                                    match cell {
                                        nodo_pb::node_signal::Value::Bool(_) => "bool",
                                        nodo_pb::node_signal::Value::Int64(_) => "i64",
                                        nodo_pb::node_signal::Value::Usize(_) => "usize",
                                        nodo_pb::node_signal::Value::Float64(_) => "f64",
                                        nodo_pb::node_signal::Value::String(_) => "String",
                                    }
                                ),
                                Style::default().fg(STYLE_COLOR_TYPENAME),
                            ),
                            None => Span::styled(
                                String::from("N/A"),
                                Style::default().fg(STYLE_COLOR_UNAVAILABLE),
                            ),
                        }),
                        Cell::from(match &pubtime {
                            Some(cell) => Span::styled(
                                format!(
                                    "{:0.03}",
                                    Duration::new(cell.secs as u64, cell.nanos).as_secs_f32()
                                ),
                                Style::default().fg(STYLE_COLOR_VALUE_DEFAULT),
                            ),
                            None => Span::styled(
                                String::from("N/A"),
                                Style::default().fg(STYLE_COLOR_UNAVAILABLE),
                            ),
                        }),
                        Cell::from(match &value {
                            Some(cell) => Span::styled(
                                format!(
                                    "{}",
                                    match cell {
                                        nodo_pb::node_signal::Value::Bool(v) => format!("{v:?}"),
                                        nodo_pb::node_signal::Value::Int64(v) => format!("{v:?}"),
                                        nodo_pb::node_signal::Value::Usize(v) => format!("{v:?}"),
                                        nodo_pb::node_signal::Value::Float64(v) => format!("{v:?}"),
                                        nodo_pb::node_signal::Value::String(v) => format!("{v:?}"),
                                    }
                                ),
                                Style::default().fg(STYLE_COLOR_VALUE_DEFAULT),
                            ),
                            None => Span::styled(
                                String::from("N/A"),
                                Style::default().fg(STYLE_COLOR_UNAVAILABLE),
                            ),
                        }),
                    ])
                },
            )
            .collect();

        // Create the combined table.
        let table = Table::new(
            combined_rows,
            &[
                Constraint::Fill(1),
                Constraint::Fill(1),
                Constraint::Max(10),
                Constraint::Max(10),
                Constraint::Fill(3),
            ],
        )
        .header(
            Row::new(vec![
                "Node".to_string(),
                "Signal".into(),
                "Type".into(),
                "Time".into(),
                "Value".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));

        // Render the combined table.
        StatefulWidget::render(table, area, buf, &mut model.signals_table_state);
    }
}