Logging & Password

This commit is contained in:
Alex Emmet 2025-11-30 00:11:05 +01:00
commit 6117cbb8d7
13 changed files with 595 additions and 54 deletions

View file

@ -142,7 +142,7 @@ pub async fn migrate_user(user_profile: &mut UserProfile) -> bool {
let client = client();
let mut payload = JsonValue::new_object();
payload["iota_id"] = JsonValue::String(CONFIG.lock().await.get_iota_id().to_string());
payload["iota_id"] = JsonValue::String(CONFIG.read().await.get_iota_id().to_string());
payload["reset_token"] = user_profile.reset_token.clone().into();
payload["new_token"] = user_profile.randomize_reset_token().into();

64
src/gui/input_handler.rs Normal file
View file

@ -0,0 +1,64 @@
use crate::{SHUTDOWN, gui::tui::UNIQUE, util::config_util::CONFIG};
use crossterm::event::{KeyEvent, KeyModifiers};
use ratatui::crossterm::event::{Event, KeyCode, read};
use tokio::{self};
use tokio_util::sync::WaitForCancellationFutureOwned;
pub fn setup_input_handler() {
tokio::spawn(async move {
while let Ok(event) = read() {
if *SHUTDOWN.read().await {
break;
}
if let Event::Key(key) = event {
handle_input(key).await;
}
}
});
}
pub async fn handle_input(key: KeyEvent) {
match (key.code, key.modifiers) {
(KeyCode::Char('c'), KeyModifiers::CONTROL) => {
*SHUTDOWN.write().await = true;
}
(KeyCode::Backspace, KeyModifiers::NONE) => {
let password: String = {
let cfg = CONFIG.read().await;
cfg.get("password").as_str().unwrap_or("").to_string()
};
let password: &str = &password;
let password = match password.char_indices().next_back() {
Some((i, _)) => &password[..i],
None => password,
};
CONFIG.write().await.change("password", password);
CONFIG.write().await.update();
*UNIQUE.write().await = true;
}
(KeyCode::Char(c), KeyModifiers::NONE) => {
let password: String = {
let cfg = CONFIG.read().await;
cfg.get("password").as_str().unwrap_or("").to_string()
};
let password = &format!("{}{}", password, c);
CONFIG.write().await.change("password", password);
CONFIG.write().await.update();
*UNIQUE.write().await = true;
}
(KeyCode::Char(c), KeyModifiers::SHIFT) => {
let password: String = {
let cfg = CONFIG.read().await;
cfg.get("password").as_str().unwrap_or("").to_string()
};
let password = &format!("{}{}", password, c);
CONFIG.write().await.change("password", password);
CONFIG.write().await.update();
*UNIQUE.write().await = true;
}
_ => {}
}
}

View file

@ -1,6 +1,8 @@
pub mod app_state;
pub mod log_panel;
pub mod settings_panel;
pub mod widgets {
pub mod betterblock;
}
pub mod input_handler;
pub mod tui;

29
src/gui/settings_panel.rs Normal file
View file

@ -0,0 +1,29 @@
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Margin, Rect},
text::Span,
widgets::{Block, Paragraph},
};
use crate::gui::app_state::AppState;
pub fn draw(frame: &mut Frame, area: Rect, block: Block, password: String, _state: AppState) {
frame.render_widget(block, area);
let padded = area.inner(Margin {
horizontal: 1,
vertical: 1,
});
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
])
.split(padded);
let prefix = Span::raw(format!("select password : {}", password));
frame.render_widget(Paragraph::new(prefix), chunks[0]);
}

View file

@ -1,11 +1,15 @@
use std::{
default,
io::{Stdout, stdout},
sync::Arc,
thread,
time::Duration,
};
use crate::{
APP_STATE, SHUTDOWN,
gui::{settings_panel, widgets::betterblock::draw_block_joins},
util::config_util::CONFIG,
};
use crossterm::{
execute,
terminal::{EnterAlternateScreen, enable_raw_mode},
@ -26,8 +30,6 @@ use tokio::{
sync::{Mutex, RwLock},
};
use crate::{APP_STATE, SHUTDOWN};
// ****** UTIL ******
fn init_terminal() {
let mut stdout = stdout();
@ -36,7 +38,7 @@ fn init_terminal() {
}
// ****** MAIN ******
pub static UNIQUE: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(false));
pub static UNIQUE: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
pub static TERMINAL: Lazy<Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>> = Lazy::new(|| {
Arc::new(Mutex::new(
Terminal::new(CrosstermBackend::new(stdout())).unwrap(),
@ -59,6 +61,13 @@ pub fn start_tui() {
});
}
pub async fn render_tui() {
let password = CONFIG
.read()
.await
.get("password")
.as_str()
.unwrap_or("")
.to_string();
TERMINAL
.lock()
.await
@ -98,18 +107,21 @@ pub async fn render_tui() {
}
// SETTINGS
{
let items: Vec<ListItem> = state
.logs
.iter()
.rev()
.map(|s| ListItem::new(s.clone()))
.collect();
let list = List::new(items).block(
settings_panel::draw(
f,
chunks[1],
Block::default()
.title("Settings")
.borders(Borders::LEFT.union(Borders::TOP).union(Borders::BOTTOM)),
password,
state.clone(),
);
draw_block_joins(
f,
chunks[1],
Borders::TOP.union(Borders::LEFT).union(Borders::BOTTOM),
Borders::LEFT,
);
f.render_widget(list, chunks[1]);
}
// GRAPHS
{
@ -129,22 +141,44 @@ pub async fn render_tui() {
stack[0],
"CPU".to_string(),
state.with_width(38).cpu,
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
Color::Cyan,
);
draw_block_joins(
f,
stack[0],
Borders::TOP.union(Borders::LEFT),
Borders::LEFT,
);
render_graphs(
f,
stack[1],
"RAM".to_string(),
state.with_width(38).ram,
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
Color::Green,
);
draw_block_joins(
f,
stack[1],
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
Borders::TOP,
);
render_graphs(
f,
stack[2],
"PING".to_string(),
state.with_width(38).ping,
Borders::ALL,
Color::Magenta,
);
draw_block_joins(f, stack[2], Borders::ALL, Borders::TOP);
draw_block_joins(
f,
stack[2],
Borders::BOTTOM.union(Borders::LEFT),
Borders::LEFT,
);
}
})
.unwrap();
@ -156,6 +190,7 @@ pub fn render_graphs(
area: Rect,
title: String,
graph: Vec<(f64, f64)>,
borders: Borders,
color: Color,
) {
let min_x = graph.first().map(|(x, _)| *x).unwrap_or(0.0);
@ -169,13 +204,13 @@ pub fn render_graphs(
let max_y = graph.iter().map(|(_, y)| *y).fold(f64::MIN, f64::max);
let block = Block::default()
.title(format!(
"{}: {}, {}/{} MIN/MAX ",
"{}: {}──{}/{} MIN/MAX ",
title,
graph.last().unwrap_or(&(0.0 as f64, 0.0 as f64)).1 as i64,
min_y as i64,
max_y as i64
))
.borders(Borders::ALL);
.borders(borders);
let canvas = Canvas::default()
.block(block)
.x_bounds([min_x, max_x])

View file

@ -35,7 +35,7 @@ pub fn create_languages() -> Result<(), JsonError> {
general_texts.insert("setup_completed", "Launched")?;
general_texts.insert(
"community_active",
"Communities active on ws://0.0.0.0:{}/community/...",
"Communities active on ws://{}:{}/community/...",
)?;
general_texts.insert(
"community_start_error",

View file

@ -1,5 +1,6 @@
use json::{self, JsonValue::String};
use once_cell::sync::Lazy;
use pnet::datalink::NetworkInterface;
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::Mutex;
@ -22,6 +23,7 @@ use crate::communities::community_manager;
use crate::communities::interactables::registry;
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::gui::app_state::AppState;
use crate::gui::input_handler;
use crate::gui::log_panel;
use crate::gui::log_panel::{log_message, log_message_trans};
use crate::gui::tui;
@ -57,12 +59,16 @@ async fn main() {
// UI
log_panel::setup();
tui::start_tui();
input_handler::setup_input_handler();
// BASIC CONFIGURATION
CONFIG.lock().await.load();
if !CONFIG.lock().await.config.has_key("iota_id") {
CONFIG.lock().await.change("iota_id", Uuid::new_v4());
CONFIG.lock().await.update();
&CONFIG.write().await.load();
if !CONFIG.read().await.config.has_key("iota_id") {
CONFIG
.write()
.await
.change("iota_id", &Uuid::new_v4().to_string());
CONFIG.write().await.update();
}
// USER MANAGEMENT
@ -84,7 +90,7 @@ async fn main() {
log_message(format!(
"IOTA ID: {}-####-####-####-############",
CONFIG
.lock()
.read()
.await
.get_iota_id()
.to_string()
@ -108,9 +114,18 @@ async fn main() {
sb1 = sb1 + ",";
}
log_message(format!("Community IDS: {}", sb1));
let port = CONFIG.lock().await.get_port();
let port = CONFIG.read().await.get_port();
let mut ip = "0.0.0.0".to_string();
for iface in pnet::datalink::interfaces() {
let iface: NetworkInterface = iface;
let ipsv = format!("{}", iface.ips[0]);
let ips: &str = ipsv.split('/').next().unwrap();
if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") {
ip = ips.to_string();
}
}
if start(port).await {
log_message(format("community_active", &[&port.to_string()]));
log_message(format("community_active", &[&ip, &port.to_string()]));
} else {
if port < 1024 {
log_message(format("community_start_error_admin", &[&port.to_string()]));
@ -137,7 +152,7 @@ async fn main() {
.add_data(DataTypes::user_ids, String(sb.to_string()))
.add_data(
DataTypes::iota_id,
String(CONFIG.lock().await.get_iota_id().to_string()),
String(CONFIG.read().await.get_iota_id().to_string()),
)
.to_json()
.to_string()

View file

@ -164,7 +164,7 @@ pub async fn handle(
if let Some(key) = headers.get("key") {
if let Some(value) = headers.get("value") {
let _ = CONFIG
.lock()
.write()
.await
.config
.insert(key.to_str().unwrap(), value.to_str().unwrap());
@ -176,11 +176,11 @@ pub async fn handle(
"{\"type\":\"error\"}".to_string()
}
}
"get" => CONFIG.lock().await.config.to_string(),
"get" => CONFIG.read().await.config.to_string(),
_ => "{\"type\":\"error\"}".to_string(),
}
} else {
CONFIG.lock().await.config.to_string()
CONFIG.read().await.config.to_string()
}
}),

View file

@ -17,6 +17,7 @@ use hyper::{
};
use hyper_util::rt::tokio::TokioIo;
use hyper_util::service::TowerToHyperService;
use pnet::datalink::NetworkInterface;
use rustls::ServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use sha1::{Digest, Sha1};
@ -221,6 +222,15 @@ fn is_local_network(addr: IpAddr) -> bool {
}
async fn run_http_server(port: u16) -> bool {
let mut ip = "0.0.0.0".to_string();
for iface in pnet::datalink::interfaces() {
let iface: NetworkInterface = iface;
let ipsv = format!("{}", iface.ips[0]);
let ips: &str = ipsv.split('/').next().unwrap();
if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") {
ip = ips.to_string();
}
}
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
if let Err(e) = listener {
log_message(format!("Failed to bind to port {}: {:?}", port, e));
@ -228,8 +238,8 @@ async fn run_http_server(port: u16) -> bool {
}
let listener = listener.unwrap();
log_message(format!(
"Standard Server listening for HTTP and WS on 0.0.0.0:{}",
port
"Standard Server listening for HTTP and WS on {}:{}",
ip, port
));
tokio::spawn(async move {
@ -276,6 +286,17 @@ async fn run_http_server(port: u16) -> bool {
/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config.
async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
let mut ip = "0.0.0.0".to_string();
for iface in pnet::datalink::interfaces() {
let iface: NetworkInterface = iface;
let ipsv = format!("{}", iface.ips[0]);
let ips: &str = ipsv.split('/').next().unwrap();
log_message(ips);
if format!("{}", ips).starts_with("10.") {
ip = ips.to_string();
}
}
let acceptor = TlsAcceptor::from(tls_config);
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await;
@ -285,8 +306,8 @@ async fn run_tls_server(port: u16, tls_config: Arc<ServerConfig>) -> bool {
}
let listener = listener.unwrap();
log_message(format!(
"Encrypted Server listening for HTTPS and WSS on 0.0.0.0:{}",
port
"Encrypted Server listening for HTTPS and WSS on {}:{}",
ip, port
));
tokio::spawn(async move {
@ -348,17 +369,10 @@ pub async fn start(port: u16) -> bool {
let tls_result = load_tls_config();
match tls_result {
Ok(Some(tls_config)) => {
// Certificates found and config loaded successfully, run the TLS server
run_tls_server(port, tls_config).await
}
Ok(None) => {
// Certificates not found, run the standard HTTP server
run_http_server(port).await
}
Ok(Some(tls_config)) => run_tls_server(port, tls_config).await,
Ok(None) => run_http_server(port).await,
Err(e) => {
log_message(format!("Fatal error during TLS config load: {}", e));
// Error, server cannot start
false
}
}

View file

@ -70,7 +70,7 @@ pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>
reset_token,
);
auth_connector::complete_register(&up, &CONFIG.lock().await.get_iota_id().to_string()).await;
auth_connector::complete_register(&up, &CONFIG.read().await.get_iota_id().to_string()).await;
save_file(
"",
&format!("{}.tu", username),

View file

@ -1,10 +1,10 @@
use crate::util::file_util::{load_file, save_file};
use json::JsonValue;
use once_cell::sync::Lazy;
use tokio::sync::Mutex;
use tokio::sync::RwLock;
use uuid::Uuid;
pub static CONFIG: Lazy<Mutex<ConfigUtil>> = Lazy::new(|| Mutex::new(ConfigUtil::new()));
pub static CONFIG: Lazy<RwLock<ConfigUtil>> = Lazy::new(|| RwLock::new(ConfigUtil::new()));
pub struct ConfigUtil {
pub config: JsonValue,
@ -38,7 +38,11 @@ impl ConfigUtil {
self.config["port"].as_u16().unwrap_or(1984)
}
pub fn change(&mut self, key: &str, value: Uuid) {
pub fn get(&self, key: &str) -> &JsonValue {
&self.config[key]
}
pub fn change(&mut self, key: &str, value: &str) {
self.config[key] = JsonValue::String(value.to_string());
self.unique = true;
}