This commit is contained in:
Alex Emmet 2026-04-04 21:43:54 +02:00
commit 71ac8d97fa
12 changed files with 445 additions and 445 deletions

1
Cargo.lock generated
View file

@ -1793,6 +1793,7 @@ dependencies = [
"hkdf", "hkdf",
"hyper", "hyper",
"hyper-util", "hyper-util",
"iota-core",
"iota-state", "iota-state",
"json", "json",
"lazy_static", "lazy_static",

View file

@ -1,2 +1,2 @@
[workspace] [workspace]
members = ["iota-storage", "iota-state", "iota-cli", "iota-core", "omikron-connector", "web-server", "web-ui", "decentralized", "iota-logger"] members = ["iota-storage", "iota-terms", "iota-state", "iota-cli", "iota-core", "omikron-connector", "web-server", "web-ui", "decentralized", "iota-logger"]

View file

@ -5,6 +5,9 @@ edition = "2024"
[dependencies] [dependencies]
iota-state = { path = "../iota-state" } iota-state = { path = "../iota-state" }
iota-core = { path = "../iota-terms" }
ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" }
ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" }

View file

@ -13,6 +13,8 @@ pub mod screens {
} }
pub mod util { pub mod util {
pub mod borders; pub mod borders;
pub mod buttons;
pub mod terms_focus;
} }
pub mod app_state; pub mod app_state;
pub mod input_handler; pub mod input_handler;

View file

@ -5,7 +5,7 @@ use ratatui::{
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},
}; };
use crate::terms::focus::Focus; use crate::util::terms_focus::Focus;
#[allow(mismatched_lifetime_syntaxes)] #[allow(mismatched_lifetime_syntaxes)]
pub fn checkbox(label: &str, checked: bool, active: bool, allowed: bool) -> Line { pub fn checkbox(label: &str, checked: bool, active: bool, allowed: bool) -> Line {

View file

@ -1,14 +1,432 @@
pub fn add(left: u64, right: u64) -> u64 { use std::{
left + right collections::BTreeMap,
fs::{self, OpenOptions},
io::Write,
path::Path,
sync::{OnceLock, atomic::Ordering, mpsc},
thread,
time::{SystemTime, UNIX_EPOCH},
};
use ratatui::style::Color;
use ttp_core::{CommunicationValue, DataTypes, DataValue};
use crate::{
APP_STATE,
gui::{elements::log_card::LogEntry, ui::UNIQUE},
langu::language_manager,
};
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(unused)]
pub enum PrintType {
Call,
Client,
Iota,
Omikron,
Omega,
General,
Command,
} }
impl PrintType {
#[cfg(test)] pub fn prefix_color(self) -> Color {
mod tests { match self {
use super::*; PrintType::Call => Color::Magenta,
PrintType::Client => Color::Green,
#[test] PrintType::Iota => Color::Yellow,
fn it_works() { PrintType::Omikron => Color::Blue,
let result = add(2, 2); PrintType::Omega => Color::Cyan,
assert_eq!(result, 4); PrintType::General => Color::LightCyan,
PrintType::Command => Color::LightGreen,
}
} }
} }
struct LogMessage {
timestamp_ms: u128,
prefix: String,
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 timestamp = format_timestamp_inline(msg.timestamp_ms);
let entry = LogEntry::new(msg.kind, resolved_message, msg.is_error);
let prefix = if msg.prefix.is_empty() {
String::new()
} else {
format!("{} ", msg.prefix)
};
let _ = writeln!(
file,
"{} {}{}",
fixed_box(&msg.timestamp_ms.to_string(), 13),
prefix,
entry.message
);
let _ = writeln!(file, " {}", timestamp);
let mut state = APP_STATE.lock().unwrap();
state.push_log(entry.into());
}
});
}
fn format_timestamp_inline(timestamp_ms: u128) -> String {
let secs = (timestamp_ms / 1000) as i64;
let hours = (secs / 3600) % 24;
let minutes = (secs / 60) % 60;
let seconds = secs % 60;
format!("[{:02}:{:02}:{:02}]", hours, minutes, seconds)
}
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
}
}
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::LightCyan,
PrintType::Command => Color::LightGreen,
}
}
pub fn log_internal_translated(
kind: PrintType,
prefix: String,
is_error: bool,
key: &str,
args: Vec<String>,
) {
if let Some(tx) = LOGGER.get() {
UNIQUE.store(true, Ordering::Relaxed);
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: String, is_error: bool, message: String) {
if let Some(tx) = LOGGER.get() {
UNIQUE.store(true, Ordering::Relaxed);
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),
});
}
}
#[macro_export]
macro_rules! log_t {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
false,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
false,
$key,
vec![$($arg),+]
)
};
}
#[macro_export]
macro_rules! log_t_err {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
true,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
true,
$key,
vec![$($arg.to_string()),+]
)
};
}
/// Log a command message.
#[macro_export]
macro_rules! log_command {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::Command,
"".to_string(),
false,
format!($($arg)*)
)
};
}
/// Log a general informational message.
#[macro_export]
macro_rules! log {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::General,
"".to_string(),
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,
">".to_string(),
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,
"<".to_string(),
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,
">>".to_string(),
true,
format!($($arg)*)
)
};
}
// ******** COMMUNICATION VALUES ********
pub fn log_cv_internal(
prefix: &'static str,
cv: &CommunicationValue,
print_type: Option<PrintType>,
) {
let formatted = format_cv(cv);
log_internal(
print_type.unwrap_or(PrintType::General),
prefix.to_string(),
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 data: &BTreeMap<DataTypes, DataValue> = cv.get_data_container();
let formated_data =
format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
parts.push(format!("{}", formated_data));
parts.join(": ")
}
fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String {
let parts: Vec<String> = data
.into_iter()
.map(|(key, value)| {
let key_str = key.to_string();
match value {
DataValue::Str(s) => format!("{}=\"{}\"", key_str, s),
DataValue::Container(inner) => {
let inner_formatted = format_data_container(inner);
format!("{}={{ {} }}", key_str, inner_formatted)
}
DataValue::Array(arr) => {
let arr_formatted = format_array(arr);
format!("{}=[{}]", key_str, arr_formatted)
}
DataValue::Bool(b) => format!("{}={}", key_str, b),
DataValue::BoolTrue => format!("{}=true", key_str),
DataValue::BoolFalse => format!("{}=false", key_str),
DataValue::Number(num) => format!("{}={}", key_str, num),
_ => "".to_string(),
}
})
.collect();
parts.join(", ")
}
fn format_array(arr: Vec<DataValue>) -> String {
let parts: Vec<String> = arr
.into_iter()
.map(|value| match value {
DataValue::Str(s) => format!("\"{}\"", s),
DataValue::Container(inner) => {
let inner_formatted = format_data_container(inner);
format!("{{ {} }}", inner_formatted)
}
DataValue::Array(inner_arr) => {
let formatted = format_array(inner_arr);
format!("[{}]", formatted)
}
DataValue::Bool(b) => b.to_string(),
DataValue::BoolTrue => "true".to_string(),
DataValue::BoolFalse => "false".to_string(),
DataValue::Number(num) => num.to_string(),
_ => String::new(),
})
.collect();
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_cv_in {
($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_cv_out {
($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal("< ", &$cv, Some($kind))
};
($cv:expr) => {
$crate::util::logger::log_cv_internal("< ", &$cv, None)
};
}

View file

@ -1,432 +0,0 @@
use std::{
collections::BTreeMap,
fs::{self, OpenOptions},
io::Write,
path::Path,
sync::{OnceLock, atomic::Ordering, mpsc},
thread,
time::{SystemTime, UNIX_EPOCH},
};
use ratatui::style::Color;
use ttp_core::{CommunicationValue, DataTypes, DataValue};
use crate::{
APP_STATE,
gui::{elements::log_card::LogEntry, ui::UNIQUE},
langu::language_manager,
};
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(unused)]
pub enum PrintType {
Call,
Client,
Iota,
Omikron,
Omega,
General,
Command,
}
impl PrintType {
pub fn prefix_color(self) -> Color {
match self {
PrintType::Call => Color::Magenta,
PrintType::Client => Color::Green,
PrintType::Iota => Color::Yellow,
PrintType::Omikron => Color::Blue,
PrintType::Omega => Color::Cyan,
PrintType::General => Color::LightCyan,
PrintType::Command => Color::LightGreen,
}
}
}
struct LogMessage {
timestamp_ms: u128,
prefix: String,
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 timestamp = format_timestamp_inline(msg.timestamp_ms);
let entry = LogEntry::new(msg.kind, resolved_message, msg.is_error);
let prefix = if msg.prefix.is_empty() {
String::new()
} else {
format!("{} ", msg.prefix)
};
let _ = writeln!(
file,
"{} {}{}",
fixed_box(&msg.timestamp_ms.to_string(), 13),
prefix,
entry.message
);
let _ = writeln!(file, " {}", timestamp);
let mut state = APP_STATE.lock().unwrap();
state.push_log(entry.into());
}
});
}
fn format_timestamp_inline(timestamp_ms: u128) -> String {
let secs = (timestamp_ms / 1000) as i64;
let hours = (secs / 3600) % 24;
let minutes = (secs / 60) % 60;
let seconds = secs % 60;
format!("[{:02}:{:02}:{:02}]", hours, minutes, seconds)
}
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
}
}
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::LightCyan,
PrintType::Command => Color::LightGreen,
}
}
pub fn log_internal_translated(
kind: PrintType,
prefix: String,
is_error: bool,
key: &str,
args: Vec<String>,
) {
if let Some(tx) = LOGGER.get() {
UNIQUE.store(true, Ordering::Relaxed);
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: String, is_error: bool, message: String) {
if let Some(tx) = LOGGER.get() {
UNIQUE.store(true, Ordering::Relaxed);
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),
});
}
}
#[macro_export]
macro_rules! log_t {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
false,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
false,
$key,
vec![$($arg),+]
)
};
}
#[macro_export]
macro_rules! log_t_err {
($key:expr) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
true,
$key,
vec![]
)
};
($key:expr, $($arg:expr),+) => {
$crate::util::logger::log_internal_translated(
$crate::util::logger::PrintType::General,
"".to_string(),
true,
$key,
vec![$($arg.to_string()),+]
)
};
}
/// Log a command message.
#[macro_export]
macro_rules! log_command {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::Command,
"".to_string(),
false,
format!($($arg)*)
)
};
}
/// Log a general informational message.
#[macro_export]
macro_rules! log {
($($arg:tt)*) => {
$crate::util::logger::log_internal(
$crate::util::logger::PrintType::General,
"".to_string(),
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,
">".to_string(),
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,
"<".to_string(),
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,
">>".to_string(),
true,
format!($($arg)*)
)
};
}
// ******** COMMUNICATION VALUES ********
pub fn log_cv_internal(
prefix: &'static str,
cv: &CommunicationValue,
print_type: Option<PrintType>,
) {
let formatted = format_cv(cv);
log_internal(
print_type.unwrap_or(PrintType::General),
prefix.to_string(),
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 data: &BTreeMap<DataTypes, DataValue> = cv.get_data_container();
let formated_data =
format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect());
parts.push(format!("{}", formated_data));
parts.join(": ")
}
fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String {
let parts: Vec<String> = data
.into_iter()
.map(|(key, value)| {
let key_str = key.to_string();
match value {
DataValue::Str(s) => format!("{}=\"{}\"", key_str, s),
DataValue::Container(inner) => {
let inner_formatted = format_data_container(inner);
format!("{}={{ {} }}", key_str, inner_formatted)
}
DataValue::Array(arr) => {
let arr_formatted = format_array(arr);
format!("{}=[{}]", key_str, arr_formatted)
}
DataValue::Bool(b) => format!("{}={}", key_str, b),
DataValue::BoolTrue => format!("{}=true", key_str),
DataValue::BoolFalse => format!("{}=false", key_str),
DataValue::Number(num) => format!("{}={}", key_str, num),
_ => "".to_string(),
}
})
.collect();
parts.join(", ")
}
fn format_array(arr: Vec<DataValue>) -> String {
let parts: Vec<String> = arr
.into_iter()
.map(|value| match value {
DataValue::Str(s) => format!("\"{}\"", s),
DataValue::Container(inner) => {
let inner_formatted = format_data_container(inner);
format!("{{ {} }}", inner_formatted)
}
DataValue::Array(inner_arr) => {
let formatted = format_array(inner_arr);
format!("[{}]", formatted)
}
DataValue::Bool(b) => b.to_string(),
DataValue::BoolTrue => "true".to_string(),
DataValue::BoolFalse => "false".to_string(),
DataValue::Number(num) => num.to_string(),
_ => String::new(),
})
.collect();
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_cv_in {
($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_cv_out {
($kind:expr, $cv:expr) => {
$crate::util::logger::log_cv_internal("< ", &$cv, Some($kind))
};
($cv:expr) => {
$crate::util::logger::log_cv_internal("< ", &$cv, None)
};
}

10
iota-terms/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "iota-terms"
version = "0.1.0"
edition = "2024"
[dependencies]
iota-state = { path = "../iota-state" }
json = "*"
reqwest = "0.13.2"

View file

@ -1,4 +1,2 @@
pub mod buttons;
pub mod doc; pub mod doc;
pub mod focus;
pub mod terms_getter; pub mod terms_getter;