liftof_tui/tabs/
tab_alerts.rs1use std::time::{
5 Instant,
6 };
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 Paragraph,
29 Row,
30 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 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 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 }
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 }
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 let no_alarm_before = 120u64;
159 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 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 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 }
295}