[WIP] New UI Logic
This commit is contained in:
parent
b8e18490f3
commit
6db4e806f8
26 changed files with 815 additions and 674 deletions
13
logs/log_1771405721.txt
Normal file
13
logs/log_1771405721.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
1771405721036 IOTA ID: 0
|
||||||
|
1771405721036 User IDS:
|
||||||
|
1771405721046 Community IDS:
|
||||||
|
1771405721047 TLS certificate 'certs/cert.pem' not found.
|
||||||
|
1771405721047 HTTP Server running on 0.0.0.0:1984
|
||||||
|
1771405721048 Communities active on ws://10.209.226.34:1984/community/...
|
||||||
|
1771405721048 Downloading ZIP file...
|
||||||
|
1771405721507 Failed to download file: Status 404 Not Found
|
||||||
|
1771405721507 Error downloading file: Failed to download file: Status 404 Not Found
|
||||||
|
1771405721507 IOTA_REGISTER_NEW
|
||||||
|
1771405721512 OMIKRON_CONNECTING
|
||||||
|
1771405721987 OMIKRON_CONNECTION_SUCCESS
|
||||||
|
1771405731879 Web Server shutdown complete.
|
||||||
|
|
@ -160,7 +160,7 @@ impl CommunityConnection {
|
||||||
|
|
||||||
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
|
let user_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) {
|
||||||
Some(key) => key,
|
Some(key) => key,
|
||||||
None => {
|
__ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -178,7 +178,7 @@ impl CommunityConnection {
|
||||||
|
|
||||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||||
Some(secret) => secret,
|
Some(secret) => secret,
|
||||||
None => {
|
_ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -223,7 +223,7 @@ impl CommunityConnection {
|
||||||
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
|
async fn handle_challenge_response(self: Arc<Self>, cv: CommunicationValue) {
|
||||||
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
|
let client_challenge_response_b64 = match cv.get_data(DataTypes::challenge) {
|
||||||
Some(data) => data.to_string(),
|
Some(data) => data.to_string(),
|
||||||
None => {
|
_ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
|
|
@ -273,7 +273,7 @@ impl CommunityConnection {
|
||||||
|
|
||||||
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
let shared_secret = match community_private_key.to_diffie_hellman(&user_pub_key) {
|
||||||
Some(secret) => secret,
|
Some(secret) => secret,
|
||||||
None => {
|
_ => {
|
||||||
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
self.send_error_response(&cv.get_id(), CommunicationType::error)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::communities::community::{self, Community};
|
use crate::communities::community::{self, Community};
|
||||||
use crate::gui::log_panel;
|
use crate::log;
|
||||||
use crate::util::file_util;
|
use crate::util::file_util;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
@ -35,7 +35,7 @@ pub async fn load_communities() {
|
||||||
if let Some(community) = community::load(&name).await {
|
if let Some(community) = community::load(&name).await {
|
||||||
add_community(community).await;
|
add_community(community).await;
|
||||||
} else {
|
} 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,
|
interactables::interactable::Interactable,
|
||||||
},
|
},
|
||||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||||
gui::log_panel::log_message,
|
log,
|
||||||
util::file_util::{get_children, load_file, save_file},
|
util::file_util::{get_children, load_file, save_file},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
@ -37,7 +37,7 @@ impl TextChat {
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = fs::create_dir_all(user_dir) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,16 +56,15 @@ impl TextChat {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
log!("Failed to parse existing JSON file: {}", file_name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// New file, use empty array
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk_index += 1;
|
chunk_index += 1;
|
||||||
if chunk_index > 1000 {
|
if chunk_index > 1000 {
|
||||||
log_message(format!("Too many message chunks. Aborting add."));
|
log!("Too many message chunks. Aborting add.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,12 +76,12 @@ impl TextChat {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = message_chunk.push(json_obj) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_name = format!("msgs_{}.json", chunk_index);
|
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());
|
save_file(&user_dir, &file_name, &message_chunk.dump());
|
||||||
}
|
}
|
||||||
pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue {
|
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 json::{JsonValue, object};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub logs: VecDeque<String>,
|
pub logs: VecDeque<UiLogEntry>,
|
||||||
pub cpu: Vec<(f64, f64)>,
|
pub cpu: Vec<(f64, f64)>,
|
||||||
pub ram: Vec<(f64, f64)>,
|
pub ram: Vec<(f64, f64)>,
|
||||||
pub ping: 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 {
|
if self.logs.len() >= MAX_LOGS {
|
||||||
self.logs.pop_front();
|
self.logs.pop_front();
|
||||||
}
|
}
|
||||||
self.logs.push_back(msg);
|
self.logs.push_back(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_logs(&self) -> &VecDeque<UiLogEntry> {
|
||||||
|
&self.logs
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_cpu(&mut self, pt: (f64, f64)) {
|
pub fn push_cpu(&mut self, pt: (f64, f64)) {
|
||||||
self.cpu.push(pt);
|
self.cpu.push(pt);
|
||||||
if self.cpu.len() > MAX_POINTS {
|
if self.cpu.len() > MAX_POINTS {
|
||||||
|
|
@ -112,28 +116,23 @@ impl AppState {
|
||||||
let len = data.len();
|
let len = data.len();
|
||||||
|
|
||||||
if len >= width_usize {
|
if len >= width_usize {
|
||||||
// Trim data to fit
|
|
||||||
data[len - width_usize..].to_vec()
|
data[len - width_usize..].to_vec()
|
||||||
} else {
|
} else {
|
||||||
let mut result = Vec::with_capacity(width_usize);
|
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 dx = 1.0;
|
||||||
let pad_len = width_usize - len;
|
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
|
let start_x = data
|
||||||
.first()
|
.first()
|
||||||
.map(|(x, _)| x - (dx * pad_len as f64))
|
.map(|(x, _)| x - (dx * pad_len as f64))
|
||||||
.unwrap_or(0.0);
|
.unwrap_or(0.0);
|
||||||
let _ = data.first().map(|(_, y)| *y).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 {
|
for i in 0..pad_len {
|
||||||
result.push((start_x + i as f64 * dx, -1 as f64));
|
result.push((start_x + i as f64 * dx, -1 as f64));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then append the real data
|
|
||||||
result.extend_from_slice(data);
|
result.extend_from_slice(data);
|
||||||
result
|
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 elements {
|
||||||
pub mod log_panel;
|
pub mod elements;
|
||||||
pub mod settings_panel;
|
pub mod log_card;
|
||||||
pub mod widgets {
|
|
||||||
pub mod betterblock;
|
|
||||||
}
|
}
|
||||||
pub mod input_handler;
|
pub mod screens {
|
||||||
pub mod tui;
|
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::community_manager;
|
||||||
use crate::communities::interactables::registry;
|
use crate::communities::interactables::registry;
|
||||||
use crate::gui::app_state::AppState;
|
use crate::gui::app_state::AppState;
|
||||||
use crate::gui::input_handler;
|
use crate::gui::ui;
|
||||||
use crate::gui::log_panel;
|
|
||||||
use crate::gui::log_panel::{log_message, log_message_trans};
|
|
||||||
use crate::gui::tui;
|
|
||||||
use crate::langu::language_creator;
|
use crate::langu::language_creator;
|
||||||
use crate::langu::language_manager::format;
|
|
||||||
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
use crate::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
||||||
use crate::server::server::start;
|
use crate::server::server::start;
|
||||||
use crate::terms::consent_state;
|
use crate::terms::consent_state;
|
||||||
|
|
@ -33,6 +29,7 @@ use crate::users::user_manager;
|
||||||
use crate::util::config_util::CONFIG;
|
use crate::util::config_util::CONFIG;
|
||||||
use crate::util::file_util::download_and_extract_zip;
|
use crate::util::file_util::download_and_extract_zip;
|
||||||
use crate::util::file_util::has_dir;
|
use crate::util::file_util::has_dir;
|
||||||
|
use crate::util::logger;
|
||||||
|
|
||||||
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
pub static APP_STATE: LazyLock<Arc<Mutex<AppState>>> =
|
||||||
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
|
LazyLock::new(|| Arc::new(Mutex::new(AppState::new())));
|
||||||
|
|
@ -48,6 +45,9 @@ async fn main() {
|
||||||
*RELOAD.write().await = false;
|
*RELOAD.write().await = false;
|
||||||
*SHUTDOWN.write().await = false;
|
*SHUTDOWN.write().await = false;
|
||||||
|
|
||||||
|
let tui = ui::UI::new(100, 100);
|
||||||
|
tokio::spawn(async move { tui.render() });
|
||||||
|
|
||||||
// EULA
|
// EULA
|
||||||
let (tos, pp) = consent_state::ConsentManager::check().await;
|
let (tos, pp) = consent_state::ConsentManager::check().await;
|
||||||
if !tos {
|
if !tos {
|
||||||
|
|
@ -71,16 +71,14 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
log_panel::setup();
|
logger::startup();
|
||||||
tui::start_tui();
|
|
||||||
input_handler::setup_input_handler();
|
|
||||||
|
|
||||||
// BASIC CONFIGURATION
|
// BASIC CONFIGURATION
|
||||||
&CONFIG.write().await.load();
|
&CONFIG.write().await.load();
|
||||||
|
|
||||||
// USER MANAGEMENT
|
// USER MANAGEMENT
|
||||||
if let Err(_) = user_manager::load_users().await {
|
if let Err(_) = user_manager::load_users().await {
|
||||||
log_message_trans("user_load_failed");
|
log_t!("user_load_failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sb = "".to_string();
|
let mut sb = "".to_string();
|
||||||
|
|
@ -94,11 +92,11 @@ async fn main() {
|
||||||
sb.remove(0);
|
sb.remove(0);
|
||||||
sb = sb + ",";
|
sb = sb + ",";
|
||||||
}
|
}
|
||||||
log_message(format!(
|
log!(
|
||||||
"IOTA ID: {}",
|
"IOTA ID: {}",
|
||||||
CONFIG.read().await.get_iota_id().to_string()
|
CONFIG.read().await.get_iota_id().to_string()
|
||||||
));
|
);
|
||||||
log_message(format!("User IDS: {}", sb));
|
log!("User IDS: {}", sb);
|
||||||
|
|
||||||
// COMMUNITY MANAGEMENT
|
// COMMUNITY MANAGEMENT
|
||||||
registry::load_interactables().await;
|
registry::load_interactables().await;
|
||||||
|
|
@ -113,7 +111,7 @@ async fn main() {
|
||||||
sb1.remove(0);
|
sb1.remove(0);
|
||||||
sb1 = sb1 + ",";
|
sb1 = sb1 + ",";
|
||||||
}
|
}
|
||||||
log_message(format!("Community IDS: {}", sb1));
|
log!("Community IDS: {}", sb1);
|
||||||
let port = CONFIG.read().await.get_port();
|
let port = CONFIG.read().await.get_port();
|
||||||
let mut ip = "0.0.0.0".to_string();
|
let mut ip = "0.0.0.0".to_string();
|
||||||
for iface in pnet::datalink::interfaces() {
|
for iface in pnet::datalink::interfaces() {
|
||||||
|
|
@ -127,12 +125,12 @@ async fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start(port).await {
|
if start(port).await {
|
||||||
log_message(format("community_active", &[&ip, &port.to_string()]));
|
log_t!("community_active", ip, port.to_string());
|
||||||
} else {
|
} else {
|
||||||
if port < 1024 {
|
if port < 1024 {
|
||||||
log_message(format("community_start_error_admin", &[&port.to_string()]));
|
log_t!("community_start_error_admin", port.to_string());
|
||||||
} else {
|
} else {
|
||||||
log_message(format("community_start_error", &[&port.to_string()]));
|
log_t!("community_start_error", port.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !has_dir("web") {
|
if !has_dir("web") {
|
||||||
|
|
@ -150,7 +148,7 @@ async fn main() {
|
||||||
omikron.connect().await;
|
omikron.connect().await;
|
||||||
let mut omikron_connection = OMIKRON_CONNECTION.write().await;
|
let mut omikron_connection = OMIKRON_CONNECTION.write().await;
|
||||||
*omikron_connection = Some(omikron.clone());
|
*omikron_connection = Some(omikron.clone());
|
||||||
log_message_trans("setup_completed");
|
log_t!("setup_completed");
|
||||||
loop {
|
loop {
|
||||||
if *SHUTDOWN.read().await {
|
if *SHUTDOWN.read().await {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
use crate::gui::log_panel::{log_cv, log_message_format};
|
|
||||||
use crate::users::contact::Contact;
|
use crate::users::contact::Contact;
|
||||||
use crate::users::user_community_util::UserCommunityUtil;
|
use crate::users::user_community_util::UserCommunityUtil;
|
||||||
use crate::util::chat_files::{MessageState, change_message_state};
|
use crate::util::chat_files::{MessageState, change_message_state};
|
||||||
use crate::util::chats_util::{get_user, mod_user};
|
use crate::util::chats_util::{get_user, mod_user};
|
||||||
use crate::util::crypto_util::{DataFormat, SecurePayload};
|
use crate::util::crypto_util::{DataFormat, SecurePayload};
|
||||||
use crate::util::file_util::{get_children, load_file, save_file};
|
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::util::{chat_files, chats_util};
|
||||||
use crate::{ACTIVE_TASKS, SHUTDOWN};
|
use crate::{ACTIVE_TASKS, SHUTDOWN, log, log_cv, log_t};
|
||||||
use crate::{
|
use crate::{
|
||||||
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
data::communication::{CommunicationType, CommunicationValue, DataTypes},
|
||||||
gui::log_panel::{log_message, log_message_trans},
|
|
||||||
util::{config_util::CONFIG, crypto_helper},
|
util::{config_util::CONFIG, crypto_helper},
|
||||||
};
|
};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
|
@ -26,6 +25,7 @@ use tokio::time::{Duration, Instant, sleep};
|
||||||
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
|
||||||
use tungstenite::Utf8Bytes;
|
use tungstenite::Utf8Bytes;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use warp::filters::log::log;
|
||||||
|
|
||||||
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
|
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
|
||||||
LazyLock::new(|| Arc::new(RwLock::new(None)));
|
LazyLock::new(|| Arc::new(RwLock::new(None)));
|
||||||
|
|
@ -66,7 +66,7 @@ impl OmikronConnection {
|
||||||
drop(conf);
|
drop(conf);
|
||||||
|
|
||||||
if iota_id == 0 || public_key.is_none() || private_key.is_none() {
|
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 key_pair = crypto_helper::generate_keypair();
|
||||||
let public_key_base64 = crypto_helper::public_key_to_base64(&key_pair.public);
|
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);
|
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.change("iota_id", iota_json.clone());
|
||||||
conf_write.update();
|
conf_write.update();
|
||||||
drop(conf_write);
|
drop(conf_write);
|
||||||
log_message(format!("Registered with Iota-ID: {}", iota_id));
|
log!("Registered with Iota-ID: {}", iota_id);
|
||||||
}
|
}
|
||||||
Err(timeout) => {
|
Err(timeout) => {
|
||||||
log_message(timeout);
|
log!("{}", timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +121,7 @@ impl OmikronConnection {
|
||||||
if self.is_connected().await {
|
if self.is_connected().await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
log_message_trans("omikron_connecting");
|
log_t!("omikron_connecting");
|
||||||
|
|
||||||
let conf = CONFIG.read().await;
|
let conf = CONFIG.read().await;
|
||||||
let addr = conf
|
let addr = conf
|
||||||
|
|
@ -130,11 +130,11 @@ impl OmikronConnection {
|
||||||
.unwrap_or("wss://app.tensamin.net/ws/iota/");
|
.unwrap_or("wss://app.tensamin.net/ws/iota/");
|
||||||
let stream_res = connect_async(addr).await;
|
let stream_res = connect_async(addr).await;
|
||||||
if let Err(e) = stream_res {
|
if let Err(e) = stream_res {
|
||||||
log_message(format!("con error {}", e.to_string()));
|
log!("con error {}", e.to_string());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let (stream, _) = stream_res.unwrap();
|
let (stream, _) = stream_res.unwrap();
|
||||||
log_message_trans("omikron_connection_success");
|
log_t!("omikron_connection_success");
|
||||||
|
|
||||||
let (write_half, read_half) = stream.split();
|
let (write_half, read_half) = stream.split();
|
||||||
|
|
||||||
|
|
@ -196,7 +196,7 @@ impl OmikronConnection {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*is_connected_out.lock().await = false;
|
*is_connected_out.lock().await = false;
|
||||||
log_message("Connection closed.");
|
log!("Connection closed.");
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
ACTIVE_TASKS
|
ACTIVE_TASKS
|
||||||
|
|
@ -219,7 +219,7 @@ impl OmikronConnection {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
match msg {
|
match msg {
|
||||||
Ok(Message::Close(Some(frame))) => {
|
Ok(Message::Close(Some(frame))) => {
|
||||||
log_message(format!("[Omikron] Closed: {:?}", frame));
|
log!("[Omikron] Closed: {:?}", frame);
|
||||||
*is_connected.lock().await = false;
|
*is_connected.lock().await = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +271,7 @@ impl OmikronConnection {
|
||||||
|
|
||||||
self.send_message(&response).await;
|
self.send_message(&response).await;
|
||||||
} else {
|
} else {
|
||||||
log_message("Failed to decrypt challenge");
|
log!("Failed to decrypt challenge");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -286,9 +286,8 @@ impl OmikronConnection {
|
||||||
let mut conf = CONFIG.write().await;
|
let mut conf = CONFIG.write().await;
|
||||||
conf.change("iota_id", JsonValue::Number(iota_id.into()));
|
conf.change("iota_id", JsonValue::Number(iota_id.into()));
|
||||||
conf.update();
|
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 =
|
let login_message =
|
||||||
CommunicationValue::new(CommunicationType::identification)
|
CommunicationValue::new(CommunicationType::identification)
|
||||||
.add_data(
|
.add_data(
|
||||||
|
|
@ -301,13 +300,13 @@ impl OmikronConnection {
|
||||||
self_clone.send_message(&login_message).await;
|
self_clone.send_message(&login_message).await;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log_message("Iota registration failed.");
|
log("Iota registration failed.");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if cv.is_type(CommunicationType::identification_response) {
|
if cv.is_type(CommunicationType::identification_response) {
|
||||||
if let Some(accepted) = cv.get_data(DataTypes::accepted) {
|
if let Some(accepted) = cv.get_data(DataTypes::accepted) {
|
||||||
log_message(format!("Omikron connected: {}", accepted.to_string()));
|
log!("Omikron connected: {}", accepted.to_string());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -315,7 +314,7 @@ impl OmikronConnection {
|
||||||
// ************************************************ //
|
// ************************************************ //
|
||||||
// Direct messages //
|
// Direct messages //
|
||||||
// ************************************************ //
|
// ************************************************ //
|
||||||
log_cv(&cv);
|
log_cv!(PrintType::General, &cv);
|
||||||
if let Some((_, y)) = waiting.remove(&cv.get_id()) {
|
if let Some((_, y)) = waiting.remove(&cv.get_id()) {
|
||||||
y(cv);
|
y(cv);
|
||||||
return;
|
return;
|
||||||
|
|
@ -699,7 +698,7 @@ impl OmikronConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_message(format!("[Omikron] Error: {}", e));
|
log!("Omikron] Error: {}", e);
|
||||||
*is_connected.lock().await = false;
|
*is_connected.lock().await = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -721,17 +720,17 @@ impl OmikronConnection {
|
||||||
Ok(_) => match writer.flush().await {
|
Ok(_) => match writer.flush().await {
|
||||||
Ok(_) => return,
|
Ok(_) => return,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_message_format("send_message_failed", &[&e.to_string()]);
|
log_t!("send_message_failed", e.to_string());
|
||||||
*connected.lock().await = false;
|
*connected.lock().await = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_message_format("send_message_failed", &[&e.to_string()]);
|
log_t!("send_message_failed", e.to_string());
|
||||||
*connected.lock().await = false;
|
*connected.lock().await = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log_message_format("send_message_failed", &["Immutable Writer"]);
|
log_t!("send_message_failed", "Immutable Writer".to_string());
|
||||||
*connected.lock().await = false;
|
*connected.lock().await = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -750,7 +749,7 @@ impl OmikronConnection {
|
||||||
let inner_tx = task_tx.clone();
|
let inner_tx = task_tx.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = inner_tx.send(response_cv).await {
|
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()) {
|
let username = match payload.get("username").and_then(|v| v.as_str()) {
|
||||||
Some(u) => u,
|
Some(u) => u,
|
||||||
None => return error(),
|
_ => return error(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let (Some(user), Some(_)) = crate::users::user_manager::create_user(username).await {
|
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::api::api_config;
|
||||||
use crate::server::socket::WsSession;
|
use crate::server::socket::WsSession;
|
||||||
use crate::server::web_path_parser;
|
use crate::server::web_path_parser;
|
||||||
use crate::util::file_util::load_file_buf;
|
use crate::util::file_util::load_file_buf;
|
||||||
use crate::{ACTIVE_TASKS, SHUTDOWN};
|
use crate::{ACTIVE_TASKS, SHUTDOWN};
|
||||||
|
|
||||||
use actix_web::{App, Error, HttpRequest, HttpServer, Responder, dev::ServerHandle, web};
|
use actix_web::{App, Error, HttpRequest, HttpServer, Responder, dev::ServerHandle, web};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
|
|
||||||
use rustls::ServerConfig;
|
use rustls::ServerConfig;
|
||||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -20,7 +18,7 @@ use std::{
|
||||||
|
|
||||||
async fn ws_handler(req: HttpRequest, stream: web::Payload) -> Result<impl Responder, Error> {
|
async fn ws_handler(req: HttpRequest, stream: web::Payload) -> Result<impl Responder, Error> {
|
||||||
let path = req.path().to_string();
|
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);
|
let session = WsSession::new(path);
|
||||||
ws::start(session, &req, stream)
|
ws::start(session, &req, stream)
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +31,7 @@ pub async fn start(port: u16) -> bool {
|
||||||
let _ = tokio::spawn(async move {
|
let _ = tokio::spawn(async move {
|
||||||
let server = match load_tls_config() {
|
let server = match load_tls_config() {
|
||||||
Ok(Some(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();
|
let _config = (*tls_config).clone();
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
@ -46,8 +44,8 @@ pub async fn start(port: u16) -> bool {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(_) => {
|
||||||
log_message(format!("HTTP Server running on 0.0.0.0:{}", port));
|
log!("HTTP Server running on 0.0.0.0:{}", port);
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(false))
|
.app_data(web::Data::new(false))
|
||||||
|
|
@ -60,7 +58,7 @@ pub async fn start(port: u16) -> bool {
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_message(format!("TLS config error: {}", e));
|
log!("TLS config error: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -71,7 +69,7 @@ pub async fn start(port: u16) -> bool {
|
||||||
ACTIVE_TASKS.lock().unwrap().push("WebServer".into());
|
ACTIVE_TASKS.lock().unwrap().push("WebServer".into());
|
||||||
server.await.unwrap();
|
server.await.unwrap();
|
||||||
ACTIVE_TASKS.lock().unwrap().retain(|t| t != "WebServer");
|
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 {
|
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) {
|
async fn wait_for_shutdown(server_handle: ServerHandle) {
|
||||||
loop {
|
loop {
|
||||||
if *SHUTDOWN.read().await {
|
if *SHUTDOWN.read().await {
|
||||||
log_message("Shutdown signal received.");
|
log!("Shutdown signal received.");
|
||||||
server_handle.stop(true).await;
|
server_handle.stop(true).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -102,7 +100,7 @@ fn load_tls_config() -> Result<Option<Arc<ServerConfig>>, Box<dyn StdError>> {
|
||||||
let cert_file_buf = match cert_file_res {
|
let cert_file_buf = match cert_file_res {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()), // Other IO error
|
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 {
|
let key_file_buf = match key_file_res {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
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);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Err(e) => return Err(e.into()), // Other IO error
|
Err(e) => return Err(e.into()), // Other IO error
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
data::communication::{CommunicationType, CommunicationValue},
|
data::communication::{CommunicationType, CommunicationValue},
|
||||||
gui::log_panel::log_message,
|
log,
|
||||||
};
|
};
|
||||||
use actix::{Actor, ActorContext, AsyncContext, StreamHandler};
|
use actix::{Actor, ActorContext, AsyncContext, StreamHandler};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
|
|
@ -57,21 +57,21 @@ impl Actor for WsSession {
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
self.start_heartbeat(ctx);
|
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/") {
|
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/") {
|
} else if self.path.starts_with("/ws/community/") {
|
||||||
let community_id = self.path.split('/').nth(3).unwrap_or_default();
|
let community_id = self.path.split('/').nth(3).unwrap_or_default();
|
||||||
log_message(format!(
|
log!(
|
||||||
"CommunityConnection handling for {} is not yet implemented.",
|
"CommunityConnection handling for {} is not yet implemented.",
|
||||||
community_id
|
community_id
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
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::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::omikron::omikron_connection::{OMIKRON_CONNECTION, OmikronConnection};
|
||||||
use crate::users::user_profile::UserProfile;
|
use crate::users::user_profile::UserProfile;
|
||||||
use crate::util::crypto_helper::{self, public_key_to_base64};
|
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;
|
*SHUTDOWN.write().await = true;
|
||||||
*RELOAD.write().await = true;
|
*RELOAD.write().await = true;
|
||||||
log_message("Created User");
|
log!("Created User");
|
||||||
save_file(
|
save_file(
|
||||||
"",
|
"",
|
||||||
&format!("{}.tu", username),
|
&format!("{}.tu", username),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use json::{self, JsonValue, array, object};
|
||||||
use std::fs::{self};
|
use std::fs::{self};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::gui::log_panel::log_message;
|
use crate::log;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub enum MessageState {
|
pub enum MessageState {
|
||||||
|
|
@ -58,7 +58,7 @@ pub fn add_message(
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(e) = fs::create_dir_all(&user_dir) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ pub fn add_message(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log_message(format!("Failed to parse existing JSON file: {}", file_name));
|
log!("Failed to parse existing JSON file: {}", file_name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
@ -85,7 +85,7 @@ pub fn add_message(
|
||||||
|
|
||||||
chunk_index += 1;
|
chunk_index += 1;
|
||||||
if chunk_index > 1000 {
|
if chunk_index > 1000 {
|
||||||
log_message(format!("Too many message chunks. Aborting add."));
|
log!("Too many message chunks. Aborting add.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +98,7 @@ pub fn add_message(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = message_chunk.push(json_obj) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use uuid::Uuid;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
use crate::gui::log_panel::log_message;
|
use crate::log;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn delete_directory(path: &str) -> bool {
|
pub fn delete_directory(path: &str) -> bool {
|
||||||
|
|
@ -23,11 +23,11 @@ fn delete_dir_recursive(directory: &Path) -> bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if let Err(e) = fs::remove_dir_all(directory) {
|
if let Err(e) = fs::remove_dir_all(directory) {
|
||||||
log_message(format!(
|
log!(
|
||||||
"[IMPORTANT] Couldn't delete directory {}: {}",
|
"[IMPORTANT] Couldn't delete directory {}: {}",
|
||||||
directory.display(),
|
directory.display(),
|
||||||
e,
|
e,
|
||||||
));
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
|
@ -97,7 +97,7 @@ pub fn load_file(path: &str, name: &str) -> String {
|
||||||
|
|
||||||
if !dir.exists() {
|
if !dir.exists() {
|
||||||
if let Err(e) = fs::create_dir_all(&dir) {
|
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();
|
||||||
}
|
}
|
||||||
return String::new();
|
return String::new();
|
||||||
|
|
@ -105,7 +105,7 @@ pub fn load_file(path: &str, name: &str) -> String {
|
||||||
|
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
if let Err(e) = File::create(&file_path) {
|
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();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
@ -130,17 +130,17 @@ pub fn save_file(path: &str, name: &str, value: &str) {
|
||||||
|
|
||||||
if !dir.exists() {
|
if !dir.exists() {
|
||||||
if let Err(e) = fs::create_dir_all(&dir) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = fs::write(&file_path, value) {
|
if let Err(e) = fs::write(&file_path, value) {
|
||||||
log_message(format!(
|
log!(
|
||||||
"[IMPORTANT] Couldn't write file {}: {}",
|
"[IMPORTANT] Couldn't write file {}: {}",
|
||||||
file_path.display(),
|
file_path.display(),
|
||||||
e
|
e
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +231,7 @@ pub async fn download_zip(url: &str, zip_path: &Path) -> Result<(), Box<dyn std:
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
let err_msg = format!("Failed to download file: Status {}", response.status());
|
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());
|
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);
|
let _ = fs::remove_dir_all(target_dir);
|
||||||
fs::rename(&staging_dir, target_dir)?;
|
fs::rename(&staging_dir, target_dir)?;
|
||||||
|
|
||||||
|
|
@ -323,14 +323,14 @@ fn extract_zip_contents_to_folder(
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
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 base_dir = PathBuf::from(get_directory());
|
||||||
let zip_filename = format!("{}.zip", Uuid::new_v4());
|
let zip_filename = format!("{}.zip", Uuid::new_v4());
|
||||||
let zip_path = base_dir.join(&zip_filename);
|
let zip_path = base_dir.join(&zip_filename);
|
||||||
let target_dir = base_dir.join(as_name);
|
let target_dir = base_dir.join(as_name);
|
||||||
|
|
||||||
if let Err(e) = download_zip(url, &zip_path).await {
|
if let Err(e) = download_zip(url, &zip_path).await {
|
||||||
log_message(format!("Error downloading file: {}", e));
|
log!("Error downloading file: {}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,18 +341,14 @@ pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
||||||
let successful = match extract_result {
|
let successful = match extract_result {
|
||||||
Ok(()) => true,
|
Ok(()) => true,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log_message(format!("Error during ZIP extraction: {}", e));
|
log!("Error during ZIP extraction: {}", e);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = tokio::fs::remove_file(&zip_path).await {
|
if let Err(e) = tokio::fs::remove_file(&zip_path).await {
|
||||||
log_message(format!(
|
log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e);
|
||||||
"Error cleaning up ZIP file {}: {}",
|
|
||||||
zip_path.display(),
|
|
||||||
e
|
|
||||||
));
|
|
||||||
} else if successful {
|
} 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_helper;
|
||||||
pub mod crypto_util;
|
pub mod crypto_util;
|
||||||
pub mod file_util;
|
pub mod file_util;
|
||||||
|
pub mod logger;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue