From c4feab2f75601b9b9505803ef2b0aee51737ce72 Mon Sep 17 00:00:00 2001 From: Phani Pavan K Date: Thu, 23 Oct 2025 15:57:27 +0530 Subject: [PATCH] added color coding, new state, improved save logic --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/app/mod.rs | 41 ++++++++++++++++++++++++++++++++--------- src/app/settings.rs | 10 +++++++++- src/app/status.rs | 1 + src/main.rs | 11 +++++++---- src/ui/mod.rs | 33 +++++++++++++++++++++++++++++++-- src/ui/textHints.rs | 2 +- 8 files changed, 89 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29db703..9881573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,6 +337,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.7.5" @@ -622,6 +628,7 @@ name = "steckbrett" version = "0.1.0" dependencies = [ "color-eyre", + "md5", "ratatui", "regex", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4aa4a4c..4997127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] color-eyre = "0.6.5" +md5 = "0.8.0" ratatui = "0.29.0" regex = "1.11.2" serde = { version = "1.0.219", features = ["derive"] } diff --git a/src/app/mod.rs b/src/app/mod.rs index a1009fd..6b6840d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,13 +3,12 @@ pub mod entry; mod settings; pub mod status; -use ratatui::widgets::TableState; - use crate::app::{ entry::Entry, settings::Settings, status::{AppStatus, CurrentScreen, EditingField, EntryValError}, }; +use ratatui::widgets::TableState; pub struct AppState { pub fromIP: String, @@ -22,6 +21,9 @@ pub struct AppState { pub confDir: String, pub tableState: TableState, pub appStatus: AppStatus, + pub savedHash: String, + pub currentHash: String, + pub settings: Settings, } impl AppState { @@ -34,13 +36,24 @@ impl AppState { toPort: String::new(), currentlyEditing: None, screen: CurrentScreen::Main, - entries: settings.entries, + savedHash: settings.entryHash(), + currentHash: settings.entryHash(), + entries: settings.entries.clone(), + settings: settings, confDir, tableState: TableState::default().with_selected(0), 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 { match Entry::new( self.fromIP.clone(), @@ -56,12 +69,17 @@ impl AppState { self.toPort = String::new(); self.currentlyEditing = None; self.tableState.select(Some(self.entries.len() - 1_usize)); + self.genCurrentEntriesHash(); EntryValError::None } Err(e) => e, } } + pub fn isHashDifferent(&self) -> bool { + self.currentHash != self.savedHash + } + pub fn nextField(&mut self) { if let Some(currentField) = &self.currentlyEditing { self.currentlyEditing = match currentField { @@ -92,9 +110,9 @@ impl AppState { println!("{resString}"); } - pub fn writeToConfig(&self) { + pub fn saveConfigToScript(&self) { 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() { outputString.push_str(&format!( "socat TCP-LISTEN:{},fork,reuseaddr,bind={} TCP:{}:{} &\n", @@ -105,10 +123,13 @@ impl AppState { let _ = std::fs::write("./forward.sh", outputString); } - pub fn save(&self) { - let mut settings = Settings::new(&self.confDir); - settings.entries = self.entries.clone(); - settings.save(&self.confDir); + pub fn saveConfigToSettingsFile(&mut self) { + // let mut settings = Settings::new(&self.confDir); + // settings.entries = self.entries.clone(); + // 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) { @@ -145,5 +166,7 @@ impl AppState { return; } self.entries.remove(self.tableState.selected().unwrap()); + self.genCurrentEntriesHash(); + self.appStatus = AppStatus::Deleted; } } diff --git a/src/app/settings.rs b/src/app/settings.rs index f359be5..5974ebc 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -2,7 +2,7 @@ use crate::app::entry::Entry; use serde::{Deserialize, Serialize}; use std::fmt::Display; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Settings { pub entries: Vec, } @@ -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) { let payload = serde_json::to_string(self).unwrap(); let _ = std::fs::write(config, payload); diff --git a/src/app/status.rs b/src/app/status.rs index 4e68690..438eee6 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -27,6 +27,7 @@ pub enum AppStatus { Error(EntryValError), Added, Saved, + Deleted, } pub fn entryValError2Field(err: &EntryValError) -> EditingField { diff --git a/src/main.rs b/src/main.rs index c12458d..720aa2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,8 +82,11 @@ fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result< app.screen = CurrentScreen::Exit } KeyCode::Char('s') => { - app.save(); - app.appStatus = AppStatus::Saved; + if app.isHashDifferent() { + app.saveConfigToScript(); + app.saveConfigToSettingsFile(); + app.appStatus = AppStatus::Saved; + } } KeyCode::F(2) => app.screen = CurrentScreen::Settings, KeyCode::Char('d') => app.screen = CurrentScreen::Delete, @@ -181,8 +184,8 @@ fn runApp(app: &mut AppState, terminal: &mut Terminal) -> Result< }, CurrentScreen::Exit => match key.code { KeyCode::Enter => { - app.save(); - app.writeToConfig(); + app.saveConfigToSettingsFile(); + app.saveConfigToScript(); return Ok(true); } KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 3ef4d2d..07c2098 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -77,6 +77,29 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { 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, [ @@ -89,7 +112,11 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { ], ) .header(headers) - .block(Block::default().borders(Borders::all())) + .block( + Block::default() + .borders(Borders::all()) + .style(Style::default().fg(tableBorderColor)), + ) .row_highlight_style( Style::default() .add_modifier(Modifier::REVERSED) @@ -114,7 +141,8 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { ]); let exitPara = Paragraph::new(exitText) .block(exitPopup) - .wrap(Wrap { trim: false }); + .wrap(Wrap { trim: false }) + .style(Style::default().fg(Color::White)); let area = centered_rect(60, 25, titleBodyChunks[1]); frame.render_widget(exitPara, area); @@ -198,6 +226,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) { match &app.appStatus { AppStatus::Welcome => Span::styled("Welcome", Style::default().fg(Color::White)), AppStatus::Added => Span::styled("Added", Style::default().fg(Color::Green)), + AppStatus::Deleted => Span::styled("Deleted", Style::default().fg(Color::Magenta)), AppStatus::Editing => { if let Some(editing) = &app.currentlyEditing { let curEdit = match editing { diff --git a/src/ui/textHints.rs b/src/ui/textHints.rs index 4059469..71c526b 100644 --- a/src/ui/textHints.rs +++ b/src/ui/textHints.rs @@ -3,7 +3,6 @@ pub mod hints { style::{Color, Style}, text::{Line, Text}, }; - pub fn mainHints<'a>() -> Text<'a> { Text::from(vec![ 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("(c) Clear field").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("(esc) Main menu").style(Style::default().fg(Color::LightBlue)), ])