1use std::rc::Rc;
10
11use super::super::super::node_mgmt::{PORT_MAX, PORT_MIN};
12use super::super::utils::centered_rect_fixed;
13use super::super::Component;
14use super::manage_nodes::MAX_NODE_COUNT;
15use crate::style::RED;
16use crate::{
17 action::{Action, OptionsActions},
18 connection_mode::ConnectionMode,
19 mode::{InputMode, Scene},
20 style::{clear_area, EUCALYPTUS, GHOST_WHITE, INDIGO, LIGHT_PERIWINKLE, VIVID_SKY_BLUE},
21};
22use color_eyre::Result;
23use crossterm::event::{Event, KeyCode, KeyEvent};
24use ratatui::{prelude::*, widgets::*};
25use tui_input::{backend::crossterm::EventHandler, Input};
26
27pub const PORT_ALLOCATION: u32 = MAX_NODE_COUNT as u32 - 1; const INPUT_SIZE: u32 = 5;
29const INPUT_AREA: u32 = INPUT_SIZE + 2; #[derive(Default)]
32enum PortRangeState {
33 #[default]
34 Selection,
35 ConfirmChange,
36 PortForwardingInfo,
37}
38
39pub struct PortRangePopUp {
40 active: bool,
41 state: PortRangeState,
42 connection_mode: ConnectionMode,
43 connection_mode_old_value: Option<ConnectionMode>,
44 port_from: Input,
45 port_to: Input,
46 port_from_old_value: u32,
47 port_to_old_value: u32,
48 can_save: bool,
49 first_stroke: bool,
50}
51
52impl PortRangePopUp {
53 pub fn new(connection_mode: ConnectionMode, port_from: u32, port_to: u32) -> Self {
54 Self {
55 active: false,
56 state: PortRangeState::Selection,
57 connection_mode,
58 connection_mode_old_value: None,
59 port_from: Input::default().with_value(port_from.to_string()),
60 port_to: Input::default().with_value(port_to.to_string()),
61 port_from_old_value: Default::default(),
62 port_to_old_value: Default::default(),
63 can_save: false,
64 first_stroke: true,
65 }
66 }
67
68 pub fn validate(&mut self) {
69 if self.port_from.value().is_empty() {
70 self.can_save = false;
71 } else {
72 let port_from: u32 = self.port_from.value().parse().unwrap_or_default();
73 let port_to: u32 = self.port_to.value().parse().unwrap_or_default();
74 self.can_save = (PORT_MIN..=PORT_MAX).contains(&port_from)
75 && (PORT_MIN..=PORT_MAX).contains(&port_to)
76 && port_from <= port_to;
77 }
78 }
79
80 fn draw_selection_state(
84 &mut self,
85 f: &mut crate::tui::Frame<'_>,
86 layer_zero: Rect,
87 layer_one: Rc<[Rect]>,
88 ) -> Paragraph {
89 let pop_up_border = Paragraph::new("").block(
91 Block::default()
92 .borders(Borders::ALL)
93 .title(" Custom Ports ")
94 .bold()
95 .title_style(Style::new().fg(VIVID_SKY_BLUE))
96 .padding(Padding::uniform(2))
97 .border_style(Style::new().fg(VIVID_SKY_BLUE)),
98 );
99 clear_area(f, layer_zero);
100
101 let layer_two = Layout::new(
103 Direction::Vertical,
104 [
105 Constraint::Length(3),
107 Constraint::Length(2),
109 Constraint::Length(3),
111 Constraint::Length(3),
113 Constraint::Length(1),
115 ],
116 )
117 .split(layer_one[1]);
118
119 let prompt = Paragraph::new("Enter Port Number")
120 .bold()
121 .alignment(Alignment::Center);
122
123 f.render_widget(prompt.fg(GHOST_WHITE), layer_two[0]);
124
125 let spaces_from = " ".repeat((INPUT_AREA - 1) as usize - self.port_from.value().len());
126
127 let input_line = Line::from(vec![
128 Span::styled(
129 format!("{}{} ", spaces_from, self.port_from.value()),
130 Style::default()
131 .fg(if self.can_save { VIVID_SKY_BLUE } else { RED })
132 .bg(INDIGO)
133 .underlined(),
134 ),
135 Span::styled(" to ", Style::default().fg(GHOST_WHITE)),
136 Span::styled(self.port_to.value(), Style::default().fg(LIGHT_PERIWINKLE)),
137 ])
138 .alignment(Alignment::Center);
139
140 f.render_widget(input_line, layer_two[1]);
141
142 let text = Paragraph::new(vec![
143 Line::from(Span::styled(
144 format!(
145 "Choose the start of the range of {} ports.",
146 PORT_ALLOCATION + 1
147 ),
148 Style::default().fg(LIGHT_PERIWINKLE),
149 )),
150 Line::from(Span::styled(
151 format!("This must be between {} and {}.", PORT_MIN, PORT_MAX),
152 Style::default().fg(if self.can_save { LIGHT_PERIWINKLE } else { RED }),
153 )),
154 ])
155 .block(block::Block::default().padding(Padding::horizontal(2)))
156 .alignment(Alignment::Center)
157 .wrap(Wrap { trim: true });
158 f.render_widget(text.fg(GHOST_WHITE), layer_two[2]);
159
160 let dash = Block::new()
161 .borders(Borders::BOTTOM)
162 .border_style(Style::new().fg(GHOST_WHITE));
163 f.render_widget(dash, layer_two[3]);
164
165 let buttons_layer =
166 Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
167 .split(layer_two[4]);
168
169 let button_no = Line::from(vec![Span::styled(
170 " Cancel [Esc]",
171 Style::default().fg(LIGHT_PERIWINKLE),
172 )]);
173 let button_yes_style = if self.can_save {
174 Style::default().fg(EUCALYPTUS)
175 } else {
176 Style::default().fg(LIGHT_PERIWINKLE)
177 };
178 f.render_widget(button_no, buttons_layer[0]);
179
180 let button_yes = Line::from(vec![
181 Span::styled("Save Port Range ", button_yes_style),
182 Span::styled("[Enter]", Style::default().fg(GHOST_WHITE)),
183 ]);
184 f.render_widget(button_yes, buttons_layer[1]);
185
186 pop_up_border
187 }
188
189 fn draw_confirm_and_reset(
191 &mut self,
192 f: &mut crate::tui::Frame<'_>,
193 layer_zero: Rect,
194 layer_one: Rc<[Rect]>,
195 ) -> Paragraph {
196 let pop_up_border = Paragraph::new("").block(
198 Block::default()
199 .borders(Borders::ALL)
200 .title(" Confirm & Reset ")
201 .bold()
202 .title_style(Style::new().fg(VIVID_SKY_BLUE))
203 .padding(Padding::uniform(2))
204 .border_style(Style::new().fg(VIVID_SKY_BLUE)),
205 );
206 clear_area(f, layer_zero);
207
208 let layer_two = Layout::new(
210 Direction::Vertical,
211 [
212 Constraint::Length(8),
214 Constraint::Length(3),
216 Constraint::Length(1),
218 ],
219 )
220 .split(layer_one[1]);
221
222 let paragraph_text = Paragraph::new(vec![
223 Line::from(Span::styled("\n\n", Style::default())),
224 Line::from(Span::styled("\n\n", Style::default())),
225 Line::from(vec![
226 Span::styled(
227 "Changing connection mode will ",
228 Style::default().fg(LIGHT_PERIWINKLE),
229 ),
230 Span::styled("reset all nodes.", Style::default().fg(GHOST_WHITE)),
231 ]),
232 Line::from(Span::styled("\n\n", Style::default())),
233 Line::from(Span::styled("\n\n", Style::default())),
234 Line::from(Span::styled("\n\n", Style::default())),
235 Line::from(vec![
236 Span::styled("You’ll need to ", Style::default().fg(LIGHT_PERIWINKLE)),
237 Span::styled("Add", Style::default().fg(GHOST_WHITE)),
238 Span::styled(" and ", Style::default().fg(LIGHT_PERIWINKLE)),
239 Span::styled("Start", Style::default().fg(GHOST_WHITE)),
240 Span::styled(
241 " them again afterwards. Are you sure you want to continue?",
242 Style::default().fg(LIGHT_PERIWINKLE),
243 ),
244 ]),
245 ])
246 .alignment(Alignment::Left)
247 .wrap(Wrap { trim: true })
248 .block(block::Block::default().padding(Padding::horizontal(2)));
249
250 f.render_widget(paragraph_text, layer_two[0]);
251
252 let dash = Block::new()
253 .borders(Borders::BOTTOM)
254 .border_style(Style::new().fg(GHOST_WHITE));
255 f.render_widget(dash, layer_two[1]);
256
257 let buttons_layer =
258 Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
259 .split(layer_two[2]);
260
261 let button_no = Line::from(vec![Span::styled(
262 " Cancel [Esc]",
263 Style::default().fg(LIGHT_PERIWINKLE),
264 )]);
265 let button_yes_style = if self.can_save {
266 Style::default().fg(EUCALYPTUS)
267 } else {
268 Style::default().fg(LIGHT_PERIWINKLE)
269 };
270 f.render_widget(button_no, buttons_layer[0]);
271
272 let button_yes = Line::from(vec![
273 Span::styled("Yes, Change Mode ", button_yes_style),
274 Span::styled("[Enter]", Style::default().fg(GHOST_WHITE)),
275 ]);
276 f.render_widget(button_yes, buttons_layer[1]);
277
278 pop_up_border
279 }
280
281 fn draw_info_port_forwarding(
283 &mut self,
284 f: &mut crate::tui::Frame<'_>,
285 layer_zero: Rect,
286 layer_one: Rc<[Rect]>,
287 ) -> Paragraph {
288 let pop_up_border = Paragraph::new("").block(
290 Block::default()
291 .borders(Borders::ALL)
292 .title(" Port Forwarding For Private IPs ")
293 .bold()
294 .title_style(Style::new().fg(VIVID_SKY_BLUE))
295 .padding(Padding::uniform(2))
296 .border_style(Style::new().fg(VIVID_SKY_BLUE)),
297 );
298 clear_area(f, layer_zero);
299
300 let layer_two = Layout::new(
302 Direction::Vertical,
303 [
304 Constraint::Length(8),
306 Constraint::Length(3),
308 Constraint::Length(1),
310 ],
311 )
312 .split(layer_one[1]);
313
314 let paragraph_text = Paragraph::new(vec![
315 Line::from(Span::styled("\n\n",Style::default())),
316 Line::from(Span::styled("If you have a Private IP (which you probably do) you’ll now need to set your router to…\n\n", Style::default().fg(LIGHT_PERIWINKLE))),
317 Line::from(Span::styled("\n\n",Style::default())),
318 Line::from(Span::styled(
319 format!("Port Forward ports {}-{} ", self.port_from.value(), self.port_to.value()),
320 Style::default().fg(GHOST_WHITE),
321 )),
322 Line::from(Span::styled("\n\n",Style::default())),
323 Line::from(Span::styled("You can do this in your router’s admin panel.\n\n", Style::default().fg(LIGHT_PERIWINKLE))),
324 ])
325 .alignment(Alignment::Left)
326 .wrap(Wrap { trim: true })
327 .block(block::Block::default().padding(Padding::horizontal(2)));
328
329 f.render_widget(paragraph_text, layer_two[0]);
330
331 let dash = Block::new()
332 .borders(Borders::BOTTOM)
333 .border_style(Style::new().fg(GHOST_WHITE));
334 f.render_widget(dash, layer_two[1]);
335
336 let buttons_layer =
337 Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
338 .split(layer_two[2]);
339
340 let button_ok = Line::from(vec![
341 Span::styled("OK ", Style::default().fg(EUCALYPTUS)),
342 Span::styled("[Enter] ", Style::default().fg(GHOST_WHITE)),
343 ])
344 .alignment(Alignment::Right);
345
346 f.render_widget(button_ok, buttons_layer[1]);
347
348 pop_up_border
349 }
350}
351
352impl Component for PortRangePopUp {
353 fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
354 if !self.active {
355 return Ok(vec![]);
356 }
357 let send_back: Vec<Action> = match &self.state {
359 PortRangeState::Selection => {
360 match key.code {
361 KeyCode::Enter => {
362 if self.port_from_old_value
363 == self.port_from.value().parse::<u32>().unwrap_or_default()
364 && self.port_to_old_value
365 == self.port_to.value().parse::<u32>().unwrap_or_default()
366 && self.connection_mode_old_value != Some(ConnectionMode::CustomPorts)
367 && self.can_save
368 {
369 self.state = PortRangeState::ConfirmChange;
370 return Ok(vec![]);
371 }
372 let port_from = self.port_from.value();
373 let port_to = self.port_to.value();
374
375 if port_from.is_empty() || port_to.is_empty() || !self.can_save {
376 debug!("Got Enter, but port_from or port_to is empty, ignoring.");
377 return Ok(vec![]);
378 }
379 debug!("Got Enter, saving the ports and switching to Options Screen",);
380 self.state = PortRangeState::ConfirmChange;
381 vec![]
382 }
383 KeyCode::Esc => {
384 debug!("Got Esc, restoring the old values and switching to actual screen");
385 if let Some(connection_mode_old_value) = self.connection_mode_old_value {
386 debug!("{:?}", connection_mode_old_value);
387 vec![
388 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
389 connection_mode_old_value,
390 )),
391 Action::SwitchScene(Scene::Options),
392 ]
393 } else {
394 if self.port_from_old_value.to_string() == "0"
397 && self.port_to_old_value.to_string() == "0"
398 {
399 self.connection_mode = self
400 .connection_mode_old_value
401 .unwrap_or(ConnectionMode::Automatic);
402 return Ok(vec![
403 Action::StoreConnectionMode(self.connection_mode),
404 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
405 self.connection_mode,
406 )),
407 Action::SwitchScene(Scene::Options),
408 ]);
409 }
410 self.port_from = self
411 .port_from
412 .clone()
413 .with_value(self.port_from_old_value.to_string());
414 self.port_to = self
415 .port_to
416 .clone()
417 .with_value(self.port_to_old_value.to_string());
418 vec![Action::SwitchScene(Scene::Options)]
419 }
420 }
421 KeyCode::Char(c) if !c.is_numeric() => vec![],
422 KeyCode::Up => {
423 if self.port_from.value().parse::<u32>().unwrap_or_default() < PORT_MAX {
424 self.port_from = self.port_from.clone().with_value(
425 (self.port_from.value().parse::<u32>().unwrap_or_default() + 1)
426 .to_string(),
427 );
428 let port_from_value: u32 =
429 self.port_from.value().parse().unwrap_or_default();
430 if port_from_value + PORT_ALLOCATION <= PORT_MAX {
431 self.port_to = Input::default()
432 .with_value((port_from_value + PORT_ALLOCATION).to_string());
433 } else {
434 self.port_to = Input::default().with_value("-".to_string());
435 }
436 };
437 self.validate();
438 vec![]
439 }
440 KeyCode::Down => {
441 if self.port_from.value().parse::<u32>().unwrap_or_default() > 0 {
442 self.port_from = self.port_from.clone().with_value(
443 (self.port_from.value().parse::<u32>().unwrap_or_default() - 1)
444 .to_string(),
445 );
446 let port_from_value: u32 =
447 self.port_from.value().parse().unwrap_or_default();
448 if port_from_value + PORT_ALLOCATION <= PORT_MAX {
449 self.port_to = Input::default()
450 .with_value((port_from_value + PORT_ALLOCATION).to_string());
451 } else {
452 self.port_to = Input::default().with_value("-".to_string());
453 }
454 };
455 self.validate();
456 vec![]
457 }
458 KeyCode::Backspace => {
459 self.port_from.handle_event(&Event::Key(key));
460 let port_from_value: u32 =
461 self.port_from.value().parse().unwrap_or_default();
462 self.port_to = Input::default()
463 .with_value((port_from_value + PORT_ALLOCATION).to_string());
464 self.validate();
465 vec![]
466 }
467 _ => {
468 if self.first_stroke {
469 self.first_stroke = false;
470 self.port_from = Input::default().with_value("".to_string());
471 }
472 if self.port_from.value().len() < INPUT_SIZE as usize {
474 self.port_from.handle_event(&Event::Key(key));
475 let port_from_value: u32 =
476 self.port_from.value().parse().unwrap_or_default();
477 if port_from_value + PORT_ALLOCATION <= PORT_MAX {
478 self.port_to = Input::default()
479 .with_value((port_from_value + PORT_ALLOCATION).to_string());
480 } else {
481 self.port_to = Input::default().with_value("-".to_string());
482 }
483 };
484 self.validate();
485 vec![]
486 }
487 }
488 }
489 PortRangeState::ConfirmChange => match key.code {
490 KeyCode::Enter => {
491 self.state = PortRangeState::PortForwardingInfo;
492 vec![
493 Action::StoreConnectionMode(ConnectionMode::CustomPorts),
494 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
495 ConnectionMode::CustomPorts,
496 )),
497 Action::StorePortRange(
498 self.port_from.value().parse().unwrap_or_default(),
499 self.port_to.value().parse().unwrap_or_default(),
500 ),
501 Action::OptionsActions(OptionsActions::UpdatePortRange(
502 self.port_from.value().parse().unwrap_or_default(),
503 self.port_to.value().parse().unwrap_or_default(),
504 )),
505 ]
506 }
507 KeyCode::Esc => {
508 self.state = PortRangeState::Selection;
509 if let Some(connection_mode_old_value) = self.connection_mode_old_value {
510 if self.port_from_old_value != 0 && self.port_to_old_value != 0 {
511 vec![
512 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
513 connection_mode_old_value,
514 )),
515 Action::OptionsActions(OptionsActions::UpdatePortRange(
516 self.port_from_old_value,
517 self.port_to_old_value,
518 )),
519 Action::SwitchScene(Scene::Options),
520 ]
521 } else {
522 vec![
523 Action::OptionsActions(OptionsActions::UpdateConnectionMode(
524 connection_mode_old_value,
525 )),
526 Action::SwitchScene(Scene::Options),
527 ]
528 }
529 } else {
530 vec![Action::SwitchScene(Scene::Options)]
531 }
532 }
533 _ => vec![],
534 },
535 PortRangeState::PortForwardingInfo => match key.code {
536 KeyCode::Enter => {
537 debug!("Got Enter, saving the ports and switching to Status Screen",);
538 self.state = PortRangeState::Selection;
539 vec![Action::SwitchScene(Scene::Status)]
540 }
541 _ => vec![],
542 },
543 };
544 Ok(send_back)
545 }
546
547 fn update(&mut self, action: Action) -> Result<Option<Action>> {
548 let send_back = match action {
549 Action::SwitchScene(scene) => match scene {
550 Scene::ChangePortsPopUp {
551 connection_mode_old_value,
552 } => {
553 if self.connection_mode == ConnectionMode::CustomPorts {
554 self.active = true;
555 self.first_stroke = true;
556 self.connection_mode_old_value = connection_mode_old_value;
557 self.validate();
558 self.port_from_old_value =
559 self.port_from.value().parse().unwrap_or_default();
560 self.port_to_old_value = self.port_to.value().parse().unwrap_or_default();
561 Some(Action::SwitchInputMode(InputMode::Entry))
564 } else {
565 self.active = false;
566 Some(Action::SwitchScene(Scene::Options))
567 }
568 }
569 _ => {
570 self.active = false;
571 None
572 }
573 },
574 Action::OptionsActions(OptionsActions::UpdateConnectionMode(connection_mode)) => {
576 self.connection_mode = connection_mode;
577 None
578 }
579 _ => None,
580 };
581 Ok(send_back)
582 }
583
584 fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
585 if !self.active {
586 return Ok(());
587 }
588
589 let layer_zero = centered_rect_fixed(52, 15, area);
590
591 let layer_one = Layout::new(
592 Direction::Vertical,
593 [
594 Constraint::Length(2),
596 Constraint::Min(1),
598 Constraint::Length(1),
600 ],
601 )
602 .split(layer_zero);
603
604 let pop_up_border: Paragraph = match self.state {
605 PortRangeState::Selection => self.draw_selection_state(f, layer_zero, layer_one),
606 PortRangeState::ConfirmChange => self.draw_confirm_and_reset(f, layer_zero, layer_one),
607 PortRangeState::PortForwardingInfo => {
608 self.draw_info_port_forwarding(f, layer_zero, layer_one)
609 }
610 };
611 f.render_widget(pop_up_border, layer_zero);
613
614 Ok(())
615 }
616}