Compare commits
11 Commits
a795ad986b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8353726c77 | |||
|
|
c2d62b59fa | ||
|
|
059415dca2 | ||
|
|
1aea9c4930 | ||
|
|
7b11193b15 | ||
|
|
26202cd7d2 | ||
|
|
8e7143d1a7 | ||
|
|
1b8275c911 | ||
|
|
f83d6038c8 | ||
|
|
9314e97c60 | ||
|
|
c4feab2f75 |
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"] }
|
||||||
|
|||||||
17
Makefile
Normal file
17
Makefile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.PHONY: clean
|
||||||
|
all:run
|
||||||
|
|
||||||
|
run: debugBuild
|
||||||
|
sudo ./target/debug/steckbrett
|
||||||
|
|
||||||
|
debugBuild:
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
rrun: build
|
||||||
|
sudo ./target/release/steckbrett
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build --release
|
||||||
@@ -19,3 +19,8 @@ This project is in very early stage of development. Use it at your own risk.
|
|||||||
Follow [LICENSE](LICENSE).
|
Follow [LICENSE](LICENSE).
|
||||||
|
|
||||||
Also follow [.NOAI](.NOAI).
|
Also follow [.NOAI](.NOAI).
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
~ A Grammer Society Project.
|
||||||
@@ -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,13 +3,15 @@ pub mod entry;
|
|||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub mod status;
|
pub mod status;
|
||||||
use ratatui::widgets::TableState;
|
use std::path::Path;
|
||||||
|
|
||||||
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;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub fromIP: String,
|
pub fromIP: String,
|
||||||
@@ -22,6 +24,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 +39,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,
|
||||||
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 +72,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 {
|
||||||
@@ -84,17 +105,9 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(&self) {
|
pub fn saveConfigToScript(&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 writeToConfig(&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",
|
||||||
@@ -102,13 +115,60 @@ impl AppState {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
outputString.push_str("\nwait");
|
outputString.push_str("\nwait");
|
||||||
let _ = std::fs::write("./forward.sh", outputString);
|
println!("Writing config to system");
|
||||||
|
let _ = std::fs::write("/usr/bin/forward.sh", outputString);
|
||||||
|
Command::new("chmod")
|
||||||
|
.args(["+x", "/usr/bin/forward.sh"])
|
||||||
|
.output()
|
||||||
|
.expect("unable to configure startup script");
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("daemon-reload")
|
||||||
|
.output()
|
||||||
|
.expect("unable to restart service");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&self) {
|
// MUST REMOVE WHEN USED ELSE WHERE
|
||||||
let mut settings = Settings::new(&self.confDir);
|
#[allow(dead_code)]
|
||||||
settings.entries = self.entries.clone();
|
pub fn installFiles(&self) {
|
||||||
settings.save(&self.confDir);
|
self.saveConfigToScript();
|
||||||
|
let mut systemdString: String = String::new();
|
||||||
|
systemdString.push_str(
|
||||||
|
"
|
||||||
|
# DO NOT EDIT THIS FILE MANUALLY
|
||||||
|
# ANY MODIFICATIONS WILL BE OVERWRITTEN WHEN STECKBRETT IS RESET
|
||||||
|
# MODIFY AT YOUR OWN RISK
|
||||||
|
[Unit]
|
||||||
|
Description=Forward port to internal system
|
||||||
|
After=network.target
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
ExecStart=/usr/bin/portForward.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target",
|
||||||
|
);
|
||||||
|
let _ = std::fs::write("/etc/systemd/system/portForward.service", systemdString);
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("daemon-reload")
|
||||||
|
.output()
|
||||||
|
.expect("Unable to reload systemd");
|
||||||
|
}
|
||||||
|
|
||||||
|
// MUST REMOVE WHEN USED ELSE WHERE
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn checkSTBFiles(&self) -> bool {
|
||||||
|
let serviceFile = Path::new("/etc/systemd/system/portForward.service");
|
||||||
|
let forwardScript = Path::new("/usr/bin/forward.sh");
|
||||||
|
serviceFile.exists() || forwardScript.exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn saveConfigToSettingsFile(&mut self) {
|
||||||
|
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 +205,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::app::entry::Entry;
|
use crate::app::entry::Entry;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Display;
|
use std::{fmt::Display, path::Path};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub entries: Vec<Entry>,
|
pub entries: Vec<Entry>,
|
||||||
}
|
}
|
||||||
@@ -15,18 +15,28 @@ impl Display for Settings {
|
|||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn new(config: &String) -> Self {
|
pub fn new(config: &String) -> Self {
|
||||||
let data = std::fs::read_to_string(config);
|
let confPath = Path::new(config);
|
||||||
|
let data = std::fs::read_to_string(confPath);
|
||||||
match data {
|
match data {
|
||||||
Ok(data) => serde_json::from_str::<Settings>(&data).expect("Settings file corrupted"),
|
Ok(data) => serde_json::from_str::<Settings>(&data).expect("Settings file corrupted"),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let newSet = Settings { entries: vec![] };
|
let newSet = Settings { entries: vec![] };
|
||||||
let payload = serde_json::to_string_pretty(&newSet).unwrap();
|
let payload = serde_json::to_string_pretty(&newSet).unwrap();
|
||||||
let _ = std::fs::write(config, payload);
|
let _ = std::fs::create_dir(confPath.parent().unwrap());
|
||||||
|
let _ = std::fs::write(confPath, payload);
|
||||||
newSet
|
newSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pub enum CurrentScreen {
|
|||||||
Settings,
|
Settings,
|
||||||
Delete,
|
Delete,
|
||||||
Exit,
|
Exit,
|
||||||
|
SaveConfirm,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum EditingField {
|
pub enum EditingField {
|
||||||
@@ -27,6 +28,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 {
|
||||||
|
|||||||
51
src/main.rs
51
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!("{home}/.config")))
|
||||||
|
.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()? {
|
||||||
@@ -82,8 +83,9 @@ 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.screen = CurrentScreen::SaveConfirm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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,
|
||||||
@@ -179,11 +181,22 @@ fn runApp<B: Backend>(app: &mut AppState, terminal: &mut Terminal<B>) -> Result<
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
CurrentScreen::SaveConfirm => match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
app.saveConfigToScript();
|
||||||
|
app.saveConfigToSettingsFile();
|
||||||
|
app.appStatus = AppStatus::Saved;
|
||||||
|
terminal.clear()?;
|
||||||
|
app.screen = CurrentScreen::Main;
|
||||||
|
}
|
||||||
|
KeyCode::Esc => app.screen = CurrentScreen::Main,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
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(app.settings.entries.len());
|
||||||
}
|
}
|
||||||
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
KeyCode::Esc | KeyCode::Char('m') => app.screen = CurrentScreen::Main,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
11
src/ui/entryBox.rs
Normal file
11
src/ui/entryBox.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use ratatui::{
|
||||||
|
style::{Color, Style},
|
||||||
|
widgets::{Block, Borders},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn entryBox<'a>(title: &'a str) -> Block<'a> {
|
||||||
|
Block::default()
|
||||||
|
.title(title)
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
}
|
||||||
71
src/ui/entryTable.rs
Normal file
71
src/ui/entryTable.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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: &'a [Entry],
|
||||||
|
curScreen: &CurrentScreen,
|
||||||
|
appStatus: &AppStatus,
|
||||||
|
isHashDifferent: bool,
|
||||||
|
) -> Table<'a> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
}
|
||||||
36
src/ui/exitPrompt.rs
Normal file
36
src/ui/exitPrompt.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use ratatui::{
|
||||||
|
style::{Color, Style},
|
||||||
|
text::{Line, Text},
|
||||||
|
widgets::{Block, Borders, Paragraph, Wrap},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn getExitPara<'a>(isSaved: bool) -> Paragraph<'a> {
|
||||||
|
let exitPopup = Block::default()
|
||||||
|
.title("Exit Window")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().bg(Color::Rgb(42, 61, 69)))
|
||||||
|
.border_style(Style::default().fg(Color::Red));
|
||||||
|
|
||||||
|
let saveDialog = {
|
||||||
|
if isSaved {
|
||||||
|
(
|
||||||
|
Line::from("All Changes Saved"),
|
||||||
|
Line::from("It is safe to exit."),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
Line::from("ANY UNSAVED CHANGES WILL BE DISCARDED."),
|
||||||
|
Line::from("Press (s) to save from main screen."),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let exitText = Text::from(vec![
|
||||||
|
Line::from("Exit the app?"),
|
||||||
|
saveDialog.0,
|
||||||
|
saveDialog.1,
|
||||||
|
]);
|
||||||
|
Paragraph::new(exitText)
|
||||||
|
.block(exitPopup)
|
||||||
|
.wrap(Wrap { trim: false })
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
}
|
||||||
73
src/ui/header.rs
Normal file
73
src/ui/header.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use ratatui::{
|
||||||
|
style::{Color, Style},
|
||||||
|
text::Span,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::status::{AppStatus, CurrentScreen, EditingField, EntryValError};
|
||||||
|
|
||||||
|
pub fn getHeaderScreen<'a>(scr: &'a CurrentScreen) -> (Color, Span<'a>) {
|
||||||
|
match scr {
|
||||||
|
CurrentScreen::Main => (
|
||||||
|
Color::LightBlue,
|
||||||
|
Span::styled("Main Screen", Style::default().fg(Color::LightBlue)),
|
||||||
|
),
|
||||||
|
CurrentScreen::Add => (
|
||||||
|
Color::LightGreen,
|
||||||
|
Span::styled("Add Window", Style::default().fg(Color::Green)),
|
||||||
|
),
|
||||||
|
CurrentScreen::Exit => (
|
||||||
|
Color::LightRed,
|
||||||
|
Span::styled("Exit Screen", Style::default().fg(Color::Red)),
|
||||||
|
),
|
||||||
|
CurrentScreen::Settings => (
|
||||||
|
Color::LightBlue,
|
||||||
|
Span::styled("Settings", Style::default().fg(Color::Blue)),
|
||||||
|
),
|
||||||
|
CurrentScreen::Delete => (
|
||||||
|
Color::Magenta,
|
||||||
|
Span::styled("Delete", Style::default().fg(Color::Magenta)),
|
||||||
|
),
|
||||||
|
CurrentScreen::SaveConfirm => (
|
||||||
|
Color::Yellow,
|
||||||
|
Span::styled("Confirmation", Style::default().fg(Color::Yellow)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeaderStatus<'a>(
|
||||||
|
status: &'a AppStatus,
|
||||||
|
editingField: &'a Option<EditingField>,
|
||||||
|
) -> Span<'a> {
|
||||||
|
match status {
|
||||||
|
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) = editingField {
|
||||||
|
let curEdit = match editing {
|
||||||
|
EditingField::FromIP => "From IP",
|
||||||
|
EditingField::ToIP => "To IP",
|
||||||
|
EditingField::FromPort => "From Port",
|
||||||
|
EditingField::ToPort => "To Port",
|
||||||
|
};
|
||||||
|
Span::styled(
|
||||||
|
format!("Editing: {curEdit}"),
|
||||||
|
Style::default().fg(Color::Green),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Span::styled("Not Editing", Style::default().fg(Color::White))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppStatus::Error(e) => {
|
||||||
|
let errString = match e {
|
||||||
|
EntryValError::ToPortValError => "To Port Invalid",
|
||||||
|
EntryValError::FromPortValError => "From Port Invalid",
|
||||||
|
EntryValError::ToIPValError => "To IP Invalid",
|
||||||
|
EntryValError::FromIPValError => "From IP Invalid",
|
||||||
|
EntryValError::None => "",
|
||||||
|
};
|
||||||
|
Span::styled(errString, Style::default().fg(Color::Red))
|
||||||
|
}
|
||||||
|
AppStatus::Saved => Span::styled("Saved", Style::default().fg(Color::Green)),
|
||||||
|
}
|
||||||
|
}
|
||||||
179
src/ui/mod.rs
179
src/ui/mod.rs
@@ -1,21 +1,26 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
mod centeredRect;
|
mod centeredRect;
|
||||||
|
mod entryBox;
|
||||||
|
mod entryTable;
|
||||||
|
mod exitPrompt;
|
||||||
|
mod header;
|
||||||
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, Clear, Paragraph},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::status::{AppStatus, CurrentScreen, EntryValError};
|
use crate::app::status::{AppStatus, CurrentScreen};
|
||||||
use crate::app::{AppState, status::EditingField};
|
use crate::app::{AppState, status::EditingField};
|
||||||
use crate::ui::centeredRect::centered_rect;
|
use crate::ui::centeredRect::centered_rect;
|
||||||
use crate::ui::textHints::hints;
|
use crate::ui::textHints::hints;
|
||||||
|
|
||||||
pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
||||||
|
// -------------------------------------------
|
||||||
// Split entire body into left title+body and right screen+keybinds chunks
|
// Split entire body into left title+body and right screen+keybinds chunks
|
||||||
let screenChunks = Layout::default()
|
let screenChunks = Layout::default()
|
||||||
.direction(ratatui::layout::Direction::Horizontal)
|
.direction(ratatui::layout::Direction::Horizontal)
|
||||||
@@ -34,7 +39,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
.constraints([Constraint::Length(3), Constraint::Min(3)])
|
.constraints([Constraint::Length(3), Constraint::Min(3)])
|
||||||
.split(screenChunks[1]);
|
.split(screenChunks[1]);
|
||||||
|
|
||||||
// ------------------------
|
// -------------------------------------------
|
||||||
// Create and add title block
|
// Create and add title block
|
||||||
let titleBlock = Block::default()
|
let titleBlock = Block::default()
|
||||||
.borders(Borders::all())
|
.borders(Borders::all())
|
||||||
@@ -48,103 +53,57 @@ 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 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()))
|
|
||||||
.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()
|
|
||||||
.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 });
|
|
||||||
|
|
||||||
let area = centered_rect(60, 25, titleBodyChunks[1]);
|
let area = centered_rect(60, 25, titleBodyChunks[1]);
|
||||||
frame.render_widget(exitPara, area);
|
frame.render_widget(Clear, area);
|
||||||
|
frame.render_widget(
|
||||||
|
exitPrompt::getExitPara(matches!(
|
||||||
|
app.appStatus,
|
||||||
|
AppStatus::Saved | AppStatus::Welcome
|
||||||
|
)),
|
||||||
|
area,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------
|
||||||
// Editing screen popup
|
// Editing screen popup
|
||||||
if let Some(edit) = &app.currentlyEditing {
|
if let Some(edit) = &app.currentlyEditing {
|
||||||
let popup = Block::default()
|
let popup = Block::default()
|
||||||
.title("Add an entry")
|
.title("Add an entry")
|
||||||
.borders(Borders::NONE)
|
.borders(Borders::all())
|
||||||
.style(Style::default().bg(Color::DarkGray));
|
.style(Style::default().bg(Color::Rgb(42, 61, 69)).fg(Color::Green));
|
||||||
let area = centered_rect(75, 20, titleBodyChunks[1]);
|
let area = centered_rect(75, 30, titleBodyChunks[1]);
|
||||||
|
frame.render_widget(Clear, area);
|
||||||
frame.render_widget(popup, area);
|
frame.render_widget(popup, area);
|
||||||
|
|
||||||
let fields = Layout::default()
|
let fields = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Percentage(25),
|
Constraint::Percentage(30),
|
||||||
Constraint::Percentage(25),
|
Constraint::Percentage(20),
|
||||||
Constraint::Percentage(25),
|
Constraint::Percentage(30),
|
||||||
Constraint::Percentage(25),
|
Constraint::Percentage(20),
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
let mut fromIPBlock = Block::default().title("From IP").borders(Borders::ALL);
|
let mut fromIPBlock = entryBox::entryBox("From IP");
|
||||||
let mut toIPBlock = Block::default().title("To IP").borders(Borders::ALL);
|
let mut toIPBlock = entryBox::entryBox("To IP");
|
||||||
let mut fromPortBlock = Block::default().title("From Port").borders(Borders::ALL);
|
let mut fromPortBlock = entryBox::entryBox("From Port");
|
||||||
let mut toPortBlock = Block::default().title("To Port").borders(Borders::ALL);
|
let mut toPortBlock = entryBox::entryBox("To Port");
|
||||||
|
|
||||||
let activeStyle = Style::default().bg(Color::LightYellow).fg(Color::Black);
|
let activeStyle = Style::default().bg(Color::LightYellow).fg(Color::Black);
|
||||||
|
|
||||||
match edit {
|
match edit {
|
||||||
@@ -170,63 +129,13 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
let mut borderColor = Color::White;
|
let mut borderColor = Color::White;
|
||||||
let _ = borderColor; // to suppress warning, remove later
|
let _ = borderColor; // to suppress warning, remove later
|
||||||
let screenHeader = vec![
|
let screenHeader = vec![
|
||||||
match app.screen {
|
|
||||||
CurrentScreen::Main => {
|
|
||||||
borderColor = Color::LightBlue;
|
|
||||||
Span::styled("Main Screen", Style::default().fg(Color::LightBlue))
|
|
||||||
}
|
|
||||||
CurrentScreen::Add => {
|
|
||||||
borderColor = Color::LightGreen;
|
|
||||||
Span::styled("Add Window", Style::default().fg(Color::Green))
|
|
||||||
}
|
|
||||||
CurrentScreen::Exit => {
|
|
||||||
borderColor = Color::LightRed;
|
|
||||||
Span::styled("Exit Screen", Style::default().fg(Color::Red))
|
|
||||||
}
|
|
||||||
CurrentScreen::Settings => {
|
|
||||||
borderColor = Color::LightBlue;
|
|
||||||
Span::styled("Settings", Style::default().fg(Color::Blue))
|
|
||||||
}
|
|
||||||
CurrentScreen::Delete => {
|
|
||||||
borderColor = Color::Magenta;
|
|
||||||
Span::styled("Delete", Style::default().fg(Color::Magenta))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.to_owned(),
|
|
||||||
Span::styled(" | ", Style::default().fg(Color::White)),
|
|
||||||
{
|
{
|
||||||
match &app.appStatus {
|
let (color, span) = header::getHeaderScreen(&app.screen);
|
||||||
AppStatus::Welcome => Span::styled("Welcome", Style::default().fg(Color::White)),
|
borderColor = color;
|
||||||
AppStatus::Added => Span::styled("Added", Style::default().fg(Color::Green)),
|
span
|
||||||
AppStatus::Editing => {
|
|
||||||
if let Some(editing) = &app.currentlyEditing {
|
|
||||||
let curEdit = match editing {
|
|
||||||
EditingField::FromIP => "From IP",
|
|
||||||
EditingField::ToIP => "To IP",
|
|
||||||
EditingField::FromPort => "From Port",
|
|
||||||
EditingField::ToPort => "To Port",
|
|
||||||
};
|
|
||||||
Span::styled(
|
|
||||||
format!("Editing: {curEdit}"),
|
|
||||||
Style::default().fg(Color::Green),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Span::styled("Not Editing", Style::default().fg(Color::White))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppStatus::Error(e) => {
|
|
||||||
let errString = match e {
|
|
||||||
EntryValError::ToPortValError => "To Port Invalid",
|
|
||||||
EntryValError::FromPortValError => "From Port Invalid",
|
|
||||||
EntryValError::ToIPValError => "To IP Invalid",
|
|
||||||
EntryValError::FromIPValError => "From IP Invalid",
|
|
||||||
EntryValError::None => "",
|
|
||||||
};
|
|
||||||
Span::styled(errString, Style::default().fg(Color::Red))
|
|
||||||
}
|
|
||||||
AppStatus::Saved => Span::styled("Saved", Style::default()),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
Span::styled(" | ", Style::default().fg(Color::White)),
|
||||||
|
header::getHeaderStatus(&app.appStatus, &app.currentlyEditing),
|
||||||
];
|
];
|
||||||
|
|
||||||
let screenHeaderPara = Paragraph::new(Line::from(screenHeader)).block(
|
let screenHeaderPara = Paragraph::new(Line::from(screenHeader)).block(
|
||||||
@@ -234,7 +143,9 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
|
.borders(Borders::TOP | Borders::LEFT | Borders::RIGHT)
|
||||||
.style(Style::default().fg(borderColor)),
|
.style(Style::default().fg(borderColor)),
|
||||||
);
|
);
|
||||||
|
frame.render_widget(screenHeaderPara, headerKeybindChunks[0]);
|
||||||
|
|
||||||
|
// -------------------------------------------
|
||||||
// Keybinds Entry
|
// Keybinds Entry
|
||||||
let keybinds = {
|
let keybinds = {
|
||||||
match app.screen {
|
match app.screen {
|
||||||
@@ -243,6 +154,7 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
CurrentScreen::Settings => hints::settingsHints(),
|
CurrentScreen::Settings => hints::settingsHints(),
|
||||||
CurrentScreen::Delete => hints::delHints(),
|
CurrentScreen::Delete => hints::delHints(),
|
||||||
CurrentScreen::Exit => hints::exitHints(),
|
CurrentScreen::Exit => hints::exitHints(),
|
||||||
|
CurrentScreen::SaveConfirm => hints::saveConfirmationHints(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -252,6 +164,5 @@ pub fn ui(frame: &mut Frame, app: &mut AppState) {
|
|||||||
.style(Style::default().fg(borderColor)),
|
.style(Style::default().fg(borderColor)),
|
||||||
);
|
);
|
||||||
|
|
||||||
frame.render_widget(screenHeaderPara, headerKeybindChunks[0]);
|
|
||||||
frame.render_widget(keyBindFooter, headerKeybindChunks[1]);
|
frame.render_widget(keyBindFooter, headerKeybindChunks[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)),
|
||||||
])
|
])
|
||||||
@@ -27,20 +27,27 @@ 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)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settingsHints<'a>() -> Text<'a> {
|
pub fn settingsHints<'a>() -> Text<'a> {
|
||||||
Text::from(Line::from("").style(Style::default().fg(Color::Red)))
|
Text::from(Line::from("").style(Style::default().fg(Color::Red)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn saveConfirmationHints<'a>() -> Text<'a> {
|
||||||
|
Text::from(vec![
|
||||||
|
Line::from("(entr) Save and reload service").style(Style::default().fg(Color::Yellow)),
|
||||||
|
Line::from("(esc) Cancel").style(Style::default().fg(Color::LightRed)),
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user