[Fix] UI patches
This commit is contained in:
parent
925fd4d1b4
commit
9d89fd310a
6 changed files with 867 additions and 228 deletions
734
Cargo.lock
generated
734
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,6 @@ once_cell = "1.21.3"
|
|||
pkcs8 = { version = "*", features = ["alloc"] }
|
||||
rand = "0.8"
|
||||
rand_core = { version = "0.6", features = ["getrandom", "std"] }
|
||||
ratatui = { version = "*", features = ["all-widgets"]}
|
||||
reactive-rs = "*"
|
||||
aws-lc-rs = "*"
|
||||
reqwest = "0.12.23"
|
||||
|
|
@ -51,10 +50,11 @@ async-trait = "0.1.89"
|
|||
sha1 = "0.10.6"
|
||||
async-tungstenite = "0.32.0"
|
||||
zip = "6.0.0"
|
||||
ratatui_input = "0.1.3"
|
||||
tui-textarea = "0.7.0"
|
||||
tui-input = "0.14.0"
|
||||
pnet = "0.35.0"
|
||||
dashmap = "6.1.0"
|
||||
strum = "0.27.2"
|
||||
strum_macros = "0.27.2"
|
||||
ratatui = "0.30.0"
|
||||
ratatui_input = "0.1.3"
|
||||
|
|
|
|||
|
|
@ -172,11 +172,7 @@ impl ConsentUiState {
|
|||
}
|
||||
|
||||
fn run_consent_ui() -> UserChoice {
|
||||
enable_raw_mode().unwrap();
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
let mut terminal = ratatui::init();
|
||||
|
||||
let mut state = ConsentUiState {
|
||||
eula: false,
|
||||
|
|
@ -188,23 +184,34 @@ fn run_consent_ui() -> UserChoice {
|
|||
let result = loop {
|
||||
terminal
|
||||
.draw(|f| {
|
||||
let size = f.area();
|
||||
|
||||
let mut needed_height = 5;
|
||||
|
||||
let margin = if size.width > 80 && size.height > 18 {
|
||||
2
|
||||
} else if size.width > 78 && size.height > 16 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let size = f.area();
|
||||
|
||||
if size.height < 6
|
||||
|| size.width < 27 {
|
||||
f.render_widget(Line::from(format!("too small {}/63 by {}/12", size.width, size.height)), size);
|
||||
return;
|
||||
}
|
||||
|
||||
let max_width = 132;
|
||||
let max_height = 20;
|
||||
|
||||
let content_width = if max_width < size.width { max_width } else { size.width} ;
|
||||
let content_height = if max_height < size.height { max_height } else { size.height} ;
|
||||
|
||||
let horizontal_margin = (size.width.saturating_sub(content_width)) / 2;
|
||||
let vertical_margin = (size.height.saturating_sub(content_height)) / 2;
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(7), Constraint::Length(3)])
|
||||
.margin(margin)
|
||||
.split(size);
|
||||
.split(Rect {
|
||||
x: horizontal_margin,
|
||||
y: vertical_margin,
|
||||
width: content_width,
|
||||
height: content_height,
|
||||
});
|
||||
let eula_text = if size.width < 76 {
|
||||
"EULA ¹ (https://docs.tensamin.net/legal/eula/)"
|
||||
} else {
|
||||
|
|
@ -222,25 +229,25 @@ fn run_consent_ui() -> UserChoice {
|
|||
};
|
||||
|
||||
let (mut optional_lines, agree_lines): (Vec<i16>, Vec<&str>) =
|
||||
if size.width > 131 {
|
||||
if size.width > 132 {
|
||||
(
|
||||
vec![7, 3, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree to the End User License Agreement and applicable Terms of Service",
|
||||
"By selecting Continue, you confirm that you have read and agree to the End User License Agreement and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the Terms of Service and Privacy Policy.",
|
||||
]
|
||||
)
|
||||
} else if size.width > 67 {
|
||||
} else if size.width > 68 {
|
||||
(
|
||||
vec![8, 3, 4],
|
||||
vec![
|
||||
"",
|
||||
"By selecting Continue, you confirm that you have read and agree",
|
||||
"to the End User License Agreement and applicable Terms of Service",
|
||||
"to the End User License Agreement and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the ToS and Privacy Policy",
|
||||
"Tensamin services require acceptance of the ToS and Privacy Policy.",
|
||||
]
|
||||
)
|
||||
} else {
|
||||
|
|
@ -250,10 +257,10 @@ fn run_consent_ui() -> UserChoice {
|
|||
"",
|
||||
"By selecting Continue, you confirm that you have",
|
||||
"read and agree to the End User License Agreement",
|
||||
"and applicable Terms of Service",
|
||||
"and applicable Terms of Service.",
|
||||
"",
|
||||
"Tensamin services require acceptance of the",
|
||||
"Terms of Service and Privacy Policy"
|
||||
"Terms of Service and Privacy Policy."
|
||||
]
|
||||
)
|
||||
};
|
||||
|
|
@ -291,7 +298,7 @@ fn run_consent_ui() -> UserChoice {
|
|||
|
||||
let height_style = if size.height < needed_height as u16 {
|
||||
Style::default().fg(Color::Red)
|
||||
} else if size.height >= 11 {
|
||||
} else if size.height < 17 {
|
||||
Style::default().fg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::Green)
|
||||
|
|
@ -368,10 +375,7 @@ fn run_consent_ui() -> UserChoice {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
disable_raw_mode().unwrap();
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
||||
terminal.show_cursor().unwrap();
|
||||
ratatui::restore();
|
||||
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ use std::time::Duration;
|
|||
|
||||
use crate::ACTIVE_TASKS;
|
||||
use crate::{RELOAD, SHUTDOWN, gui::tui::UNIQUE, util::config_util::CONFIG};
|
||||
// Switched to poll/read which are available by default in crossterm
|
||||
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, poll, read};
|
||||
|
||||
use json::JsonValue;
|
||||
|
|
@ -48,6 +47,9 @@ pub fn setup_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;
|
||||
}
|
||||
|
|
@ -67,7 +69,7 @@ pub async fn handle_input(key: KeyEvent) {
|
|||
let password: &str = &password;
|
||||
let password = match password.char_indices().next_back() {
|
||||
Some((i, _)) => &password[..i],
|
||||
None => password,
|
||||
_ => password,
|
||||
};
|
||||
|
||||
CONFIG
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use ratatui::{
|
|||
};
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -14,16 +15,29 @@ pub fn draw(frame: &mut Frame, area: Rect, block: Block, password: String, _stat
|
|||
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([
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
])
|
||||
.constraints(constraints)
|
||||
.split(padded);
|
||||
|
||||
let prefix = Span::raw(format!("select password : {}", password));
|
||||
for i in 0..info.len() {
|
||||
frame.render_widget(Paragraph::new(info[i]), chunks[i]);
|
||||
}
|
||||
|
||||
frame.render_widget(Paragraph::new(prefix), chunks[0]);
|
||||
let prefix = Span::raw(format!("select password : {}", password));
|
||||
frame.render_widget(Paragraph::new(prefix), chunks[info.len()]);
|
||||
}
|
||||
|
|
|
|||
265
src/gui/tui.rs
265
src/gui/tui.rs
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
io::{Stdout, stdout},
|
||||
io::{Stdout, Write, stdout},
|
||||
sync::Arc,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
|
@ -11,12 +10,14 @@ use crate::{
|
|||
util::config_util::CONFIG,
|
||||
};
|
||||
use crossterm::{
|
||||
cursor::Show,
|
||||
execute,
|
||||
terminal::{EnterAlternateScreen, enable_raw_mode},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode},
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use ratatui::{
|
||||
Frame, Terminal,
|
||||
crossterm::terminal::disable_raw_mode,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
prelude::CrosstermBackend,
|
||||
style::Color,
|
||||
|
|
@ -30,46 +31,27 @@ use tokio::{
|
|||
sync::{Mutex, RwLock},
|
||||
};
|
||||
|
||||
// ****** UTIL ******
|
||||
fn init_terminal() {
|
||||
let mut stdout = stdout();
|
||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
||||
enable_raw_mode().unwrap();
|
||||
}
|
||||
|
||||
// ****** MAIN ******
|
||||
pub static UNIQUE: Lazy<RwLock<bool>> = Lazy::new(|| RwLock::new(true));
|
||||
pub static TERMINAL: Lazy<Arc<Mutex<Terminal<CrosstermBackend<Stdout>>>>> = Lazy::new(|| {
|
||||
Arc::new(Mutex::new(
|
||||
Terminal::new(CrosstermBackend::new(stdout())).unwrap(),
|
||||
))
|
||||
});
|
||||
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 {
|
||||
init_terminal();
|
||||
|
||||
{
|
||||
ACTIVE_TASKS.lock().unwrap().push("UI".to_string());
|
||||
}
|
||||
loop {
|
||||
if *SHUTDOWN.read().await {
|
||||
break;
|
||||
}
|
||||
|
||||
if *UNIQUE.read().await {
|
||||
render_tui().await;
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
{
|
||||
ACTIVE_TASKS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.retain(|t| !t.eq(&"UI".to_string()));
|
||||
}
|
||||
ratatui::restore();
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn render_tui() {
|
||||
let password = CONFIG
|
||||
.read()
|
||||
|
|
@ -78,123 +60,116 @@ pub async fn render_tui() {
|
|||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
TERMINAL
|
||||
.lock()
|
||||
.await
|
||||
.draw(|f| {
|
||||
let area = f.area();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
|
||||
let mut terminal = TERMINAL.lock().await;
|
||||
let _ = terminal.draw(|f| {
|
||||
let area = f.area();
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(60),
|
||||
Constraint::Min(40),
|
||||
])
|
||||
.split(area);
|
||||
|
||||
let state = APP_STATE.lock().unwrap().clone();
|
||||
|
||||
// LOGS PANEL
|
||||
{
|
||||
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::LEFT.union(Borders::TOP).union(Borders::BOTTOM)),
|
||||
);
|
||||
f.render_widget(list, chunks[0]);
|
||||
}
|
||||
// SETTINGS PANEL
|
||||
{
|
||||
settings_panel::draw(
|
||||
f,
|
||||
chunks[1],
|
||||
Block::default()
|
||||
.title("Settings")
|
||||
.borders(Borders::LEFT.union(Borders::TOP).union(Borders::BOTTOM)),
|
||||
password,
|
||||
state.clone(),
|
||||
);
|
||||
draw_block_joins(
|
||||
f,
|
||||
chunks[1],
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::BOTTOM),
|
||||
Borders::LEFT,
|
||||
);
|
||||
}
|
||||
// PERFORMANCE GRAPHS
|
||||
{
|
||||
let stack = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(60),
|
||||
Constraint::Max(40),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
])
|
||||
.constraints([
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Percentage(60),
|
||||
Constraint::Min(40),
|
||||
])
|
||||
.split(area);
|
||||
let state;
|
||||
{
|
||||
state = APP_STATE.lock().unwrap().clone();
|
||||
}
|
||||
// LOGS
|
||||
{
|
||||
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::LEFT.union(Borders::TOP).union(Borders::BOTTOM)),
|
||||
);
|
||||
f.render_widget(list, chunks[0]);
|
||||
}
|
||||
// SETTINGS
|
||||
{
|
||||
settings_panel::draw(
|
||||
f,
|
||||
chunks[1],
|
||||
Block::default()
|
||||
.title("Settings")
|
||||
.borders(Borders::LEFT.union(Borders::TOP).union(Borders::BOTTOM)),
|
||||
password,
|
||||
state.clone(),
|
||||
);
|
||||
draw_block_joins(
|
||||
f,
|
||||
chunks[1],
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::BOTTOM),
|
||||
Borders::LEFT,
|
||||
);
|
||||
}
|
||||
// GRAPHS
|
||||
{
|
||||
let stack = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]
|
||||
.as_ref(),
|
||||
)
|
||||
.split(chunks[2]);
|
||||
render_graphs(
|
||||
f,
|
||||
stack[0],
|
||||
"CPU".to_string(),
|
||||
"%".to_string(),
|
||||
state.with_width(38).cpu,
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
|
||||
Color::Cyan,
|
||||
);
|
||||
draw_block_joins(
|
||||
f,
|
||||
stack[0],
|
||||
Borders::TOP.union(Borders::LEFT),
|
||||
Borders::LEFT,
|
||||
);
|
||||
render_graphs(
|
||||
f,
|
||||
stack[1],
|
||||
"RAM".to_string(),
|
||||
"%".to_string(),
|
||||
state.with_width(38).ram,
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
|
||||
Color::Green,
|
||||
);
|
||||
draw_block_joins(
|
||||
f,
|
||||
stack[1],
|
||||
Borders::TOP.union(Borders::LEFT).union(Borders::RIGHT),
|
||||
Borders::TOP,
|
||||
);
|
||||
render_graphs(
|
||||
f,
|
||||
stack[2],
|
||||
"PING".to_string(),
|
||||
"ms".to_string(),
|
||||
state.with_width(38).ping,
|
||||
Borders::ALL,
|
||||
Color::Magenta,
|
||||
);
|
||||
draw_block_joins(f, stack[2], Borders::ALL, Borders::TOP);
|
||||
draw_block_joins(
|
||||
f,
|
||||
stack[2],
|
||||
Borders::BOTTOM.union(Borders::LEFT),
|
||||
Borders::LEFT,
|
||||
);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
.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;
|
||||
}
|
||||
|
||||
|
|
@ -216,16 +191,18 @@ pub fn render_graphs(
|
|||
.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 as f64, 0.0 as f64)).1 as i64,
|
||||
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])
|
||||
|
|
|
|||
Loading…
Reference in a new issue