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 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 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 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 }
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 }
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 let no_alarm_before = 120u64;
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 ... )");
162 if self.met.elapsed().as_secs() < no_alarm_before {
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());
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 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 }
296}