added color coding, new state, improved save logic
Some checks failed
/ Quality Check (push) Failing after 1m33s
/ Build (push) Successful in 1m14s

This commit is contained in:
Phani Pavan K
2025-10-23 15:57:27 +05:30
parent a795ad986b
commit c4feab2f75
8 changed files with 89 additions and 17 deletions

7
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

View File

@@ -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;
} }
} }

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)),
]) ])