handle * ip, ui refac, fix #1 to handle .conf
This commit is contained in:
@@ -29,7 +29,14 @@ impl Entry {
|
|||||||
toPort: String,
|
toPort: String,
|
||||||
) -> Result<Self, EntryValError> {
|
) -> Result<Self, EntryValError> {
|
||||||
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();
|
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)
|
Err(EntryValError::FromIPValError)
|
||||||
} else if !fromPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535) {
|
} else if !fromPort.parse::<i32>().is_ok_and(|a| a > 1 && a < 65535) {
|
||||||
Err(EntryValError::FromPortValError)
|
Err(EntryValError::FromPortValError)
|
||||||
@@ -39,7 +46,7 @@ impl Entry {
|
|||||||
Err(EntryValError::ToPortValError)
|
Err(EntryValError::ToPortValError)
|
||||||
} else {
|
} else {
|
||||||
Ok(Entry {
|
Ok(Entry {
|
||||||
fromIP,
|
fromIP: newFromIP,
|
||||||
toIP,
|
toIP,
|
||||||
fromPort: fromPort.trim_start_matches('0').to_string(),
|
fromPort: fromPort.trim_start_matches('0').to_string(),
|
||||||
toPort: toPort.trim_start_matches('0').to_string(),
|
toPort: toPort.trim_start_matches('0').to_string(),
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ pub mod entry;
|
|||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
use std::{thread::sleep, time::Duration};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
entry::Entry,
|
entry::Entry,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
@@ -102,13 +104,13 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(&self) {
|
// pub fn print(&self) {
|
||||||
let tempEntry = &self.entries[0];
|
// let tempEntry = &self.entries[0];
|
||||||
let res = serde_json::to_string(tempEntry).unwrap();
|
// let res = serde_json::to_string(tempEntry).unwrap();
|
||||||
println!("{res}");
|
// println!("{res}");
|
||||||
let resString = tempEntry.to_string();
|
// let resString = tempEntry.to_string();
|
||||||
println!("{resString}");
|
// println!("{resString}");
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn saveConfigToScript(&self) {
|
pub fn saveConfigToScript(&self) {
|
||||||
let mut outputString: String = String::new();
|
let mut outputString: String = String::new();
|
||||||
@@ -120,6 +122,12 @@ impl AppState {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
outputString.push_str("\nwait");
|
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);
|
let _ = std::fs::write("./forward.sh", outputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
src/main.rs
36
src/main.rs
@@ -2,6 +2,9 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
|
use crate::app::status::{
|
||||||
|
AppStatus, CurrentScreen, EditingField, EntryValError, entryValError2Field,
|
||||||
|
};
|
||||||
use crate::ui::ui;
|
use crate::ui::ui;
|
||||||
use app::AppState;
|
use app::AppState;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
@@ -14,12 +17,9 @@ use ratatui::crossterm::terminal::{
|
|||||||
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
|
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
|
||||||
};
|
};
|
||||||
use ratatui::prelude::{Backend, CrosstermBackend};
|
use ratatui::prelude::{Backend, CrosstermBackend};
|
||||||
|
use std::env::var;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::app::status::{
|
|
||||||
AppStatus, CurrentScreen, EditingField, EntryValError, entryValError2Field,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stderr = io::stderr(); // This is a special case. Normally using stdout is fine
|
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 backend = CrosstermBackend::new(stderr);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
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
|
// 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();
|
// app.load();
|
||||||
let res = runApp(&mut app, &mut terminal);
|
let _ = runApp(&mut appSettings, &mut terminal);
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
execute!(
|
execute!(
|
||||||
terminal.backend_mut(),
|
terminal.backend_mut(),
|
||||||
@@ -39,17 +42,15 @@ fn main() -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
terminal.show_cursor()?;
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
if let Ok(do_print) = res {
|
// match res {
|
||||||
if do_print {
|
// Ok(ent) => println!("{ent} entries "),
|
||||||
app.print();
|
// _ => {}
|
||||||
}
|
// }
|
||||||
} else if let Err(err) = res {
|
|
||||||
println!("{err:?}");
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<bool> {
|
fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<usize> {
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|f| ui(f, app))?; // from ui.rs
|
terminal.draw(|f| ui(f, app))?; // from ui.rs
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
@@ -86,6 +87,7 @@ fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<
|
|||||||
app.saveConfigToScript();
|
app.saveConfigToScript();
|
||||||
app.saveConfigToSettingsFile();
|
app.saveConfigToSettingsFile();
|
||||||
app.appStatus = AppStatus::Saved;
|
app.appStatus = AppStatus::Saved;
|
||||||
|
terminal.clear()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::F(2) => app.screen = CurrentScreen::Settings,
|
KeyCode::F(2) => app.screen = CurrentScreen::Settings,
|
||||||
@@ -184,9 +186,9 @@ fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<
|
|||||||
},
|
},
|
||||||
CurrentScreen::Exit => match key.code {
|
CurrentScreen::Exit => match key.code {
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
app.saveConfigToSettingsFile();
|
// app.saveConfigToSettingsFile();
|
||||||
app.saveConfigToScript();
|
// app.saveConfigToScript();
|
||||||
return Ok(true);
|
return Ok(app.settings.entries.len());
|
||||||
}
|
}
|
||||||
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
72
src/ui/entryTable.rs
Normal file
72
src/ui/entryTable.rs
Normal file
@@ -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<Entry>,
|
||||||
|
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::<Row>()
|
||||||
|
.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::<Row>()
|
||||||
|
.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
|
||||||
|
}
|
||||||
25
src/ui/exitPrompt.rs
Normal file
25
src/ui/exitPrompt.rs
Normal file
@@ -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
|
||||||
|
}
|
||||||
108
src/ui/mod.rs
108
src/ui/mod.rs
@@ -1,13 +1,15 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
mod centeredRect;
|
mod centeredRect;
|
||||||
|
mod entryTable;
|
||||||
|
mod exitPrompt;
|
||||||
mod textHints;
|
mod textHints;
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
Frame,
|
Frame,
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Style},
|
||||||
text::{Line, Span, Text},
|
text::{Line, Span, Text},
|
||||||
widgets::{Block, Borders, Cell, Paragraph, Row, Table, Wrap},
|
widgets::{Block, Borders, Paragraph},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::status::{AppStatus, CurrentScreen, EntryValError};
|
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]);
|
frame.render_widget(title, titleBodyChunks[0]);
|
||||||
|
|
||||||
// Create and add body
|
|
||||||
// let mut currentItems = Vec::<ListItem>::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
|
// table drawing
|
||||||
let headers = ["No.", "From IP", "From Port", "-->", "To IP", "To Port"]
|
let table = entryTable::getTableElement(
|
||||||
.into_iter()
|
&app.entries,
|
||||||
.map(Cell::from)
|
&app.screen,
|
||||||
.collect::<Row>()
|
&app.appStatus,
|
||||||
.style(Style::default().fg(Color::White).bg(Color::Black))
|
app.isHashDifferent(),
|
||||||
.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::<Row>()
|
|
||||||
.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 list = List::new(currentItems).block(Block::default().borders(Borders::all()));
|
|
||||||
frame.render_stateful_widget(table, titleBodyChunks[1], &mut app.tableState);
|
frame.render_stateful_widget(table, titleBodyChunks[1], &mut app.tableState);
|
||||||
|
|
||||||
// Renter exit prompt
|
// Renter exit prompt
|
||||||
if let CurrentScreen::Exit = app.screen {
|
if let CurrentScreen::Exit = app.screen {
|
||||||
let exitPopup = Block::default()
|
frame.render_widget(
|
||||||
.title("Exit Window")
|
exitPrompt::getExitPara(),
|
||||||
.borders(Borders::ALL)
|
centered_rect(60, 25, titleBodyChunks[1]),
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editing screen popup
|
// Editing screen popup
|
||||||
@@ -154,7 +74,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
.title("Add an entry")
|
.title("Add an entry")
|
||||||
.borders(Borders::NONE)
|
.borders(Borders::NONE)
|
||||||
.style(Style::default().bg(Color::DarkGray));
|
.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);
|
frame.render_widget(popup, area);
|
||||||
|
|
||||||
let fields = Layout::default()
|
let fields = Layout::default()
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ pub mod hints {
|
|||||||
|
|
||||||
pub fn exitHints<'a>() -> Text<'a> {
|
pub fn exitHints<'a>() -> Text<'a> {
|
||||||
Text::from(vec![
|
Text::from(vec![
|
||||||
Line::from("(enter) save and exit").style(Style::default().fg(Color::Red)),
|
Line::from("(enter) Exit").style(Style::default().fg(Color::Red)),
|
||||||
Line::from("(esc) main menu").style(Style::default().fg(Color::LightBlue)),
|
Line::from("(esc) Main menu").style(Style::default().fg(Color::LightBlue)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delHints<'a>() -> Text<'a> {
|
pub fn delHints<'a>() -> Text<'a> {
|
||||||
Text::from(vec![
|
Text::from(vec![
|
||||||
Line::from("(↑/↓) Scroll").style(Style::default().fg(Color::White)),
|
Line::from("(↑/↓) Scroll").style(Style::default().fg(Color::White)),
|
||||||
Line::from("(enter) delete selection").style(Style::default().fg(Color::Magenta)),
|
Line::from("(enter) Delete selection").style(Style::default().fg(Color::Magenta)),
|
||||||
Line::from("(esc) main menu").style(Style::default().fg(Color::LightBlue)),
|
Line::from("(esc) Main menu").style(Style::default().fg(Color::LightBlue)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user