[Fix] Connection Management

This commit is contained in:
Alex Emmet 2026-09-13 20:58:41 +02:00
commit 3f2ac18333
No known key found for this signature in database
122 changed files with 19970 additions and 5263 deletions

View file

@ -13,11 +13,11 @@ use std::{
};
use tokio::time::Instant;
use crate::theme::ResolvedTheme;
use crate::{
elements::elements::{Element, InteractableElement, JoinableElement},
interaction_result::InteractionResult,
ipc_client::IpcClient,
render_context::RenderContext,
screens::screens::IotaInteraction,
util::borders::draw_block_joins,
};
@ -300,18 +300,13 @@ impl Element for ConsoleCard {
self
}
fn render(&self, f: &mut Frame, r: Rect, context: &RenderContext<'_>) {
if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
let inner = crate::controls::panel::render_panel(
f,
r,
&self.title,
self.focused,
context.theme,
);
fn render(&self, f: &mut Frame, r: Rect, theme: &ResolvedTheme) {
if matches!(theme.chrome, crate::theme::ChromeMode::Surfaces) {
let inner =
crate::controls::panel::render_panel(f, r, &self.title, self.focused, theme);
f.render_widget(
Paragraph::new(Line::from(self.render_cursor_spans(context.theme)))
.style(context.theme.console.text),
Paragraph::new(Line::from(self.render_cursor_spans(theme)))
.style(theme.console.text),
inner,
);
return;
@ -319,15 +314,15 @@ impl Element for ConsoleCard {
let block = Block::default()
.borders(self.borders)
.title(self.title.clone())
.title_style(context.theme.console.title)
.title_style(theme.console.title)
.border_style(if self.focused {
context.theme.console.focused_border
theme.console.focused_border
} else {
context.theme.console.border
theme.console.border
})
.style(context.theme.console.text);
.style(theme.console.text);
let spans = self.render_cursor_spans(context.theme);
let spans = self.render_cursor_spans(theme);
let par = Paragraph::new(Line::from(spans))
.block(block)
.scroll((0, 0));
@ -338,9 +333,9 @@ impl Element for ConsoleCard {
self.borders,
self.joins,
if self.focused {
context.theme.borders.focused
theme.borders.focused
} else {
context.theme.borders.normal
theme.borders.normal
},
);
}
@ -389,7 +384,7 @@ impl InteractableElement for ConsoleCard {
self
}
fn interact(&mut self, key: KeyEvent) -> InteractionResult {
fn interact(&mut self, key: KeyEvent) -> IotaInteraction {
// Check if a previously failed command should be restored.
if let Some(restored) = self.pending_restore.lock().unwrap().take() {
self.content = restored;
@ -401,26 +396,26 @@ impl InteractableElement for ConsoleCard {
if matches!(key.code, KeyCode::Char('y') | KeyCode::Char('Y')) {
self.dispatch_command(command);
}
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
match key.code {
KeyCode::Enter => {
if self.content.is_empty() {
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
let command = self.content.clone();
if let Some(error) = iota_ipc::text_commands::validation_error(&command) {
self.message = Some(error);
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
if command.trim_start_matches('/').trim() == "help" {
self.message = Some(format!(
"Commands: {}",
iota_ipc::text_commands::COMMANDS.join(", ")
));
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
if self.history.last() != Some(&command) {
self.history.push(command.clone());
@ -434,12 +429,12 @@ impl InteractableElement for ConsoleCard {
} else {
self.dispatch_command(command);
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Backspace => {
self.message = None;
self.delete_at_cursor();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Delete => {
self.message = None;
@ -454,41 +449,41 @@ impl InteractableElement for ConsoleCard {
.unwrap_or(self.content.len());
self.content.replace_range(start..end, "");
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Left => {
self.move_cursor_left();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Right => {
self.move_cursor_right();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Home => {
self.cursor_position = 0;
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::End => {
self.cursor_position = self.content.chars().count();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Up => {
self.history_previous();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Down => {
self.history_next();
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Tab if !self.content.is_empty() => {
if self.complete() {
InteractionResult::Handled
IotaInteraction::Changed
} else {
self.message = Some("No command completion.".into());
InteractionResult::Handled
IotaInteraction::Changed
}
}
KeyCode::Tab | KeyCode::BackTab => InteractionResult::Unhandled,
KeyCode::Tab | KeyCode::BackTab => IotaInteraction::Unhandled,
_ => {
if !key
.modifiers
@ -497,10 +492,10 @@ impl InteractableElement for ConsoleCard {
if let Some(c) = key.code.as_char() {
self.insert_at_cursor(c);
self.message = None;
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
}
InteractionResult::Unhandled
IotaInteraction::Unhandled
}
}
}

View file

@ -3,16 +3,15 @@ use std::any::Any;
use crossterm::event::KeyEvent;
use ratatui::{Frame, layout::Rect, widgets::Borders};
use crate::{
interaction_result::InteractionResult, render_context::RenderContext, screens::screens::Screen,
};
use crate::screens::screens::{IotaInteraction, IotaScreen};
use crate::theme::ResolvedTheme;
#[allow(unused)]
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, context: &RenderContext<'_>);
fn render(&self, f: &mut Frame, r: Rect, theme: &ResolvedTheme);
}
#[allow(unused)]
@ -33,7 +32,7 @@ pub trait InfoElement: Send + Sync + Any {
fn as_element(&self) -> &dyn Element;
fn as_element_mut(&mut self) -> &mut dyn Element;
fn get_info_screen(&self) -> Box<dyn Screen>;
fn get_info_screen(&self) -> Box<dyn IotaScreen>;
}
#[allow(unused)]
@ -43,7 +42,7 @@ pub trait InteractableElement: Send + Sync + Any {
fn as_element(&self) -> &dyn Element;
fn as_element_mut(&mut self) -> &mut dyn Element;
fn interact(&mut self, key: KeyEvent) -> InteractionResult;
fn interact(&mut self, key: KeyEvent) -> IotaInteraction;
fn can_focus(&self) -> bool;
fn is_focused(&self) -> bool;

View file

@ -1,4 +1,4 @@
use std::{any::Any, sync::Arc};
use std::any::Any;
use crossterm::event::KeyEvent;
use iota_state::ClientState;
@ -11,11 +11,10 @@ use ratatui::{
},
};
use crate::theme::ResolvedTheme;
use crate::{
elements::elements::{Element, InteractableElement, JoinableElement},
interaction_result::InteractionResult,
render_context::RenderContext,
ui::UI,
screens::screens::IotaInteraction,
util::borders::draw_block_joins,
};
@ -67,7 +66,6 @@ impl GRAPHS {
#[allow(unused)]
pub struct GraphCard {
ui: Arc<UI>,
state: ClientState,
graph_type: GRAPHS,
@ -82,9 +80,8 @@ pub struct GraphCard {
}
impl GraphCard {
pub fn new(ui: Arc<UI>, state: ClientState, graph_type: GRAPHS, title: String) -> Self {
pub fn new(state: ClientState, graph_type: GRAPHS, title: String) -> Self {
Self {
ui,
state,
graph_type,
focused: false,
@ -113,7 +110,7 @@ impl Element for GraphCard {
self
}
fn render(&self, f: &mut Frame, r: Rect, context: &RenderContext<'_>) {
fn render(&self, f: &mut Frame, r: Rect, theme: &ResolvedTheme) {
if self.open {
let graph = self.graph_type.get_graph(&self.state, self.sample_width);
if graph.is_empty() {
@ -121,13 +118,13 @@ impl Element for GraphCard {
.title(format!(" {} ", self.title))
.borders(self.borders)
.border_style(if self.focused {
context.theme.graphs.focused_border
theme.graphs.focused_border
} else {
context.theme.graphs.border
theme.graphs.border
});
f.render_widget(
ratatui::widgets::Paragraph::new("No metric samples yet.")
.style(context.theme.text.muted)
.style(theme.text.muted)
.block(block),
r,
);
@ -149,7 +146,7 @@ impl Element for GraphCard {
GRAPHS::Ping => (max_y * 1.2).max(10.0),
};
let surface = matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces);
let surface = matches!(theme.chrome, crate::theme::ChromeMode::Surfaces);
let title = format!(
"{}: {}{} {}min/{}max",
self.title,
@ -159,7 +156,7 @@ impl Element for GraphCard {
max_y as i64
);
let plot_area = if surface {
crate::controls::panel::render_panel(f, r, &title, self.focused, context.theme)
crate::controls::panel::render_panel(f, r, &title, self.focused, theme)
} else {
r
};
@ -178,9 +175,9 @@ impl Element for GraphCard {
})
.borders(if surface { Borders::NONE } else { self.borders })
.border_style(if self.focused {
context.theme.graphs.focused_border
theme.graphs.focused_border
} else {
context.theme.graphs.border
theme.graphs.border
});
let canvas = Canvas::default()
@ -194,7 +191,7 @@ impl Element for GraphCard {
y1: 0.0,
x2: *x,
y2: *y,
color: self.graph_type.get_color(context.theme),
color: self.graph_type.get_color(theme),
});
}
});
@ -204,22 +201,22 @@ impl Element for GraphCard {
.title("")
.borders(self.borders)
.border_style(if self.focused {
context.theme.graphs.focused_border
theme.graphs.focused_border
} else {
context.theme.graphs.border
theme.graphs.border
});
f.render_widget(block, r);
}
if !matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
if !matches!(theme.chrome, crate::theme::ChromeMode::Surfaces) {
draw_block_joins(
f,
r,
self.borders,
self.joins,
if self.focused {
context.theme.borders.focused
theme.borders.focused
} else {
context.theme.borders.normal
theme.borders.normal
},
);
}
@ -269,8 +266,8 @@ impl InteractableElement for GraphCard {
self
}
fn interact(&mut self, _key: KeyEvent) -> InteractionResult {
InteractionResult::Handled
fn interact(&mut self, _key: KeyEvent) -> IotaInteraction {
IotaInteraction::Changed
}
fn can_focus(&self) -> bool {

View file

@ -1,6 +1,6 @@
use crate::elements::elements::{Element, InteractableElement, JoinableElement};
use crate::util::borders::draw_block_joins;
use crate::{interaction_result::InteractionResult, render_context::RenderContext};
use crate::{screens::screens::IotaInteraction, theme::ResolvedTheme};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use iota_state::{ClientState, UiLogEntry};
use ratatui::{
@ -270,7 +270,7 @@ impl LogCard {
}
}
fn scroll_up(&mut self) {
pub fn scroll_older(&mut self) {
let max_offset = self
.last_total_lines
.load(Ordering::Relaxed)
@ -278,7 +278,7 @@ impl LogCard {
self.scroll_offset = (self.scroll_offset + 1).min(max_offset);
}
fn scroll_down(&mut self) {
pub fn scroll_newer(&mut self) {
self.scroll_offset = self.scroll_offset.saturating_sub(1);
}
@ -318,25 +318,19 @@ impl Element for LogCard {
self
}
fn render(&self, f: &mut Frame, area: Rect, context: &RenderContext<'_>) {
fn render(&self, f: &mut Frame, area: Rect, theme: &ResolvedTheme) {
let entries = self.get_logs();
let inner_area = if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
crate::controls::panel::render_panel(
f,
area,
&self.build_title(),
self.focused,
context.theme,
)
let inner_area = if matches!(theme.chrome, crate::theme::ChromeMode::Surfaces) {
crate::controls::panel::render_panel(f, area, &self.build_title(), self.focused, theme)
} else {
let block = Block::default()
.title(self.build_title())
.borders(self.borders)
.border_style(if self.focused {
context.theme.logs.focused_border
theme.logs.focused_border
} else {
context.theme.logs.border
theme.logs.border
});
let inner = block.inner(area);
f.render_widget(block, area);
@ -350,9 +344,9 @@ impl Element for LogCard {
self.borders,
self.joins,
if self.focused {
context.theme.borders.focused
theme.borders.focused
} else {
context.theme.borders.normal
theme.borders.normal
},
);
return;
@ -376,24 +370,21 @@ impl Element for LogCard {
let mut spans = Vec::new();
let (prefix, rest) = Self::split_line_prefix(line);
let prefix = if matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
let prefix = if matches!(theme.chrome, crate::theme::ChromeMode::Surfaces) {
""
} else {
prefix
};
if !prefix.is_empty() {
spans.push(Span::styled(
prefix.to_string(),
source.style(context.theme),
));
spans.push(Span::styled(prefix.to_string(), source.style(theme)));
}
let (content, timestamp) = Self::split_timestamp_suffix(rest);
let text_style = if *is_error {
context.theme.logs.error
theme.logs.error
} else {
context.theme.logs.text
theme.logs.text
};
if !content.is_empty() {
@ -401,10 +392,7 @@ impl Element for LogCard {
}
if !timestamp.is_empty() {
spans.push(Span::styled(
timestamp.to_string(),
context.theme.logs.timestamp,
));
spans.push(Span::styled(timestamp.to_string(), theme.logs.timestamp));
}
Line::from(spans)
@ -421,16 +409,16 @@ impl Element for LogCard {
f.render_widget(Paragraph::new(line.clone()), line_area);
}
if !matches!(context.theme.chrome, crate::theme::ChromeMode::Surfaces) {
if !matches!(theme.chrome, crate::theme::ChromeMode::Surfaces) {
draw_block_joins(
f,
area,
self.borders,
self.joins,
if self.focused {
context.theme.borders.focused
theme.borders.focused
} else {
context.theme.borders.normal
theme.borders.normal
},
);
}
@ -480,7 +468,7 @@ impl InteractableElement for LogCard {
&mut *self
}
fn interact(&mut self, key: KeyEvent) -> InteractionResult {
fn interact(&mut self, key: KeyEvent) -> IotaInteraction {
if self.filtering {
match key.code {
KeyCode::Esc => {
@ -501,7 +489,7 @@ impl InteractableElement for LogCard {
_ => {}
}
self.scroll_offset = 0;
return InteractionResult::Handled;
return IotaInteraction::Changed;
}
let entries = self.get_logs();
let width = self.last_width.load(Ordering::Relaxed).max(1);
@ -517,51 +505,51 @@ impl InteractableElement for LogCard {
self.filtering = true;
self.filter.clear();
self.scroll_offset = 0;
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Enter | KeyCode::Char(' ') => {
self.selected = !self.selected;
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Char('j') | KeyCode::Char('J') => {
let (can_up, _) = self.get_title_hints();
if can_up {
self.scroll_up();
self.scroll_older();
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Char('k') | KeyCode::Char('K') => {
let (_, can_down) = self.get_title_hints();
if can_down {
self.scroll_down();
self.scroll_newer();
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Up if self.selected => {
let (can_up, _) = self.get_title_hints();
if can_up {
self.scroll_up();
self.scroll_older();
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Down if self.selected => {
let (_, can_down) = self.get_title_hints();
if can_down {
self.scroll_down();
self.scroll_newer();
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::Home => {
if total_lines > visible_height {
self.scroll_offset = total_lines - visible_height;
}
InteractionResult::Handled
IotaInteraction::Changed
}
KeyCode::End => {
self.scroll_offset = 0;
InteractionResult::Handled
IotaInteraction::Changed
}
_ => InteractionResult::Unhandled,
_ => IotaInteraction::Unhandled,
}
}