[WIP] New UI Logic
This commit is contained in:
parent
b8e18490f3
commit
6db4e806f8
26 changed files with 815 additions and 674 deletions
|
|
@ -160,7 +160,7 @@ impl CommunityConnection {
|
|||
|
||||
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
__ => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||
.await;
|
||||
return;
|
||||
|
|
@ -178,7 +178,7 @@ impl CommunityConnection {
|
|||
|
||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||
Some(secret) => secret,
|
||||
None => {
|
||||
_ => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
|
|
@ -223,7 +223,7 @@ impl CommunityConnection {
|
|||
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
|
||||
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
|
||||
Some(data) => data.to_string(),
|
||||
None => {
|
||||
_ => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
|
|
@ -273,7 +273,7 @@ impl CommunityConnection {
|
|||
|
||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||
Some(secret) => secret,
|
||||
None => {
|
||||
_ => {
|
||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||
.await;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::communities::community::{self, Community};
|
||||
use crate::gui::log_panel;
|
||||
use crate::log;
|
||||
use crate::util::file_util;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -35,7 +35,7 @@ pub async fn load_communities() {
|
|||
if let Some(community) = community::load(&name).await {
|
||||
add_community(community).await;
|
||||
} else {
|
||||
log_panel::log_message(format!("failed to load the {} community", &name));
|
||||
log!("failed to load the {} community", &name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
interactables::interactable::Interactable,
|
||||
},
|
||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||
gui::log_panel::log_message,
|
||||
log,
|
||||
util::file_util::{get_children, load_file, save_file},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
|
@ -37,7 +37,7 @@ impl TextChat {
|
|||
);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(user_dir) {
|
||||
log_message(format!("Failed to create chat directory: {}", e));
|
||||
log!("Failed to create chat directory: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -56,16 +56,15 @@ impl TextChat {
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
||||
log!("Failed to parse existing JSON file: {}", file_name);
|
||||
}
|
||||
} else {
|
||||
// New file, use empty array
|
||||
break;
|
||||
}
|
||||
|
||||
chunk_index += 1;
|
||||
if chunk_index > 1000 {
|
||||
log_message(format!("Too many message chunks. Aborting add."));
|
||||
log!("Too many message chunks. Aborting add.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -77,12 +76,12 @@ impl TextChat {
|
|||
};
|
||||
|
||||
if let Err(e) = message_chunk.push(json_obj) {
|
||||
log_message(format!("Failed to push new message into JSON array: {}", e));
|
||||
log!("Failed to push new message into JSON array: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let file_name = format!("msgs_{}.json", chunk_index);
|
||||
log_message(format!("Saving message to {}/{}", user_dir, file_name));
|
||||
log!("Saving message to {}/{}", user_dir, file_name);
|
||||
save_file(&user_dir, &file_name, &message_chunk.dump());
|
||||
}
|
||||
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use crate::gui::elements::log_card::UiLogEntry;
|
||||
use json::{JsonValue, object};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub logs: VecDeque<String>,
|
||||
pub logs: VecDeque<UiLogEntry>,
|
||||
pub cpu: Vec<(f64, f64)>,
|
||||
pub ram: Vec<(f64, f64)>,
|
||||
pub ping: Vec<(f64, f64)>,
|
||||
|
|
@ -28,13 +28,17 @@ impl AppState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn push_log(&mut self, msg: String) {
|
||||
pub fn push_log(&mut self, msg: UiLogEntry) {
|
||||
if self.logs.len() >= MAX_LOGS {
|
||||
self.logs.pop_front();
|
||||
}
|
||||
self.logs.push_back(msg);
|
||||
}
|
||||
|
||||
pub fn get_logs(&self) -> &VecDeque<UiLogEntry> {
|
||||
&self.logs
|
||||
}
|
||||
|
||||
pub fn push_cpu(&mut self, pt: (f64, f64)) {
|
||||
self.cpu.push(pt);
|
||||
if self.cpu.len() > MAX_POINTS {
|
||||
|
|
@ -112,28 +116,23 @@ impl AppState {
|
|||
let len = data.len();
|
||||
|
||||
if len >= width_usize {
|
||||
// Trim data to fit
|
||||
data[len - width_usize..].to_vec()
|
||||
} else {
|
||||
let mut result = Vec::with_capacity(width_usize);
|
||||
|
||||
// Define X spacing (so dummy points are properly spaced across the canvas)
|
||||
let dx = 1.0;
|
||||
let pad_len = width_usize - len;
|
||||
|
||||
// If we have real data, use its first x position to determine where to start padding
|
||||
let start_x = data
|
||||
.first()
|
||||
.map(|(x, _)| x - (dx * pad_len as f64))
|
||||
.unwrap_or(0.0);
|
||||
let _ = data.first().map(|(_, y)| *y).unwrap_or(0.0);
|
||||
|
||||
// Fill padding with increasing x positions so they're visible
|
||||
for i in 0..pad_len {
|
||||
result.push((start_x + i as f64 * dx, -1 as f64));
|
||||
}
|
||||
|
||||
// Then append the real data
|
||||
result.extend_from_slice(data);
|
||||
result
|
||||
}
|
||||
|
|
|
|||
35
src/gui/elements/elements.rs
Normal file
35
src/gui/elements/elements.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::any::Any;
|
||||
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{Frame, layout::Rect};
|
||||
|
||||
use crate::gui::{interaction_result::InteractionResult, screens::screens::Screen};
|
||||
|
||||
pub trait Element: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
fn render(&self, f: &mut Frame, r: Rect);
|
||||
}
|
||||
|
||||
pub trait InfoElement: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn as_element(&self) -> &dyn Element;
|
||||
fn as_element_mut(&mut self) -> &mut dyn Element;
|
||||
|
||||
fn get_info_screen(&self) -> Box<dyn Screen>;
|
||||
}
|
||||
|
||||
pub trait InteractableElement: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
fn as_element(&self) -> &dyn Element;
|
||||
fn as_element_mut(&mut self) -> &mut dyn Element;
|
||||
|
||||
fn interact(&mut self, key: KeyEvent) -> InteractionResult;
|
||||
|
||||
fn can_focus(&self) -> bool;
|
||||
fn is_focused(&self) -> bool;
|
||||
fn focus(&mut self, f: bool);
|
||||
}
|
||||
113
src/gui/elements/log_card.rs
Normal file
113
src/gui/elements/log_card.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use crate::gui::elements::elements::InteractableElement;
|
||||
use crate::gui::interaction_result::InteractionResult;
|
||||
use crate::{APP_STATE, gui::elements::elements::Element};
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
layout::Rect,
|
||||
style::{Color, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UiLogEntry {
|
||||
pub line: String,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
pub struct LogCard {
|
||||
focused: bool,
|
||||
scroll: u16,
|
||||
}
|
||||
|
||||
impl LogCard {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
focused: false,
|
||||
scroll: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Element for LogCard {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn render(&self, f: &mut Frame, area: Rect) {
|
||||
let state = APP_STATE.lock().unwrap();
|
||||
let logs = state.get_logs();
|
||||
|
||||
let lines: Vec<Line> = logs
|
||||
.iter()
|
||||
.map(|log| Line::from(Span::raw(log.line.clone())).style(log.color))
|
||||
.collect();
|
||||
|
||||
let block = Block::default()
|
||||
.title("Logs")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(if self.focused {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default()
|
||||
});
|
||||
|
||||
let paragraph = Paragraph::new(lines)
|
||||
.block(block)
|
||||
.wrap(Wrap { trim: false })
|
||||
.scroll((self.scroll, 0));
|
||||
|
||||
f.render_widget(paragraph, area);
|
||||
}
|
||||
}
|
||||
impl InteractableElement for LogCard {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_element(&self) -> &dyn Element {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_element_mut(&mut self) -> &mut dyn Element {
|
||||
self
|
||||
}
|
||||
|
||||
fn interact(&mut self, key: KeyEvent) -> InteractionResult {
|
||||
match key.code {
|
||||
KeyCode::Up => {
|
||||
if self.scroll > 0 {
|
||||
self.scroll -= 1;
|
||||
}
|
||||
InteractionResult::Handeled
|
||||
}
|
||||
KeyCode::Down => {
|
||||
self.scroll += 1;
|
||||
InteractionResult::Handeled
|
||||
}
|
||||
_ => InteractionResult::Unhandeled,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_focus(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
fn focus(&mut self, f: bool) {
|
||||
self.focused = f;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use crate::ACTIVE_TASKS;
|
||||
use crate::{RELOAD, SHUTDOWN, gui::tui::UNIQUE, util::config_util::CONFIG};
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, poll, read};
|
||||
|
||||
use json::JsonValue;
|
||||
|
||||
pub fn setup_input_handler() {
|
||||
tokio::spawn(async move {
|
||||
{
|
||||
let mut tasks = ACTIVE_TASKS.lock().unwrap();
|
||||
tasks.push("Input Handler".to_string());
|
||||
}
|
||||
|
||||
loop {
|
||||
{
|
||||
let should_shutdown = *SHUTDOWN.read().await;
|
||||
if should_shutdown {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let has_event = match poll(Duration::from_millis(100)) {
|
||||
Ok(true) => true,
|
||||
Ok(false) => false,
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
if has_event {
|
||||
match read() {
|
||||
Ok(event) => {
|
||||
if let Event::Key(key_event) = event {
|
||||
handle_input(key_event).await;
|
||||
}
|
||||
}
|
||||
Err(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut tasks = ACTIVE_TASKS.lock().unwrap();
|
||||
tasks.retain(|t| t != "Input Handler");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn handle_input(key: KeyEvent) {
|
||||
match (key.code, key.modifiers) {
|
||||
(KeyCode::Char('q'), KeyModifiers::CONTROL) => {
|
||||
*SHUTDOWN.write().await = true;
|
||||
}
|
||||
(KeyCode::Char('c'), KeyModifiers::CONTROL) => {
|
||||
*SHUTDOWN.write().await = true;
|
||||
}
|
||||
(KeyCode::Char('r'), KeyModifiers::CONTROL) => {
|
||||
{
|
||||
*RELOAD.write().await = true;
|
||||
}
|
||||
{
|
||||
*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],
|
||||
_ => password,
|
||||
};
|
||||
|
||||
CONFIG
|
||||
.write()
|
||||
.await
|
||||
.change("password", JsonValue::String(password.to_string()));
|
||||
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", JsonValue::String(password.to_string()));
|
||||
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", JsonValue::String(password.to_string()));
|
||||
CONFIG.write().await.update();
|
||||
*UNIQUE.write().await = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
27
src/gui/interaction_result.rs
Normal file
27
src/gui/interaction_result.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::gui::screens::screens::Screen;
|
||||
|
||||
pub enum InteractionResult {
|
||||
OpenScreen {
|
||||
screen: Box<dyn Screen>,
|
||||
},
|
||||
OpenFutureScreen {
|
||||
screen: Pin<Box<dyn Future<Output = Box<dyn Screen>> + Send>>,
|
||||
},
|
||||
Handeled,
|
||||
Unhandeled,
|
||||
}
|
||||
|
||||
impl Debug for InteractionResult {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InteractionResult::OpenScreen { screen: _ } => write!(f, "OpenScreen"),
|
||||
InteractionResult::OpenFutureScreen { screen: _ } => write!(f, "OpenFutureScreen"),
|
||||
InteractionResult::Handeled => write!(f, "Handeled"),
|
||||
InteractionResult::Unhandeled => write!(f, "Unhandeled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
use crate::ACTIVE_TASKS;
|
||||
use crate::APP_STATE;
|
||||
use crate::SHUTDOWN;
|
||||
use crate::gui::tui::UNIQUE;
|
||||
use crate::langu::language_manager::format;
|
||||
use crate::langu::language_manager::from_key;
|
||||
|
||||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
|
||||
use std::{thread, time::Duration};
|
||||
use sysinfo::{RefreshKind, System};
|
||||
|
||||
pub fn log_cv(cv: &CommunicationValue) {
|
||||
if cv.is_type(CommunicationType::identification_response) {
|
||||
let args = [cv.get_data(DataTypes::accepted).unwrap().as_str().unwrap()];
|
||||
log_message(format(&"identification_response", &args));
|
||||
} else {
|
||||
log_message_trans(format!("{:?}", &cv.get_type()));
|
||||
}
|
||||
tokio::spawn(async move {
|
||||
*UNIQUE.write().await = true;
|
||||
});
|
||||
}
|
||||
pub fn log_message_trans(key: impl Into<String>) {
|
||||
APP_STATE.lock().unwrap().push_log(from_key(&key.into()));
|
||||
tokio::spawn(async move {
|
||||
*UNIQUE.write().await = true;
|
||||
});
|
||||
}
|
||||
pub fn log_message(msg: impl Into<String>) {
|
||||
APP_STATE.lock().unwrap().push_log(msg.into());
|
||||
tokio::spawn(async move {
|
||||
*UNIQUE.write().await = true;
|
||||
});
|
||||
}
|
||||
pub fn log_message_format(msg: impl Into<String>, args: &[&str]) {
|
||||
APP_STATE
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push_log(format(&msg.into(), args));
|
||||
tokio::spawn(async move {
|
||||
*UNIQUE.write().await = true;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn setup() {
|
||||
tokio::spawn(async move {
|
||||
{
|
||||
ACTIVE_TASKS.lock().unwrap().push("metrics".to_string());
|
||||
}
|
||||
let mut sys = System::new_with_specifics(RefreshKind::new());
|
||||
let mut last_total_received = 0u64;
|
||||
let mut last_total_transmitted = 0u64;
|
||||
let mut counter = 0.0;
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
break;
|
||||
}
|
||||
sys.refresh_all();
|
||||
|
||||
let mut tcpu = 0;
|
||||
for cpu in sys.cpus() {
|
||||
tcpu += cpu.cpu_usage() as i64;
|
||||
tcpu /= 2;
|
||||
}
|
||||
let ram = (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0;
|
||||
|
||||
let total_received = 0u64;
|
||||
let total_transmitted = 0u64;
|
||||
|
||||
let delta_received = if last_total_received == 0 {
|
||||
0
|
||||
} else {
|
||||
total_received.saturating_sub(last_total_received)
|
||||
};
|
||||
let delta_transmitted = if last_total_transmitted == 0 {
|
||||
0
|
||||
} else {
|
||||
total_transmitted.saturating_sub(last_total_transmitted)
|
||||
};
|
||||
last_total_received = total_received;
|
||||
last_total_transmitted = total_transmitted;
|
||||
|
||||
let net_down = delta_received as f64;
|
||||
let net_up = delta_transmitted as f64;
|
||||
|
||||
{
|
||||
let mut st = APP_STATE.lock().unwrap();
|
||||
st.push_cpu((counter, tcpu as f64));
|
||||
st.push_ram((counter, ram));
|
||||
st.push_net_down((counter, net_down));
|
||||
st.push_net_up((counter, net_up));
|
||||
|
||||
st.sys_info = format!("NetDown: {} NetUp: {}", delta_received, delta_transmitted);
|
||||
}
|
||||
|
||||
counter += 1.0;
|
||||
*UNIQUE.write().await = true;
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
}
|
||||
{
|
||||
ACTIVE_TASKS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|t| !t.eq(&"metrics".to_string()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
pub mod app_state;
|
||||
pub mod log_panel;
|
||||
pub mod settings_panel;
|
||||
pub mod widgets {
|
||||
pub mod betterblock;
|
||||
pub mod elements {
|
||||
pub mod elements;
|
||||
pub mod log_card;
|
||||
}
|
||||
pub mod input_handler;
|
||||
pub mod tui;
|
||||
pub mod screens {
|
||||
pub mod screens;
|
||||
}
|
||||
pub mod app_state;
|
||||
pub mod interaction_result;
|
||||
pub mod ui;
|
||||
|
|
|
|||
27
src/gui/screens/screens.rs
Normal file
27
src/gui/screens/screens.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use std::{any::Any, sync::Arc};
|
||||
|
||||
use crossterm::event::KeyEvent;
|
||||
use ratatui::{Frame, layout::Rect};
|
||||
|
||||
use crate::gui::{interaction_result::InteractionResult, ui::UI};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum NavDirection {
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
Next,
|
||||
Prev,
|
||||
}
|
||||
|
||||
pub trait Screen: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
fn get_ui(&self) -> &Arc<UI>;
|
||||
|
||||
fn render(&self, f: &mut Frame, rect: Rect);
|
||||
fn handle_input(&mut self, event: KeyEvent) -> InteractionResult;
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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 info = vec![
|
||||
"This is the technical end of your Iota",
|
||||
"Press Ctrl+Q to quit",
|
||||
"Press Ctrl+R to reload",
|
||||
"Select a password for the WebUI",
|
||||
];
|
||||
let mut constraints: Vec<Constraint> = Vec::new();
|
||||
|
||||
for _ in 0..info.len() {
|
||||
constraints.push(Constraint::Length(1));
|
||||
}
|
||||
|
||||
constraints.push(Constraint::Length(3));
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(constraints)
|
||||
.split(padded);
|
||||
|
||||
for i in 0..info.len() {
|
||||
frame.render_widget(Paragraph::new(info[i]), chunks[i]);
|
||||
}
|
||||
|
||||
let prefix = Span::raw(format!("select password : {}", password));
|
||||
frame.render_widget(Paragraph::new(prefix), chunks[info.len()]);
|
||||
}
|
||||
230
src/gui/tui.rs
230
src/gui/tui.rs
|
|
@ -1,230 +0,0 @@
|
|||
use std::{io::Stdout, sync::Arc, time::Duration};
|
||||
|
||||
use crate::{
|
||||
APP_STATE, SHUTDOWN,
|
||||
gui::{settings_panel, widgets::betterblock::draw_block_joins},
|
||||
util::config_util::CONFIG,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use ratatui::{
|
||||
Frame, Terminal,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
prelude::CrosstermBackend,
|
||||
style::Color,
|
||||
widgets::{
|
||||
Block, Borders, List, ListItem,
|
||||
canvas::{Canvas, Line},
|
||||
},
|
||||
};
|
||||
use tokio::{
|
||||
self,
|
||||
sync::{Mutex, RwLock},
|
||||
};
|
||||
|
||||
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(ratatui::init())));
|
||||
|
||||
pub fn start_tui() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
if *UNIQUE.read().await {
|
||||
render_tui().await;
|
||||
} else {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
ratatui::restore();
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn render_tui() {
|
||||
let password = CONFIG
|
||||
.read()
|
||||
.await
|
||||
.get("password")
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
let mut terminal = TERMINAL.lock().await;
|
||||
let _ = terminal.draw(|f| {
|
||||
let area = f.area();
|
||||
|
||||
let is_horizontal = area.width >= 120;
|
||||
let chunks = if is_horizontal {
|
||||
Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(60),
|
||||
Constraint::Min(40),
|
||||
])
|
||||
.split(area)
|
||||
} else {
|
||||
Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
|
||||
.split(area)
|
||||
};
|
||||
|
||||
let state = APP_STATE.lock().unwrap().clone();
|
||||
|
||||
// LOGS PANEL
|
||||
{
|
||||
let borders = if is_horizontal {
|
||||
Borders::LEFT.union(Borders::TOP).union(Borders::BOTTOM)
|
||||
} else {
|
||||
Borders::LEFT.union(Borders::RIGHT).union(Borders::TOP)
|
||||
};
|
||||
let items: Vec<ListItem> = state
|
||||
.logs
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|s| ListItem::new(s.clone()))
|
||||
.collect();
|
||||
let list = List::new(items).block(Block::default().title("Logs").borders(borders));
|
||||
f.render_widget(list, chunks[0]);
|
||||
}
|
||||
// SETTINGS PANEL
|
||||
{
|
||||
let borders = if is_horizontal {
|
||||
Borders::LEFT.union(Borders::TOP).union(Borders::BOTTOM)
|
||||
} else {
|
||||
Borders::ALL
|
||||
};
|
||||
settings_panel::draw(
|
||||
f,
|
||||
chunks[1],
|
||||
Block::default().title("Settings").borders(borders),
|
||||
password,
|
||||
state.clone(),
|
||||
);
|
||||
if is_horizontal {
|
||||
draw_block_joins(
|
||||
f,
|
||||
chunks[1],
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::BOTTOM),
|
||||
Borders::LEFT,
|
||||
);
|
||||
} else {
|
||||
draw_block_joins(f, chunks[1], Borders::ALL, Borders::TOP);
|
||||
}
|
||||
}
|
||||
|
||||
// PERFORMANCE GRAPHS
|
||||
if is_horizontal {
|
||||
let stack = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
])
|
||||
.split(chunks[2]);
|
||||
|
||||
render_graphs(
|
||||
f,
|
||||
stack[0],
|
||||
"CPU".into(),
|
||||
"%".into(),
|
||||
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".into(),
|
||||
"%".into(),
|
||||
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".into(),
|
||||
"ms".into(),
|
||||
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,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
*UNIQUE.write().await = false;
|
||||
}
|
||||
|
||||
pub fn render_graphs(
|
||||
f: &mut Frame<'_>,
|
||||
area: Rect,
|
||||
title: String,
|
||||
unit: String,
|
||||
graph: Vec<(f64, f64)>,
|
||||
borders: Borders,
|
||||
color: Color,
|
||||
) {
|
||||
let min_x = graph.first().map(|(x, _)| *x).unwrap_or(0.0);
|
||||
let max_x = graph.last().map(|(x, _)| *x).unwrap_or(100.0);
|
||||
let min_y = graph
|
||||
.iter()
|
||||
.map(|(_, y)| *y)
|
||||
.filter(|y| *y > 0.0)
|
||||
.min_by(|a, b| a.total_cmp(b))
|
||||
.unwrap_or(0.0);
|
||||
let max_y = graph.iter().map(|(_, y)| *y).fold(f64::MIN, f64::max);
|
||||
|
||||
let block = Block::default()
|
||||
.title(format!(
|
||||
"─{}:─{}{}──{}/{}─MIN/MAX",
|
||||
title,
|
||||
graph.last().unwrap_or(&(0.0, 0.0)).1 as i64,
|
||||
unit,
|
||||
min_y as i64,
|
||||
max_y as i64
|
||||
))
|
||||
.borders(borders);
|
||||
|
||||
let canvas = Canvas::default()
|
||||
.block(block)
|
||||
.x_bounds([min_x, max_x])
|
||||
.y_bounds([0.0, 100.0])
|
||||
.paint(|ctx| {
|
||||
for (x, y) in &graph {
|
||||
ctx.draw(&Line {
|
||||
x1: *x,
|
||||
y1: 0.0,
|
||||
x2: *x,
|
||||
y2: *y,
|
||||
color,
|
||||
});
|
||||
}
|
||||
});
|
||||
f.render_widget(canvas, area);
|
||||
}
|
||||
197
src/gui/ui.rs
Normal file
197
src/gui/ui.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
use crossterm::{
|
||||
cursor::MoveTo,
|
||||
execute,
|
||||
terminal::{Clear, ClearType, EnterAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
Terminal,
|
||||
backend::{Backend, CrosstermBackend},
|
||||
layout::Size,
|
||||
};
|
||||
use std::{
|
||||
io::{self, Error},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
use crate::gui::{interaction_result::InteractionResult, screens::screens::Screen};
|
||||
|
||||
struct ResizableBackend<'a> {
|
||||
backend: CrosstermBackend<&'a mut Vec<u8>>,
|
||||
size: Size,
|
||||
}
|
||||
|
||||
impl<'a> ResizableBackend<'a> {
|
||||
fn new(buf: &'a mut Vec<u8>, width: u16, height: u16) -> Self {
|
||||
Self {
|
||||
backend: CrosstermBackend::new(buf),
|
||||
size: Size::new(width, height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Backend for ResizableBackend<'a> {
|
||||
type Error = Error;
|
||||
|
||||
fn draw<'b, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'b ratatui::buffer::Cell)>,
|
||||
{
|
||||
self.backend.draw(content)
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
self.backend.hide_cursor()
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
self.backend.show_cursor()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
self.backend.get_cursor()
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.backend.set_cursor(x, y)
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
self.backend.clear()
|
||||
}
|
||||
|
||||
fn clear_region(&mut self, region: ratatui::backend::ClearType) -> io::Result<()> {
|
||||
self.backend.clear_region(region)
|
||||
}
|
||||
|
||||
fn size(&self) -> Result<Size, Error> {
|
||||
Ok(self.size)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.backend.flush()
|
||||
}
|
||||
|
||||
fn get_cursor_position(&mut self) -> Result<ratatui::prelude::Position, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_cursor_position<P: Into<ratatui::prelude::Position>>(
|
||||
&mut self,
|
||||
_position: P,
|
||||
) -> Result<(), Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn window_size(&mut self) -> Result<ratatui::prelude::backend::WindowSize, Self::Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// UI state and rendering
|
||||
|
||||
pub struct UI {
|
||||
pub cols: Arc<Mutex<u16>>,
|
||||
pub rows: Arc<Mutex<u16>>,
|
||||
screen: Arc<Mutex<Option<Box<dyn Screen>>>>,
|
||||
|
||||
cached_render: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl UI {
|
||||
pub fn new(cols: u16, rows: u16) -> Self {
|
||||
Self {
|
||||
cols: Arc::new(Mutex::new(cols)),
|
||||
rows: Arc::new(Mutex::new(rows)),
|
||||
screen: Arc::new(Mutex::new(None)),
|
||||
cached_render: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_screen(&self, screen: Box<dyn Screen>) {
|
||||
*self.screen.lock().unwrap() = Some(screen);
|
||||
}
|
||||
|
||||
pub fn resize(&self, cols: u32, rows: u32) {
|
||||
*self.cols.lock().unwrap() = cols as u16;
|
||||
*self.rows.lock().unwrap() = rows as u16;
|
||||
}
|
||||
|
||||
pub async fn handle_input(&self, input: &[u8]) {
|
||||
let key_event = if input.len() == 1 {
|
||||
let c = input[0] as char;
|
||||
if c.is_ascii() {
|
||||
Some(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let event = match input {
|
||||
b"\x1b[A" => Some(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE)),
|
||||
b"\x1b[B" => Some(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE)),
|
||||
b"\x1b[C" => Some(KeyEvent::new(KeyCode::Right, KeyModifiers::NONE)),
|
||||
b"\x1b[D" => Some(KeyEvent::new(KeyCode::Left, KeyModifiers::NONE)),
|
||||
b"\r" | b"\n" => Some(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)),
|
||||
b"\x7f" => Some(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)),
|
||||
_ => key_event,
|
||||
};
|
||||
|
||||
if let Some(event) = event {
|
||||
let result = {
|
||||
let mut guard = self.screen.lock().unwrap();
|
||||
if let Some(screen) = guard.as_mut() {
|
||||
screen.handle_input(event)
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
match result {
|
||||
InteractionResult::OpenScreen { screen } => {
|
||||
self.set_screen(screen);
|
||||
}
|
||||
InteractionResult::OpenFutureScreen { screen: fut } => {
|
||||
let ui = self.clone();
|
||||
let screen = fut.await;
|
||||
ui.set_screen(screen);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
execute!(
|
||||
&mut buf,
|
||||
EnterAlternateScreen,
|
||||
Clear(ClearType::All),
|
||||
MoveTo(0, 0)
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let backend = ResizableBackend::new(
|
||||
&mut buf,
|
||||
*self.cols.lock().unwrap(),
|
||||
*self.rows.lock().unwrap(),
|
||||
);
|
||||
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
|
||||
if let Some(screen) = self.screen.lock().unwrap().as_ref() {
|
||||
let _ = terminal.draw(|f| screen.render(f, f.area()));
|
||||
}
|
||||
}
|
||||
|
||||
*self.cached_render.lock().unwrap() = buf.clone();
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::style::Style;
|
||||
use ratatui::widgets::Borders;
|
||||
|
||||
fn set_join_char(frame: &mut Frame, x: u16, y: u16, c: char) {
|
||||
frame
|
||||
.buffer_mut()
|
||||
.set_string(x, y, c.to_string(), Style::default());
|
||||
}
|
||||
|
||||
/// Draws corner join characters only if the block has borders on that side
|
||||
pub fn draw_block_joins(frame: &mut Frame, area: Rect, borders: Borders, joins: Borders) {
|
||||
let x0 = area.x;
|
||||
let y0 = area.y;
|
||||
let x1 = area.x + area.width - 1;
|
||||
let y1 = area.y + area.height - 1;
|
||||
|
||||
// Top-left corner
|
||||
if borders.contains(Borders::TOP) && borders.contains(Borders::LEFT) {
|
||||
let top_left = match (joins.contains(Borders::TOP), joins.contains(Borders::LEFT)) {
|
||||
(true, true) => '┼',
|
||||
(true, false) => '├',
|
||||
(false, true) => '┬',
|
||||
(false, false) => '┌',
|
||||
};
|
||||
set_join_char(frame, x0, y0, top_left);
|
||||
}
|
||||
|
||||
// Top-right corner
|
||||
if borders.contains(Borders::TOP) && borders.contains(Borders::RIGHT) {
|
||||
let top_right = match (joins.contains(Borders::TOP), joins.contains(Borders::RIGHT)) {
|
||||
(true, true) => '┼',
|
||||
(true, false) => '┤',
|
||||
(false, true) => '┬',
|
||||
(false, false) => '┐',
|
||||
};
|
||||
set_join_char(frame, x1, y0, top_right);
|
||||
}
|
||||
|
||||
// Bottom-left corner
|
||||
if borders.contains(Borders::BOTTOM) && borders.contains(Borders::LEFT) {
|
||||
let bottom_left = match (
|
||||
joins.contains(Borders::BOTTOM),
|
||||
joins.contains(Borders::LEFT),
|
||||
) {
|
||||
(true, true) => '┼',
|
||||
(true, false) => '├',
|
||||
(false, true) => '┴',
|
||||
(false, false) => '└',
|
||||
};
|
||||
set_join_char(frame, x0, y1, bottom_left);
|
||||
}
|
||||
|
||||
// Bottom-right corner
|
||||
if borders.contains(Borders::BOTTOM) && borders.contains(Borders::RIGHT) {
|
||||
let bottom_right = match (
|
||||
joins.contains(Borders::BOTTOM),
|
||||
joins.contains(Borders::RIGHT),
|
||||
) {
|
||||
(true, true) => '┼',
|
||||
(true, false) => '┤',
|
||||
(false, true) => '┴',
|
||||
(false, false) => '┘',
|
||||
};
|
||||
set_join_char(frame, x1, y1, bottom_right);
|
||||
}
|
||||
}
|
||||
32
src/main.rs
32
src/main.rs
|
|
@ -20,12 +20,8 @@ mod util;
|
|||
use crate::communities::community_manager;
|
||||
use crate::communities::interactables::registry;
|
||||
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;
|
||||
use crate::gui::ui;
|
||||
use crate::langu::language_creator;
|
||||
use crate::langu::language_manager::format;
|
||||
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
||||
use crate::server::server::start;
|
||||
use crate::terms::consent_state;
|
||||
|
|
@ -33,6 +29,7 @@ use crate::users::user_manager;
|
|||
use crate::util::config_util::CONFIG;
|
||||
use crate::util::file_util::download_and_extract_zip;
|
||||
use crate::util::file_util::has_dir;
|
||||
use crate::util::logger;
|
||||
|
||||
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
||||
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
|
||||
|
|
@ -48,6 +45,9 @@ async fn main() {
|
|||
*RELOAD.write().await = false;
|
||||
*SHUTDOWN.write().await = false;
|
||||
|
||||
let tui = ui::UI::new(100, 100);
|
||||
tokio::spawn(async move { tui.render() });
|
||||
|
||||
// EULA
|
||||
let (tos, pp) = consent_state::ConsentManager::check().await;
|
||||
if !tos {
|
||||
|
|
@ -71,16 +71,14 @@ async fn main() {
|
|||
}
|
||||
|
||||
// UI
|
||||
log_panel::setup();
|
||||
tui::start_tui();
|
||||
input_handler::setup_input_handler();
|
||||
logger::startup();
|
||||
|
||||
// BASIC CONFIGURATION
|
||||
&CONFIG.write().await.load();
|
||||
|
||||
// USER MANAGEMENT
|
||||
if let Err(_) = user_manager::load_users().await {
|
||||
log_message_trans("user_load_failed");
|
||||
log_t!("user_load_failed");
|
||||
}
|
||||
|
||||
let mut sb = "".to_string();
|
||||
|
|
@ -94,11 +92,11 @@ async fn main() {
|
|||
sb.remove(0);
|
||||
sb = sb + ",";
|
||||
}
|
||||
log_message(format!(
|
||||
log!(
|
||||
"IOTA ID: {}",
|
||||
CONFIG.read().await.get_iota_id().to_string()
|
||||
));
|
||||
log_message(format!("User IDS: {}", sb));
|
||||
);
|
||||
log!("User IDS: {}", sb);
|
||||
|
||||
// COMMUNITY MANAGEMENT
|
||||
registry::load_interactables().await;
|
||||
|
|
@ -113,7 +111,7 @@ async fn main() {
|
|||
sb1.remove(0);
|
||||
sb1 = sb1 + ",";
|
||||
}
|
||||
log_message(format!("Community IDS: {}", sb1));
|
||||
log!("Community IDS: {}", sb1);
|
||||
let port = CONFIG.read().await.get_port();
|
||||
let mut ip = "0.0.0.0".to_string();
|
||||
for iface in pnet::datalink::interfaces() {
|
||||
|
|
@ -127,12 +125,12 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
if start(port).await {
|
||||
log_message(format("community_active", &[&ip, &port.to_string()]));
|
||||
log_t!("community_active", ip, port.to_string());
|
||||
} else {
|
||||
if port < 1024 {
|
||||
log_message(format("community_start_error_admin", &[&port.to_string()]));
|
||||
log_t!("community_start_error_admin", port.to_string());
|
||||
} else {
|
||||
log_message(format("community_start_error", &[&port.to_string()]));
|
||||
log_t!("community_start_error", port.to_string());
|
||||
}
|
||||
}
|
||||
if !has_dir("web") {
|
||||
|
|
@ -150,7 +148,7 @@ async fn main() {
|
|||
omikron.connect().await;
|
||||
let mut omikron_connection = OMIKRON_CONNECTION.write().await;
|
||||
*omikron_connection = Some(omikron.clone());
|
||||
log_message_trans("setup_completed");
|
||||
log_t!("setup_completed");
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
use crate::gui::log_panel::{log_cv, log_message_format};
|
||||
use crate::users::contact::Contact;
|
||||
use crate::users::user_community_util::UserCommunityUtil;
|
||||
use crate::util::chat_files::{MessageState, change_message_state};
|
||||
use crate::util::chats_util::{get_user, mod_user};
|
||||
use crate::util::crypto_util::{DataFormat, SecurePayload};
|
||||
use crate::util::file_util::{get_children, load_file, save_file};
|
||||
use crate::util::logger::PrintType;
|
||||
use crate::util::{chat_files, chats_util};
|
||||
use crate::{ACTIVE_TASKS, SHUTDOWN};
|
||||
use crate::{ACTIVE_TASKS, SHUTDOWN, log, log_cv, log_t};
|
||||
use crate::{
|
||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||
gui::log_panel::{log_message, log_message_trans},
|
||||
util::{config_util::CONFIG, crypto_helper},
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
|
|
@ -26,6 +25,7 @@ use tokio::time::{Duration, Instant, sleep};
|
|||
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
||||
use tungstenite::Utf8Bytes;
|
||||
use uuid::Uuid;
|
||||
use warp::filters::log::log;
|
||||
|
||||
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
|
||||
LazyLock::new(|| Arc::new(RwLock::new(None)));
|
||||
|
|
@ -66,7 +66,7 @@ impl OmikronConnection {
|
|||
drop(conf);
|
||||
|
||||
if iota_id == 0 || public_key.is_none() || private_key.is_none() {
|
||||
log_message_trans("iota_register_new");
|
||||
log_t!("iota_register_new");
|
||||
let key_pair = crypto_helper::generate_keypair();
|
||||
let public_key_base64 = crypto_helper::public_key_to_base64(&key_pair.public);
|
||||
let private_key_base64 = crypto_helper::secret_key_to_base64(&key_pair.secret);
|
||||
|
|
@ -97,10 +97,10 @@ impl OmikronConnection {
|
|||
conf_write.change("iota_id", iota_json.clone());
|
||||
conf_write.update();
|
||||
drop(conf_write);
|
||||
log_message(format!("Registered with Iota-ID: {}", iota_id));
|
||||
log!("Registered with Iota-ID: {}", iota_id);
|
||||
}
|
||||
Err(timeout) => {
|
||||
log_message(timeout);
|
||||
log!("{}", timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ impl OmikronConnection {
|
|||
if self.is_connected().await {
|
||||
return true;
|
||||
}
|
||||
log_message_trans("omikron_connecting");
|
||||
log_t!("omikron_connecting");
|
||||
|
||||
let conf = CONFIG.read().await;
|
||||
let addr = conf
|
||||
|
|
@ -130,11 +130,11 @@ impl OmikronConnection {
|
|||
.unwrap_or("wss://app.tensamin.net/ws/iota/");
|
||||
let stream_res = connect_async(addr).await;
|
||||
if let Err(e) = stream_res {
|
||||
log_message(format!("con error {}", e.to_string()));
|
||||
log!("con error {}", e.to_string());
|
||||
return false;
|
||||
}
|
||||
let (stream, _) = stream_res.unwrap();
|
||||
log_message_trans("omikron_connection_success");
|
||||
log_t!("omikron_connection_success");
|
||||
|
||||
let (write_half, read_half) = stream.split();
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ impl OmikronConnection {
|
|||
);
|
||||
}
|
||||
*is_connected_out.lock().await = false;
|
||||
log_message("Connection closed.");
|
||||
log!("Connection closed.");
|
||||
});
|
||||
{
|
||||
ACTIVE_TASKS
|
||||
|
|
@ -219,7 +219,7 @@ impl OmikronConnection {
|
|||
tokio::spawn(async move {
|
||||
match msg {
|
||||
Ok(Message::Close(Some(frame))) => {
|
||||
log_message(format!("[Omikron] Closed: {:?}", frame));
|
||||
log!("[Omikron] Closed: {:?}", frame);
|
||||
*is_connected.lock().await = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -271,7 +271,7 @@ impl OmikronConnection {
|
|||
|
||||
self.send_message(&response).await;
|
||||
} else {
|
||||
log_message("Failed to decrypt challenge");
|
||||
log!("Failed to decrypt challenge");
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -286,9 +286,8 @@ impl OmikronConnection {
|
|||
let mut conf = CONFIG.write().await;
|
||||
conf.change("iota_id", JsonValue::Number(iota_id.into()));
|
||||
conf.update();
|
||||
log_message(format!("Iota registered with ID: {}", iota_id));
|
||||
log!("Iota registered with ID: {}", iota_id);
|
||||
|
||||
// Now, proceed to login
|
||||
let login_message =
|
||||
CommunicationValue::new(CommunicationType::identification)
|
||||
.add_data(
|
||||
|
|
@ -301,13 +300,13 @@ impl OmikronConnection {
|
|||
self_clone.send_message(&login_message).await;
|
||||
});
|
||||
} else {
|
||||
log_message("Iota registration failed.");
|
||||
log("Iota registration failed.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if cv.is_type(CommunicationType::identification_response) {
|
||||
if let Some(accepted) = cv.get_data(DataTypes::accepted) {
|
||||
log_message(format!("Omikron connected: {}", accepted.to_string()));
|
||||
log!("Omikron connected: {}", accepted.to_string());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -315,7 +314,7 @@ impl OmikronConnection {
|
|||
// ************************************************ //
|
||||
// Direct messages //
|
||||
// ************************************************ //
|
||||
log_cv(&cv);
|
||||
log_cv!(PrintType::General, &cv);
|
||||
if let Some((_, y)) = waiting.remove(&cv.get_id()) {
|
||||
y(cv);
|
||||
return;
|
||||
|
|
@ -699,7 +698,7 @@ impl OmikronConnection {
|
|||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log_message(format!("[Omikron] Error: {}", e));
|
||||
log!("Omikron] Error: {}", e);
|
||||
*is_connected.lock().await = false;
|
||||
return;
|
||||
}
|
||||
|
|
@ -721,17 +720,17 @@ impl OmikronConnection {
|
|||
Ok(_) => match writer.flush().await {
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
log_message_format("send_message_failed", &[&e.to_string()]);
|
||||
log_t!("send_message_failed", e.to_string());
|
||||
*connected.lock().await = false;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log_message_format("send_message_failed", &[&e.to_string()]);
|
||||
log_t!("send_message_failed", e.to_string());
|
||||
*connected.lock().await = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_message_format("send_message_failed", &["Immutable Writer"]);
|
||||
log_t!("send_message_failed", "Immutable Writer".to_string());
|
||||
*connected.lock().await = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -750,7 +749,7 @@ impl OmikronConnection {
|
|||
let inner_tx = task_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = inner_tx.send(response_cv).await {
|
||||
log_message(format!("Failed to send response back to awaiter: {}", &e));
|
||||
log_t!("Failed to send response back to awaiter: {}", e.to_string());
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ async fn users_add(
|
|||
|
||||
let username = match payload.get("username").and_then(|v| v.as_str()) {
|
||||
Some(u) => u,
|
||||
None => return error(),
|
||||
_ => return error(),
|
||||
};
|
||||
|
||||
if let (Some(user), Some(_)) = crate::users::user_manager::create_user(username).await {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
use crate::gui::log_panel::log_message;
|
||||
use crate::log;
|
||||
use crate::server::api::api_config;
|
||||
use crate::server::socket::WsSession;
|
||||
use crate::server::web_path_parser;
|
||||
use crate::util::file_util::load_file_buf;
|
||||
use crate::{ACTIVE_TASKS, SHUTDOWN};
|
||||
|
||||
use actix_web::{App, Error, HttpRequest, HttpServer, Responder, dev::ServerHandle, web};
|
||||
use actix_web_actors::ws;
|
||||
|
||||
use rustls::ServerConfig;
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use std::{
|
||||
|
|
@ -20,7 +18,7 @@ use std::{
|
|||
|
||||
async fn ws_handler(req: HttpRequest, stream: web::Payload) -> Result<impl Responder, Error> {
|
||||
let path = req.path().to_string();
|
||||
log_message(format!("WS connection from {:?}", req.peer_addr()));
|
||||
log!("WS connection from {:?}", req.peer_addr());
|
||||
let session = WsSession::new(path);
|
||||
ws::start(session, &req, stream)
|
||||
}
|
||||
|
|
@ -33,7 +31,7 @@ pub async fn start(port: u16) -> bool {
|
|||
let _ = tokio::spawn(async move {
|
||||
let server = match load_tls_config() {
|
||||
Ok(Some(tls_config)) => {
|
||||
log_message(format!("HTTPS (HTTP/2) Server running on 0.0.0.0:{}", port));
|
||||
log!("HTTPS (HTTP/2) Server running on 0.0.0.0:{}", port);
|
||||
let _config = (*tls_config).clone();
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
|
|
@ -46,8 +44,8 @@ pub async fn start(port: u16) -> bool {
|
|||
.unwrap()
|
||||
.run()
|
||||
}
|
||||
Ok(None) => {
|
||||
log_message(format!("HTTP Server running on 0.0.0.0:{}", port));
|
||||
Ok(_) => {
|
||||
log!("HTTP Server running on 0.0.0.0:{}", port);
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(web::Data::new(false))
|
||||
|
|
@ -60,7 +58,7 @@ pub async fn start(port: u16) -> bool {
|
|||
.run()
|
||||
}
|
||||
Err(e) => {
|
||||
log_message(format!("TLS config error: {}", e));
|
||||
log!("TLS config error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -71,7 +69,7 @@ pub async fn start(port: u16) -> bool {
|
|||
ACTIVE_TASKS.lock().unwrap().push("WebServer".into());
|
||||
server.await.unwrap();
|
||||
ACTIVE_TASKS.lock().unwrap().retain(|t| t != "WebServer");
|
||||
log_message("Web Server shutdown complete.");
|
||||
log!("Web Server shutdown complete.");
|
||||
});
|
||||
|
||||
if let Ok(server_handle) = rx.await {
|
||||
|
|
@ -87,7 +85,7 @@ pub async fn start(port: u16) -> bool {
|
|||
async fn wait_for_shutdown(server_handle: ServerHandle) {
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
log_message("Shutdown signal received.");
|
||||
log!("Shutdown signal received.");
|
||||
server_handle.stop(true).await;
|
||||
break;
|
||||
}
|
||||
|
|
@ -102,7 +100,7 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn StdError>> {
|
|||
let cert_file_buf = match cert_file_res {
|
||||
Ok(b) => b,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
log_message("TLS certificate 'certs/cert.pem' not found.");
|
||||
log!("TLS certificate 'certs/cert.pem' not found.");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => return Err(e.into()), // Other IO error
|
||||
|
|
@ -111,7 +109,7 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn StdError>> {
|
|||
let key_file_buf = match key_file_res {
|
||||
Ok(b) => b,
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
log_message("TLS key 'certs/cert.key' not found.");
|
||||
log!("TLS key 'certs/cert.key' not found.");
|
||||
return Ok(None);
|
||||
}
|
||||
Err(e) => return Err(e.into()), // Other IO error
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
data::communication::{CommunicationType, CommunicationValue},
|
||||
gui::log_panel::log_message,
|
||||
log,
|
||||
};
|
||||
use actix::{Actor, ActorContext, AsyncContext, StreamHandler};
|
||||
use actix_web_actors::ws;
|
||||
|
|
@ -57,21 +57,21 @@ impl Actor for WsSession {
|
|||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.start_heartbeat(ctx);
|
||||
|
||||
log_message(format!("WebSocket session started for path: {}", self.path));
|
||||
log!("WebSocket session started for path: {}", self.path);
|
||||
|
||||
if self.path.starts_with("/ws/users/") {
|
||||
log_message(format!("UserConnection handling is not yet implemented.",));
|
||||
log!("UserConnection handling is not yet implemented.");
|
||||
} else if self.path.starts_with("/ws/community/") {
|
||||
let community_id = self.path.split('/').nth(3).unwrap_or_default();
|
||||
log_message(format!(
|
||||
log!(
|
||||
"CommunityConnection handling for {} is not yet implemented.",
|
||||
community_id
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
||||
log_message(format!("WebSocket session stopped for path: {}", self.path));
|
||||
log!("WebSocket session stopped for path: {}", self.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::log;
|
||||
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
||||
use crate::users::user_profile::UserProfile;
|
||||
use crate::util::crypto_helper::{self, public_key_to_base64};
|
||||
|
|
@ -113,7 +113,7 @@ pub async fn create_user(username: &str) -> (Option<UserProfile>, Option<String>
|
|||
}
|
||||
*SHUTDOWN.write().await = true;
|
||||
*RELOAD.write().await = true;
|
||||
log_message("Created User");
|
||||
log!("Created User");
|
||||
save_file(
|
||||
"",
|
||||
&format!("{}.tu", username),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use json::{self, JsonValue, array, object};
|
|||
use std::fs::{self};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::log;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum MessageState {
|
||||
|
|
@ -58,7 +58,7 @@ pub fn add_message(
|
|||
);
|
||||
|
||||
if let Err(e) = fs::create_dir_all(&user_dir) {
|
||||
log_message(format!("Failed to create chat directory: {}", e));
|
||||
log!("Failed to create chat directory: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ pub fn add_message(
|
|||
break;
|
||||
}
|
||||
} else {
|
||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
||||
log!("Failed to parse existing JSON file: {}", file_name);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -85,7 +85,7 @@ pub fn add_message(
|
|||
|
||||
chunk_index += 1;
|
||||
if chunk_index > 1000 {
|
||||
log_message(format!("Too many message chunks. Aborting add."));
|
||||
log!("Too many message chunks. Aborting add.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ pub fn add_message(
|
|||
};
|
||||
|
||||
if let Err(e) = message_chunk.push(json_obj) {
|
||||
log_message(format!("Failed to push new message into JSON array: {}", e));
|
||||
log!("Failed to push new message into JSON array: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use uuid::Uuid;
|
|||
use walkdir::WalkDir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::gui::log_panel::log_message;
|
||||
use crate::log;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_directory(path: &str) -> bool {
|
||||
|
|
@ -23,11 +23,11 @@ fn delete_dir_recursive(directory: &Path) -> bool {
|
|||
return false;
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(directory) {
|
||||
log_message(format!(
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't delete directory {}: {}",
|
||||
directory.display(),
|
||||
e,
|
||||
));
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
|
@ -97,7 +97,7 @@ pub fn load_file(path: &str, name: &str) -> String {
|
|||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log_message(format!("[IMPORTANT] Couldn't create directories: {}", e));
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return String::new();
|
||||
}
|
||||
return String::new();
|
||||
|
|
@ -105,7 +105,7 @@ pub fn load_file(path: &str, name: &str) -> String {
|
|||
|
||||
if !file_path.exists() {
|
||||
if let Err(e) = File::create(&file_path) {
|
||||
log_message(format!("[IMPORTANT] Couldn't create file: {}", e));
|
||||
log!("[IMPORTANT] Couldn't create file: {}", e);
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
|
@ -130,17 +130,17 @@ pub fn save_file(path: &str, name: &str, value: &str) {
|
|||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log_message(format!("[IMPORTANT] Couldn't create directories: {}", e));
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = fs::write(&file_path, value) {
|
||||
log_message(format!(
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't write file {}: {}",
|
||||
file_path.display(),
|
||||
e
|
||||
));
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +231,7 @@ pub async fn download_zip(url: &str, zip_path: &Path) -> Result<(), Box<dyn std:
|
|||
|
||||
if !response.status().is_success() {
|
||||
let err_msg = format!("Failed to download file: Status {}", response.status());
|
||||
log_message(err_msg.clone());
|
||||
log!("{}", err_msg.clone());
|
||||
return Err(err_msg.into());
|
||||
}
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ fn extract_zip_contents_to_folder(
|
|||
}
|
||||
}
|
||||
|
||||
log_message("Extracting directly (no single root folder detected).");
|
||||
log!("Extracting directly (no single root folder detected).");
|
||||
let _ = fs::remove_dir_all(target_dir);
|
||||
fs::rename(&staging_dir, target_dir)?;
|
||||
|
||||
|
|
@ -323,14 +323,14 @@ fn extract_zip_contents_to_folder(
|
|||
|
||||
#[allow(dead_code)]
|
||||
pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
||||
log_message("Downloading ZIP file...");
|
||||
log!("Downloading ZIP file...");
|
||||
let base_dir = PathBuf::from(get_directory());
|
||||
let zip_filename = format!("{}.zip", Uuid::new_v4());
|
||||
let zip_path = base_dir.join(&zip_filename);
|
||||
let target_dir = base_dir.join(as_name);
|
||||
|
||||
if let Err(e) = download_zip(url, &zip_path).await {
|
||||
log_message(format!("Error downloading file: {}", e));
|
||||
log!("Error downloading file: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -341,18 +341,14 @@ pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
|||
let successful = match extract_result {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
log_message(format!("Error during ZIP extraction: {}", e));
|
||||
log!("Error during ZIP extraction: {}", e);
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = tokio::fs::remove_file(&zip_path).await {
|
||||
log_message(format!(
|
||||
"Error cleaning up ZIP file {}: {}",
|
||||
zip_path.display(),
|
||||
e
|
||||
));
|
||||
log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e);
|
||||
} else if successful {
|
||||
log_message("Downloaded and extracted ZIP file successfully.");
|
||||
log!("Downloaded and extracted ZIP file successfully.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
298
src/util/logger.rs
Normal file
298
src/util/logger.rs
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
use std::{
|
||||
fs::{self, OpenOptions},
|
||||
io::Write,
|
||||
path::Path,
|
||||
sync::{OnceLock, mpsc},
|
||||
thread,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use ratatui::style::Color;
|
||||
|
||||
use crate::{APP_STATE, gui::elements::log_card::UiLogEntry, langu::language_manager};
|
||||
|
||||
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[allow(unused)]
|
||||
pub enum PrintType {
|
||||
Call,
|
||||
Client,
|
||||
Iota,
|
||||
Omikron,
|
||||
Omega,
|
||||
General,
|
||||
}
|
||||
|
||||
struct LogMessage {
|
||||
timestamp_ms: u128,
|
||||
prefix: &'static str,
|
||||
kind: PrintType,
|
||||
is_error: bool,
|
||||
|
||||
translation_key: Option<String>,
|
||||
format_args: Vec<String>,
|
||||
|
||||
message: Option<String>,
|
||||
}
|
||||
|
||||
pub fn startup() {
|
||||
let (tx, rx) = mpsc::channel::<LogMessage>();
|
||||
LOGGER.set(tx).expect("Logger already initialized");
|
||||
|
||||
thread::spawn(move || {
|
||||
let log_dir = Path::new("logs");
|
||||
fs::create_dir_all(log_dir).expect("Failed to create log directory");
|
||||
|
||||
let start_ts = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
let path = log_dir.join(format!("log_{}.txt", start_ts));
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path)
|
||||
.expect("Failed to open log file");
|
||||
|
||||
for msg in rx {
|
||||
let resolved_message = if let Some(key) = msg.translation_key {
|
||||
let args: Vec<&str> = msg.format_args.iter().map(|s| s.as_str()).collect();
|
||||
language_manager::format(&key, &args)
|
||||
} else {
|
||||
msg.message.unwrap_or_default()
|
||||
};
|
||||
|
||||
let ts = fixed_box(&msg.timestamp_ms.to_string(), 13);
|
||||
|
||||
let line = format!("{} {}", msg.prefix, resolved_message);
|
||||
|
||||
let _ = writeln!(file, "{} {}", ts, line);
|
||||
|
||||
let color = colorize(msg.kind, msg.is_error);
|
||||
|
||||
let ui_entry = UiLogEntry {
|
||||
line: line.clone(),
|
||||
color,
|
||||
};
|
||||
|
||||
{
|
||||
let mut state = APP_STATE.lock().unwrap();
|
||||
state.push_log(ui_entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn colorize(kind: PrintType, is_error: bool) -> Color {
|
||||
if is_error {
|
||||
return Color::Red;
|
||||
}
|
||||
|
||||
match kind {
|
||||
PrintType::Call => Color::Magenta,
|
||||
PrintType::Client => Color::Green,
|
||||
PrintType::Iota => Color::Yellow,
|
||||
PrintType::Omikron => Color::Blue,
|
||||
PrintType::Omega => Color::Cyan,
|
||||
PrintType::General => Color::White,
|
||||
}
|
||||
}
|
||||
|
||||
fn fixed_box(content: &str, width: usize) -> String {
|
||||
let s: String = content.chars().take(width).collect();
|
||||
let len = s.chars().count();
|
||||
if len < width {
|
||||
format!("[{}{}]", " ".repeat(width - len), s)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_internal_translated(
|
||||
kind: PrintType,
|
||||
prefix: &'static str,
|
||||
is_error: bool,
|
||||
key: &str,
|
||||
args: Vec<String>,
|
||||
) {
|
||||
if let Some(tx) = LOGGER.get() {
|
||||
let _ = tx.send(LogMessage {
|
||||
timestamp_ms: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
prefix,
|
||||
kind,
|
||||
is_error,
|
||||
translation_key: Some(key.to_string()),
|
||||
format_args: args,
|
||||
message: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log_internal(kind: PrintType, prefix: &'static str, is_error: bool, message: String) {
|
||||
if let Some(tx) = LOGGER.get() {
|
||||
let _ = tx.send(LogMessage {
|
||||
timestamp_ms: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis(),
|
||||
prefix,
|
||||
kind,
|
||||
is_error,
|
||||
translation_key: None,
|
||||
format_args: Vec::new(),
|
||||
message: Some(message),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
use crate::data::communication::CommunicationValue;
|
||||
use json::JsonValue;
|
||||
pub fn log_cv_internal(cv: &CommunicationValue, print_type: Option<PrintType>) {
|
||||
let formatted = format_cv(cv);
|
||||
|
||||
log_internal(
|
||||
print_type.unwrap_or(PrintType::General),
|
||||
"",
|
||||
false,
|
||||
formatted,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn format_cv(cv: &CommunicationValue) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
let sender = cv.get_sender();
|
||||
let receiver = cv.get_receiver();
|
||||
|
||||
if sender > 0 && receiver > 0 {
|
||||
parts.push(format!("{} > {}", sender, receiver));
|
||||
} else if sender > 0 {
|
||||
parts.push(format!("{}", sender));
|
||||
} else if receiver > 0 {
|
||||
parts.push(format!("> {}", receiver));
|
||||
}
|
||||
|
||||
let comm_type = cv.get_type().to_string();
|
||||
parts.push(format!("{}", comm_type));
|
||||
|
||||
let mut data_parts = Vec::new();
|
||||
if let JsonValue::Object(data) = &cv.clone().to_json()["data"] {
|
||||
for (key, value) in data.iter() {
|
||||
let val_string = match value {
|
||||
JsonValue::String(s) => s.clone(),
|
||||
_ => value.dump(),
|
||||
};
|
||||
|
||||
data_parts.push(format!("{} {}", key, val_string));
|
||||
}
|
||||
}
|
||||
|
||||
if !data_parts.is_empty() {
|
||||
parts.push(format!("{}", data_parts.join(", ")));
|
||||
}
|
||||
|
||||
parts.join(": ")
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! log_cv {
|
||||
($kind:expr, $cv:expr) => {
|
||||
$crate::util::logger::log_cv_internal(&$cv, Some($kind))
|
||||
};
|
||||
($cv:expr) => {
|
||||
$crate::util::logger::log_cv_internal(&$cv, None)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! log_t {
|
||||
($key:expr) => {
|
||||
$crate::util::logger::log_internal_translated(
|
||||
$crate::util::logger::PrintType::General,
|
||||
"",
|
||||
false,
|
||||
$key,
|
||||
vec![]
|
||||
)
|
||||
};
|
||||
|
||||
($key:expr, $($arg:expr),+) => {
|
||||
$crate::util::logger::log_internal_translated(
|
||||
$crate::util::logger::PrintType::General,
|
||||
"",
|
||||
false,
|
||||
$key,
|
||||
vec![$($arg),+]
|
||||
)
|
||||
};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! log_t_err {
|
||||
($key:expr) => {
|
||||
$crate::util::logger::log_internal_translated(
|
||||
$crate::util::logger::PrintType::General,
|
||||
">>",
|
||||
true,
|
||||
$key,
|
||||
vec![]
|
||||
)
|
||||
};
|
||||
|
||||
($key:expr, $($arg:expr),+) => {
|
||||
$crate::util::logger::log_internal_translated(
|
||||
$crate::util::logger::PrintType::General,
|
||||
">>",
|
||||
true,
|
||||
$key,
|
||||
vec![$($arg.to_string()),+]
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Log a general informational message.
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal($crate::util::logger::PrintType::General, "", false, format!($($arg)*))
|
||||
};
|
||||
}
|
||||
/// Log an inbound message (`>`).
|
||||
#[macro_export]
|
||||
macro_rules! log_in {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
$crate::util::logger::PrintType::General,
|
||||
">",
|
||||
false,
|
||||
format!($($arg)*)
|
||||
)
|
||||
};
|
||||
}
|
||||
/// Log an outbound message (`<`).
|
||||
#[macro_export]
|
||||
macro_rules! log_out {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
$crate::util::logger::PrintType::General,
|
||||
"<",
|
||||
false,
|
||||
format!($($arg)*)
|
||||
)
|
||||
};
|
||||
}
|
||||
/// Log an error message (`>>`).
|
||||
#[macro_export]
|
||||
macro_rules! log_err {
|
||||
($($arg:tt)*) => {
|
||||
$crate::util::logger::log_internal(
|
||||
$crate::util::logger::PrintType::General,
|
||||
">>",
|
||||
true,
|
||||
format!($($arg)*)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
@ -4,3 +4,4 @@ pub mod config_util;
|
|||
pub mod crypto_helper;
|
||||
pub mod crypto_util;
|
||||
pub mod file_util;
|
||||
pub mod logger;
|
||||
|
|
|
|||
Loading…
Reference in a new issue