added color coding, new state, improved save logic
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -337,6 +337,12 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
@@ -622,6 +628,7 @@ name = "steckbrett"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
|
"md5",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
|
md5 = "0.8.0"
|
||||||
ratatui = "0.29.0"
|
ratatui = "0.29.0"
|
||||||
regex = "1.11.2"
|
regex = "1.11.2"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ pub mod entry;
|
|||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub mod status;
|
pub mod status;
|
||||||
use ratatui::widgets::TableState;
|
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
entry::Entry,
|
entry::Entry,
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
status::{AppStatus, CurrentScreen, EditingField, EntryValError},
|
status::{AppStatus, CurrentScreen, EditingField, EntryValError},
|
||||||
};
|
};
|
||||||
|
use ratatui::widgets::TableState;
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub fromIP: String,
|
pub fromIP: String,
|
||||||
@@ -22,6 +21,9 @@ pub struct AppState {
|
|||||||
pub confDir: String,
|
pub confDir: String,
|
||||||
pub tableState: TableState,
|
pub tableState: TableState,
|
||||||
pub appStatus: AppStatus,
|
pub appStatus: AppStatus,
|
||||||
|
pub savedHash: String,
|
||||||
|
pub currentHash: String,
|
||||||
|
pub settings: Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -34,13 +36,24 @@ impl AppState {
|
|||||||
toPort: String::new(),
|
toPort: String::new(),
|
||||||
currentlyEditing: None,
|
currentlyEditing: None,
|
||||||
screen: CurrentScreen::Main,
|
screen: CurrentScreen::Main,
|
||||||
entries: settings.entries,
|
savedHash: settings.entryHash(),
|
||||||
|
currentHash: settings.entryHash(),
|
||||||
|
entries: settings.entries.clone(),
|
||||||
|
settings: settings,
|
||||||
confDir,
|
confDir,
|
||||||
tableState: TableState::default().with_selected(0),
|
tableState: TableState::default().with_selected(0),
|
||||||
appStatus: AppStatus::Welcome,
|
appStatus: AppStatus::Welcome,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn genCurrentEntriesHash(&mut self) {
|
||||||
|
let mut entries = String::new();
|
||||||
|
for e in &self.entries {
|
||||||
|
entries.push_str(&format!("{}", e));
|
||||||
|
}
|
||||||
|
self.currentHash = format!("{:x}", md5::compute(entries));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn store(&mut self) -> EntryValError {
|
pub fn store(&mut self) -> EntryValError {
|
||||||
match Entry::new(
|
match Entry::new(
|
||||||
self.fromIP.clone(),
|
self.fromIP.clone(),
|
||||||
@@ -56,12 +69,17 @@ impl AppState {
|
|||||||
self.toPort = String::new();
|
self.toPort = String::new();
|
||||||
self.currentlyEditing = None;
|
self.currentlyEditing = None;
|
||||||
self.tableState.select(Some(self.entries.len() - 1_usize));
|
self.tableState.select(Some(self.entries.len() - 1_usize));
|
||||||
|
self.genCurrentEntriesHash();
|
||||||
EntryValError::None
|
EntryValError::None
|
||||||
}
|
}
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isHashDifferent(&self) -> bool {
|
||||||
|
self.currentHash != self.savedHash
|
||||||
|
}
|
||||||
|
|
||||||
pub fn nextField(&mut self) {
|
pub fn nextField(&mut self) {
|
||||||
if let Some(currentField) = &self.currentlyEditing {
|
if let Some(currentField) = &self.currentlyEditing {
|
||||||
self.currentlyEditing = match currentField {
|
self.currentlyEditing = match currentField {
|
||||||
@@ -92,9 +110,9 @@ impl AppState {
|
|||||||
println!("{resString}");
|
println!("{resString}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn writeToConfig(&self) {
|
pub fn saveConfigToScript(&self) {
|
||||||
let mut outputString: String = String::new();
|
let mut outputString: String = String::new();
|
||||||
outputString.push_str("#! /bin/bash\n\n");
|
outputString.push_str("#! /bin/bash\n\n# DO NOT EDIT THIS FILE MANUALLY\n# ANY MODIFICATIONS WILL BE OVERWRITTEN BY STECKBRETT\n# USE THAT STECKBRETT (stb) TO CONFIGURE PORT MAPPINGS.\n\n");
|
||||||
for ent in self.entries.iter() {
|
for ent in self.entries.iter() {
|
||||||
outputString.push_str(&format!(
|
outputString.push_str(&format!(
|
||||||
"socat TCP-LISTEN:{},fork,reuseaddr,bind={} TCP:{}:{} &\n",
|
"socat TCP-LISTEN:{},fork,reuseaddr,bind={} TCP:{}:{} &\n",
|
||||||
@@ -105,10 +123,13 @@ impl AppState {
|
|||||||
let _ = std::fs::write("./forward.sh", outputString);
|
let _ = std::fs::write("./forward.sh", outputString);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) {
|
pub fn saveConfigToSettingsFile(&mut self) {
|
||||||
let mut settings = Settings::new(&self.confDir);
|
// let mut settings = Settings::new(&self.confDir);
|
||||||
settings.entries = self.entries.clone();
|
// settings.entries = self.entries.clone();
|
||||||
settings.save(&self.confDir);
|
// settings.save(&self.confDir);
|
||||||
|
self.settings.entries = self.entries.clone();
|
||||||
|
self.settings.save(&self.confDir);
|
||||||
|
self.savedHash = self.settings.entryHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextRow(&mut self) {
|
pub fn nextRow(&mut self) {
|
||||||
@@ -145,5 +166,7 @@ impl AppState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.entries.remove(self.tableState.selected().unwrap());
|
self.entries.remove(self.tableState.selected().unwrap());
|
||||||
|
self.genCurrentEntriesHash();
|
||||||
|
self.appStatus = AppStatus::Deleted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::app::entry::Entry;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub entries: Vec<Entry>,
|
pub entries: Vec<Entry>,
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,14 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn entryHash(&self) -> String {
|
||||||
|
let mut entries = String::new();
|
||||||
|
for e in &self.entries {
|
||||||
|
entries.push_str(&format!("{}", e));
|
||||||
|
}
|
||||||
|
format!("{:x}", md5::compute(entries))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save(&self, config: &String) {
|
pub fn save(&self, config: &String) {
|
||||||
let payload = serde_json::to_string(self).unwrap();
|
let payload = serde_json::to_string(self).unwrap();
|
||||||
let _ = std::fs::write(config, payload);
|
let _ = std::fs::write(config, payload);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub enum AppStatus {
|
|||||||
Error(EntryValError),
|
Error(EntryValError),
|
||||||
Added,
|
Added,
|
||||||
Saved,
|
Saved,
|
||||||
|
Deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entryValError2Field(err: &EntryValError) -> EditingField {
|
pub fn entryValError2Field(err: &EntryValError) -> EditingField {
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@@ -82,8 +82,11 @@ fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<
|
|||||||
app.screen = CurrentScreen::Exit
|
app.screen = CurrentScreen::Exit
|
||||||
}
|
}
|
||||||
KeyCode::Char('s') => {
|
KeyCode::Char('s') => {
|
||||||
app.save();
|
if app.isHashDifferent() {
|
||||||
app.appStatus = AppStatus::Saved;
|
app.saveConfigToScript();
|
||||||
|
app.saveConfigToSettingsFile();
|
||||||
|
app.appStatus = AppStatus::Saved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
KeyCode::F(2) => app.screen = CurrentScreen::Settings,
|
KeyCode::F(2) => app.screen = CurrentScreen::Settings,
|
||||||
KeyCode::Char('d') => app.screen = CurrentScreen::Delete,
|
KeyCode::Char('d') => app.screen = CurrentScreen::Delete,
|
||||||
@@ -181,8 +184,8 @@ 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.save();
|
app.saveConfigToSettingsFile();
|
||||||
app.writeToConfig();
|
app.saveConfigToScript();
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
||||||
|
|||||||
@@ -77,6 +77,29 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
CurrentScreen::Delete => Color::Yellow,
|
CurrentScreen::Delete => Color::Yellow,
|
||||||
_ => Color::DarkGray,
|
_ => 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(
|
let table = Table::new(
|
||||||
dataRows,
|
dataRows,
|
||||||
[
|
[
|
||||||
@@ -89,7 +112,11 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
.header(headers)
|
.header(headers)
|
||||||
.block(Block::default().borders(Borders::all()))
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::all())
|
||||||
|
.style(Style::default().fg(tableBorderColor)),
|
||||||
|
)
|
||||||
.row_highlight_style(
|
.row_highlight_style(
|
||||||
Style::default()
|
Style::default()
|
||||||
.add_modifier(Modifier::REVERSED)
|
.add_modifier(Modifier::REVERSED)
|
||||||
@@ -114,7 +141,8 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
]);
|
]);
|
||||||
let exitPara = Paragraph::new(exitText)
|
let exitPara = Paragraph::new(exitText)
|
||||||
.block(exitPopup)
|
.block(exitPopup)
|
||||||
.wrap(Wrap { trim: false });
|
.wrap(Wrap { trim: false })
|
||||||
|
.style(Style::default().fg(Color::White));
|
||||||
|
|
||||||
let area = centered_rect(60, 25, titleBodyChunks[1]);
|
let area = centered_rect(60, 25, titleBodyChunks[1]);
|
||||||
frame.render_widget(exitPara, area);
|
frame.render_widget(exitPara, area);
|
||||||
@@ -198,6 +226,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
match &app.appStatus {
|
match &app.appStatus {
|
||||||
AppStatus::Welcome => Span::styled("Welcome", Style::default().fg(Color::White)),
|
AppStatus::Welcome => Span::styled("Welcome", Style::default().fg(Color::White)),
|
||||||
AppStatus::Added => Span::styled("Added", Style::default().fg(Color::Green)),
|
AppStatus::Added => Span::styled("Added", Style::default().fg(Color::Green)),
|
||||||
|
AppStatus::Deleted => Span::styled("Deleted", Style::default().fg(Color::Magenta)),
|
||||||
AppStatus::Editing => {
|
AppStatus::Editing => {
|
||||||
if let Some(editing) = &app.currentlyEditing {
|
if let Some(editing) = &app.currentlyEditing {
|
||||||
let curEdit = match editing {
|
let curEdit = match editing {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ pub mod hints {
|
|||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
text::{Line, Text},
|
text::{Line, Text},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn mainHints<'a>() -> Text<'a> {
|
pub fn mainHints<'a>() -> Text<'a> {
|
||||||
Text::from(vec![
|
Text::from(vec![
|
||||||
Line::from("(a) Add entry").style(Style::default().fg(Color::Green)),
|
Line::from("(a) Add entry").style(Style::default().fg(Color::Green)),
|
||||||
@@ -20,6 +19,7 @@ pub mod hints {
|
|||||||
Line::from("(o) 0.0.0.0").style(Style::default().fg(Color::White)),
|
Line::from("(o) 0.0.0.0").style(Style::default().fg(Color::White)),
|
||||||
Line::from("(c) Clear field").style(Style::default().fg(Color::White)),
|
Line::from("(c) Clear field").style(Style::default().fg(Color::White)),
|
||||||
Line::from("(C) Clear all fields").style(Style::default().fg(Color::White)),
|
Line::from("(C) Clear all fields").style(Style::default().fg(Color::White)),
|
||||||
|
Line::from("(←/→) Prev/Next field").style(Style::default().fg(Color::White)),
|
||||||
Line::from("(enter) Next field/Save"),
|
Line::from("(enter) Next field/Save"),
|
||||||
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