liftof_tui/tabs/
tab_alerts.rs

1//! Overview over precarious conditions
2//! - Alert system
3
4use std::time::{
5  Instant,
6  //Duration,
7};
8
9use std::sync::{
10  Arc,
11  Mutex,
12};
13use std::collections::HashMap;
14
15use ratatui::layout::Rect;
16use ratatui::Frame;
17use ratatui::prelude::*;
18use ratatui::style::{
19  Style,
20  Modifier
21};
22use ratatui::widgets::{
23  Block, 
24  Borders,
25  BorderType,
26  Cell,
27  //HighlightSpacing,
28  Paragraph,
29  Row, 
30  //Scrollbar,
31  //ScrollbarOrientation,
32  //ScrollbarState,
33  Table, 
34  TableState,
35};
36
37
38//use tof_dataclasses::alerts::TofAlert;
39use gondola_core::prelude::TofAlert;
40
41use crate::colors::ColorTheme;
42
43#[derive(Debug, Clone)]
44pub struct AlertTab<'a> {
45  pub theme       : ColorTheme,
46  pub alerts      : Arc<Mutex<HashMap<&'a str, TofAlert<'a>>>>,
47  //pub active_al   : Vec<TofAlert<'a>>,
48  met             : Instant,
49  active_alerts   : Vec<TofAlert<'a>>,
50  table_state     : TableState,
51  check_interval  : Instant,
52  check_every     : u32,
53}
54
55impl AlertTab<'_> {
56  pub fn new<'a>(theme  : ColorTheme,
57                 alerts : Arc<Mutex<HashMap<&'a str,TofAlert<'a>>>>) -> AlertTab<'a> {
58  
59    AlertTab {
60      theme,
61      alerts,
62      //active_al    :  Vec::<TofAlert<'a>>::new(),
63      met            : Instant::now(),
64      table_state    : TableState::default(),
65      active_alerts  : Vec::<TofAlert<'a>>::new(),
66      check_interval : Instant::now(),
67      check_every    : 10,
68    }
69  }
70
71  pub fn check_alert_state(&mut self) {
72    let no_alarm_before  = 120u64;
73    if self.check_interval.elapsed().as_secs() > self.check_every as u64 {
74      match self.alerts.lock() {
75        Ok(mut al) => {
76          let mut triggered = Vec::<&str>::new();
77          for k in al.keys() {
78            if al[k].has_triggered() {
79              triggered.push(k);
80            }
81          }
82          if self.met.elapsed().as_secs() < no_alarm_before {
83            for k in triggered {
84              al.get_mut(k).unwrap().acknowledge();
85            }
86          } else {
87            for k in triggered {
88              if !self.active_alerts.contains(&al[k]) {
89                self.active_alerts.push(al[k].clone());
90                if al.get(k).unwrap().n_paged == 0 {
91                  al.get_mut(k).unwrap().page();
92                }
93              }
94            }
95          }
96        }
97        Err(err) => {
98          error!("Unable to lock alert mutex! Alert state unknown! {err}");
99        }
100      }
101      self.check_interval = Instant::now();
102    }
103  }
104
105  pub fn next_row(&mut self) {
106    info!("Selecting next row!");
107    let i = match self.table_state.selected() {
108      Some(i) => {
109        if i >= self.active_alerts.len() - 1 {
110          0
111        } else {
112          i + 1
113        }
114      }
115      None => 0,
116    };
117    self.table_state.select(Some(i));
118    //self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
119  }
120
121  pub fn previous_row(&mut self) {
122    info!("Selecting previous row!");
123    let i = match self.table_state.selected() {
124      Some(i) => {
125        if i == 0 {
126          self.active_alerts.len() - 1
127        } else {
128          i - 1
129        }
130      }
131      None => 0,
132    };
133    self.table_state.select(Some(i));
134    //self.scroll_state = self.scroll_state.position(i * ITEM_HEIGHT);
135  }
136
137  pub fn render(&mut self, main_window : &Rect, frame : &mut Frame) {
138
139     let selected_row_style = Style::default()
140       .add_modifier(Modifier::REVERSED)
141       .fg(self.theme.hc);
142    
143    let main_lo = Layout::default()
144      .direction(Direction::Horizontal)
145      .constraints(
146          [Constraint::Percentage(30),
147           Constraint::Percentage(70)].as_ref(),
148      )
149      .split(*main_window);
150    let right_col = Layout::default()
151      .direction(Direction::Vertical)
152      .constraints(
153          [Constraint::Percentage(40),
154           Constraint::Percentage(60)].as_ref(),
155      )
156      .split(main_lo[1]);
157
158    // suppress alarms at program start
159    let no_alarm_before  = 120u64;
160    // keep a copy of active alerts
161    let mut fallback     = String::from("You're lucky! Everything is Awesome!\n (... or we haven't yet had the chance to implement this features yet, ore we are not catching the fact that something is misbehaving ... )");
162    if self.met.elapsed().as_secs() < no_alarm_before {
163      // delete all alarms!
164      fallback = format!("Alarms will only be available 2mins after program start since it might take a bit until everything is settled!\n So far {}s have passed!", self.met.elapsed().as_secs());
165    }
166
167    let table_title = format!("Current active alerts \u{26A0} ({})", self.active_alerts.len());
168    if self.active_alerts.is_empty() {
169      let main_view = Paragraph::new(fallback)
170      .style(self.theme.style())
171      .alignment(Alignment::Center)
172      .block(
173        Block::default()
174          .borders(Borders::ALL)
175          .border_type(BorderType::Thick)
176      );
177      frame.render_widget(main_view, *main_window);
178      return;
179    }
180
181
182    //let header_style = Style::default();
183        //.fg(self.colors.header_fg)
184        //.bg(self.colors.header_bg);
185    //let selected_row_style = Style::default();
186        //.add_modifier(Modifier::REVERSED)
187        //.fg(self.colors.selected_row_style_fg);
188    //let selected_col_style = Style::default().fg(self.colors.selected_column_style_fg);
189    
190    //let selected_cell_style = Style::default()
191    //    .add_modifier(Modifier::REVERSED)
192    //    .fg(self.colors.selected_cell_style_fg);
193
194    //let header = ["What", "Variable", "Condition", "Description", "Required Action?"]
195    //  .into_iter()
196    //  .map(Cell::from)
197    //  .collect::<Row>()
198    //  //.style(header_style)
199    //  .style(self.theme.style())
200    //  .height(1);
201    //let rows = alerts.iter().enumerate().map(|(i, data)| {
202    let mut rows   = Vec::<Row>::new();
203    for ale in &self.active_alerts {
204      rows.push(Row::new(vec![Cell::from(Text::from(format!("{}", ale)))]));
205    }
206    let widths = [Constraint::Percentage(100)];
207    let table  = Table::new(rows, widths)
208      .column_spacing(1)
209      .row_highlight_style(selected_row_style)
210      .header(
211        Row::new(vec![" ALERT "])
212        .bottom_margin(1)
213        .top_margin(1)
214        .style(Style::new().add_modifier(Modifier::UNDERLINED))
215      )
216      .block(Block::new()
217        .title(table_title)
218        .borders(Borders::ALL)
219        .border_type(BorderType::Thick)
220        )
221      .style(self.theme.style());
222    frame.render_stateful_widget(table, main_lo[0], &mut self.table_state);
223   
224    match self.table_state.selected() {
225      Some(trow) => {
226        let descr_view = Paragraph::new(self.active_alerts[trow].descr)
227          .style(self.theme.style())
228          .alignment(Alignment::Left)
229          .block(
230            Block::default()
231              .borders(Borders::ALL)
232              .border_type(BorderType::Thick)
233        );
234        frame.render_widget(descr_view, right_col[0]);
235        let mut message = String::from("");
236        for k in &self.active_alerts[trow].whattodo {
237          message += k;
238        }
239        let whattodo_view = Paragraph::new(message)
240          .style(self.theme.style())
241          .alignment(Alignment::Left)
242          .block(
243            Block::default()
244              .borders(Borders::ALL)
245              .border_type(BorderType::Thick)
246        );
247        frame.render_widget(whattodo_view, right_col[1]);
248      }
249      None => ()
250    }
251    //let whattodo_view = Paragraph::new(self.active_alerts[self.table_state as usize].whattodo)
252    //  .style(self.theme.style())
253    //  .alignment(Alignment::Center)
254    //  .block(
255    //    Block::default()
256    //      .borders(Borders::ALL)
257    //      .border_type(BorderType::Thick)
258    //);
259    //frame.render_widget(whattodo_view, right_col[1]);
260    
261    //    let color = match i % 2 {
262    //        0 => self.colors.normal_row_color,
263    //        _ => self.colors.alt_row_color,
264    //    };
265    //    let item = data.ref_array();
266    //    item.into_iter()
267    //        .map(|content| Cell::from(Text::from(format!("\n{content}\n"))))
268    //        .collect::<Row>()
269    //        .style(Style::new().fg(self.colors.row_fg).bg(color))
270    //        .height(4)
271    //});
272    //let bar = " █ ";
273    //let t = Table::new(
274    //    rows,
275    //    [
276    //        // + 1 is for padding.
277    //        Constraint::Length(self.longest_item_lens.0 + 1),
278    //        Constraint::Min(self.longest_item_lens.1 + 1),
279    //        Constraint::Min(self.longest_item_lens.2),
280    //    ],
281    //)
282    //.header(header)
283    //.row_highlight_style(selected_row_style)
284    //.column_highlight_style(selected_col_style)
285    //.cell_highlight_style(selected_cell_style)
286    //.highlight_symbol(Text::from(vec![
287    //    "".into(),
288    //    bar.into(),
289    //    bar.into(),
290    //    "".into(),
291    //]))
292    //.bg(self.colors.buffer_bg)
293    //.highlight_spacing(HighlightSpacing::Always);
294    //frame.render_stateful_widget(t, main_window, &mut self.state);
295  }
296}