diff --git a/src/app/entry.rs b/src/app/entry.rs index ff1a245..574c786 100644 --- a/src/app/entry.rs +++ b/src/app/entry.rs @@ -29,7 +29,14 @@ impl Entry { toPort: String, ) -> Result { let ip = Regex::new("^(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\.(?:25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])$").unwrap(); - if !ip.is_match(fromIP.trim()) { + + let newFromIP = if fromIP.eq("*") { + "0.0.0.0".to_string() + } else { + fromIP + }; + + if !ip.is_match(newFromIP.trim()) { Err(EntryValError::FromIPValError) } else if !fromPort.parse::().is_ok_and(|a| a > 1 && a < 65535) { Err(EntryValError::FromPortValError) @@ -39,7 +46,7 @@ impl Entry { Err(EntryValError::ToPortValError) } else { Ok(Entry { - fromIP, + fromIP: newFromIP, toIP, fromPort: fromPort.trim_start_matches('0').to_string(), toPort: toPort.trim_start_matches('0').to_string(), diff --git a/src/app/mod.rs b/src/app/mod.rs index 6c58f2f..9de9dcf 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,6 +3,8 @@ pub mod entry; mod settings; pub mod status; +use std::{thread::sleep, time::Duration}; + use crate::app::{ entry::Entry, settings::Settings, @@ -102,13 +104,13 @@ impl AppState { } } - pub fn print(&self) { - let tempEntry = &self.entries[0]; - let res = serde_json::to_string(tempEntry).unwrap(); - println!("{res}"); - let resString = tempEntry.to_string(); - println!("{resString}"); - } + // pub fn print(&self) { + // let tempEntry = &self.entries[0]; + // let res = serde_json::to_string(tempEntry).unwrap(); + // println!("{res}"); + // let resString = tempEntry.to_string(); + // println!("{resString}"); + // } pub fn saveConfigToScript(&self) { let mut outputString: String = String::new(); @@ -120,6 +122,12 @@ impl AppState { )) } outputString.push_str("\nwait"); + // for o in 0..1 { + // println!("test {o}"); + // sleep(Duration::from_secs(1)); + // } + println!("Writing config to system"); + sleep(Duration::from_millis(100)); let _ = std::fs::write("./forward.sh", outputString); } diff --git a/src/main.rs b/src/main.rs index 720aa2b..4eebe72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,9 @@ mod app; mod ui; +use crate::app::status::{ + AppStatus, CurrentScreen, EditingField, EntryValError, entryValError2Field, +}; use crate::ui::ui; use app::AppState; use color_eyre::Result; @@ -14,12 +17,9 @@ use ratatui::crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, }; use ratatui::prelude::{Backend, CrosstermBackend}; +use std::env::var; use std::io; -use crate::app::status::{ - AppStatus, CurrentScreen, EditingField, EntryValError, entryValError2Field, -}; - fn main() -> Result<()> { enable_raw_mode()?; let mut stderr = io::stderr(); // This is a special case. Normally using stdout is fine @@ -27,10 +27,13 @@ fn main() -> Result<()> { let backend = CrosstermBackend::new(stderr); let mut terminal = Terminal::new(backend)?; + let configHome = var("XDG_CONFIG_HOME") + .or_else(|_| var("HOME").map(|home| format!("{}/.config", home))) + .unwrap_or("~/.config".to_string()); // create app and run it - let mut app = AppState::new("./conf.json".to_string()); + let mut appSettings = AppState::new(format!("{configHome}/steckbrett.conf")); // app.load(); - let res = runApp(&mut app, &mut terminal); + let _ = runApp(&mut appSettings, &mut terminal); disable_raw_mode()?; execute!( terminal.backend_mut(), @@ -39,17 +42,15 @@ fn main() -> Result<()> { )?; terminal.show_cursor()?; - if let Ok(do_print) = res { - if do_print { - app.print(); - } - } else if let Err(err) = res { - println!("{err:?}"); - } + // match res { + // Ok(ent) => println!("{ent} entries "), + // _ => {} + // } + Ok(()) } -fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result { +fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result { loop { terminal.draw(|f| ui(f, app))?; // from ui.rs if let Event::Key(key) = event::read()? { @@ -86,6 +87,7 @@ fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result< app.saveConfigToScript(); app.saveConfigToSettingsFile(); app.appStatus = AppStatus::Saved; + terminal.clear()?; } } KeyCode::F(2) => app.screen = CurrentScreen::Settings, @@ -184,9 +186,9 @@ fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result< }, CurrentScreen::Exit => match key.code { KeyCode::Enter => { - app.saveConfigToSettingsFile(); - app.saveConfigToScript(); - return Ok(true); + // app.saveConfigToSettingsFile(); + // app.saveConfigToScript(); + return Ok(app.settings.entries.len()); } KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main, _ => {} diff --git a/src/ui/entryTable.rs b/src/ui/entryTable.rs new file mode 100644 index 0000000..1ff38d5 --- /dev/null +++ b/src/ui/entryTable.rs @@ -0,0 +1,72 @@ +use crate::app::{ + entry::Entry, + status::{AppStatus, CurrentScreen}, +}; +use ratatui::{ + layout::Constraint, + style::{Color, Modifier, Style}, + text::Text, + widgets::{Block, Borders, Cell, Row, Table}, +}; + +pub fn getTableElement<'a>( + entries: &Vec, + curScreen: &CurrentScreen, + appStatus: &AppStatus, + isHashDifferent: bool, +) -> Table<'static> { + let headers = ["No.", "From IP", "From Port", "-->", "To IP", "To Port"] + .into_iter() + .map(Cell::from) + .collect::() + .style(Style::default().fg(Color::White).bg(Color::Black)) + .height(2); + + let dataRows = entries.iter().enumerate().map(|(i, d)| { + let item = d.array(i + 1); + item.into_iter() + .map(|content| Cell::from(Text::from(format!("\n{content}\n")))) + .collect::() + .style(Style::new().fg(Color::White).bg(Color::Black)) + .height(2) + }); + let selectedColor = match curScreen { + CurrentScreen::Delete => Color::Yellow, + _ => Color::DarkGray, + }; + let tableBorderColor = match appStatus { + AppStatus::Saved => Color::LightGreen, + AppStatus::Welcome => Color::White, + _ => { + if isHashDifferent { + Color::LightYellow + } else { + Color::White + } + } + }; + + let table = Table::new( + dataRows, + [ + Constraint::Percentage(4), + Constraint::Percentage(28), + Constraint::Percentage(18), + Constraint::Percentage(4), + Constraint::Percentage(28), + Constraint::Percentage(18), + ], + ) + .header(headers) + .block( + Block::default() + .borders(Borders::all()) + .style(Style::default().fg(tableBorderColor)), + ) + .row_highlight_style( + Style::default() + .add_modifier(Modifier::REVERSED) + .fg(selectedColor), + ); + table +} diff --git a/src/ui/exitPrompt.rs b/src/ui/exitPrompt.rs new file mode 100644 index 0000000..207e55e --- /dev/null +++ b/src/ui/exitPrompt.rs @@ -0,0 +1,25 @@ +use ratatui::{ + style::{Color, Style}, + text::{Line, Text}, + widgets::{Block, Borders, Paragraph, Wrap}, +}; + +pub fn getExitPara() -> Paragraph<'static> { + let exitPopup = Block::default() + .title("Exit Window") + .borders(Borders::ALL) + .style(Style::default().bg(Color::DarkGray)) + .border_style(Style::default().fg(Color::Red)); + + let exitText = Text::from(vec![ + Line::from("Exit the app?"), + Line::from("ANY UNSAVED CHANGES WILL BE DISCARDED."), + Line::from("Press (s) to save from the main screen."), + ]); + let exitPara = Paragraph::new(exitText) + .block(exitPopup) + .wrap(Wrap { trim: false }) + .style(Style::default().fg(Color::White)); + + exitPara +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 07c2098..e3d5f97 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,13 +1,15 @@ #![allow(non_snake_case)] mod centeredRect; +mod entryTable; +mod exitPrompt; mod textHints; use ratatui::{ Frame, layout::{Constraint, Direction, Layout}, - style::{Color, Modifier, Style}, + style::{Color, Style}, text::{Line, Span, Text}, - widgets::{Block, Borders, Cell, Paragraph, Row, Table, Wrap}, + widgets::{Block, Borders, Paragraph}, }; use crate::app::status::{AppStatus, CurrentScreen, EntryValError}; @@ -48,104 +50,22 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { frame.render_widget(title, titleBodyChunks[0]); - // Create and add body - // let mut currentItems = Vec::::new(); - // for (i, e) in app.entries.iter().enumerate() { - // currentItems.push(ListItem::new(Line::from(Span::styled( - // format!("{i}. {e}"), - // Style::default().fg(Color::Yellow), - // )))); - // } - // table drawing - let headers = ["No.", "From IP", "From Port", "-->", "To IP", "To Port"] - .into_iter() - .map(Cell::from) - .collect::() - .style(Style::default().fg(Color::White).bg(Color::Black)) - .height(2); - - let dataRows = app.entries.iter().enumerate().map(|(i, d)| { - let item = d.array(i + 1); - item.into_iter() - .map(|content| Cell::from(Text::from(format!("\n{content}\n")))) - .collect::() - .style(Style::new().fg(Color::White).bg(Color::Black)) - .height(2) - }); - let selectedColor = match app.screen { - CurrentScreen::Delete => Color::Yellow, - _ => Color::DarkGray, - }; - let tableBorderColor = match app.appStatus { - AppStatus::Saved => Color::LightGreen, - AppStatus::Welcome => Color::White, - _ => { - if app.isHashDifferent() { - Color::LightYellow - } else { - Color::White - } - } - }; - - // if app.appStatus == AppStatus::Saved { - // Color::LightGreen - // } else if app.appStatus == AppStatus::Welcome { - // Color::White - // } else { - // if app.isHashDifferent() { - // Color::LightYellow - // } else { - // Color::White - // } - // }; - let table = Table::new( - dataRows, - [ - Constraint::Percentage(4), - Constraint::Percentage(28), - Constraint::Percentage(18), - Constraint::Percentage(4), - Constraint::Percentage(28), - Constraint::Percentage(18), - ], - ) - .header(headers) - .block( - Block::default() - .borders(Borders::all()) - .style(Style::default().fg(tableBorderColor)), - ) - .row_highlight_style( - Style::default() - .add_modifier(Modifier::REVERSED) - .fg(selectedColor), + let table = entryTable::getTableElement( + &app.entries, + &app.screen, + &app.appStatus, + app.isHashDifferent(), ); - // let list = List::new(currentItems).block(Block::default().borders(Borders::all())); frame.render_stateful_widget(table, titleBodyChunks[1], &mut app.tableState); // Renter exit prompt if let CurrentScreen::Exit = app.screen { - let exitPopup = Block::default() - .title("Exit Window") - .borders(Borders::ALL) - .style(Style::default().bg(Color::DarkGray)) - .border_style(Style::default().fg(Color::Red)); - - let exitText = Text::from(vec![ - Line::from("Exit the app?"), - Line::from("ANY UNSAVED CHANGES WILL BE DISCARDED."), - Line::from("Press (s) to save from the main screen."), - ]); - let exitPara = Paragraph::new(exitText) - .block(exitPopup) - .wrap(Wrap { trim: false }) - .style(Style::default().fg(Color::White)); - - let area = centered_rect(60, 25, titleBodyChunks[1]); - frame.render_widget(exitPara, area); + frame.render_widget( + exitPrompt::getExitPara(), + centered_rect(60, 25, titleBodyChunks[1]), + ); } // Editing screen popup @@ -154,7 +74,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { .title("Add an entry") .borders(Borders::NONE) .style(Style::default().bg(Color::DarkGray)); - let area = centered_rect(75, 20, titleBodyChunks[1]); + let area = centered_rect(75, 30, titleBodyChunks[1]); frame.render_widget(popup, area); let fields = Layout::default() diff --git a/src/ui/textHints.rs b/src/ui/textHints.rs index 71c526b..95bb7bc 100644 --- a/src/ui/textHints.rs +++ b/src/ui/textHints.rs @@ -27,16 +27,16 @@ pub mod hints { pub fn exitHints<'a>() -> Text<'a> { Text::from(vec![ - Line::from("(enter) save and exit").style(Style::default().fg(Color::Red)), - Line::from("(esc) main menu").style(Style::default().fg(Color::LightBlue)), + Line::from("(enter) Exit").style(Style::default().fg(Color::Red)), + Line::from("(esc) Main menu").style(Style::default().fg(Color::LightBlue)), ]) } pub fn delHints<'a>() -> Text<'a> { Text::from(vec![ Line::from("(↑/↓) Scroll").style(Style::default().fg(Color::White)), - Line::from("(enter) delete selection").style(Style::default().fg(Color::Magenta)), - Line::from("(esc) main menu").style(Style::default().fg(Color::LightBlue)), + Line::from("(enter) Delete selection").style(Style::default().fg(Color::Magenta)), + Line::from("(esc) Main menu").style(Style::default().fg(Color::LightBlue)), ]) }