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