Skip to content

Commit

Permalink
Update Dashboard (#45)
Browse files Browse the repository at this point in the history
* Add another tab

* Bump ratatui version

* Update the system tab

* Add tab_pallets

* Split the system tab

* A compiled version

* Display pallet list

* Support UP/DOWN

* Fix CI
  • Loading branch information
boundless-forest authored Nov 9, 2023
1 parent bc04040 commit 3d036c3
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 97 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ thiserror = { version = "1.0.44" }
env_logger = { version = "0.10.0" }
log = { version = "0.4.19"}
scale-info = { version = "2.9.0" }
subxt-metadata = { version = "0.31.0" }
subxt-metadata = { version = "0.32.1" }
scale-value = { version = "0.12.0" }
comfy-table = { version = "7.0.1" }
ratatui = { version = "0.23.0", features = ["all-widgets"] }
ratatui = { version = "0.24.0", features = ["all-widgets"] }
crossterm = { version = "0.27.0" }
array-bytes = { version = "6.1.0" }
open = { version = "5.0.0" }
Expand Down
129 changes: 69 additions & 60 deletions src/app/dashboard/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod tab_blocks;
mod tab_events;
mod tab_pallets;
mod tab_system;

// std
Expand All @@ -22,32 +23,32 @@ use crate::{
networks::ChainInfo,
rpc::{BlockForChain, ChainApi, HeaderForChain, RpcClient, SystemPaneInfo},
};
use tab_blocks::draw_blocks_tab;
use tab_events::draw_events_tab;
use tab_system::draw_system;

pub(crate) const BLOCKS_MAX_LIMIT: usize = 30;
pub(crate) const EVENTS_MAX_LIMIT: usize = 5;

pub struct DashBoard<CI: ChainInfo> {
pub metadata: Metadata,
pub struct DashBoard<'a, CI: ChainInfo> {
pub metadata: &'a mut Metadata,
pub system_pane_info: SystemPaneInfo,
pub blocks_rev: UnboundedReceiver<HeaderForChain<CI>>,
pub blocks: StatefulList<BlockForChain<CI>>,
pub selected_block: Option<BlockForChain<CI>>,
pub events_rev: UnboundedReceiver<Vec<StorageData>>,
pub events: StatefulList<Value<u32>>,
pub pallets: StatefulList<(u8, String)>,
pub selected_pallet: Option<(u8, String)>,
pub tab_titles: Vec<String>,
pub index: usize,
pub selected_tab: usize,
}

impl<CI: ChainInfo> DashBoard<CI> {
impl<'a, CI: ChainInfo> DashBoard<'a, CI> {
pub(crate) fn new(
system_pane_info: SystemPaneInfo,
blocks_rev: UnboundedReceiver<HeaderForChain<CI>>,
events_rev: UnboundedReceiver<Vec<StorageData>>,
metadata: Metadata,
) -> DashBoard<CI> {
metadata: &'a mut Metadata,
) -> DashBoard<'a, CI> {
let pallets: VecDeque<(u8, String)> = metadata.pallets().map(|p| (p.index(), p.name().to_string())).collect();
DashBoard {
metadata,
system_pane_info,
Expand All @@ -56,8 +57,10 @@ impl<CI: ChainInfo> DashBoard<CI> {
selected_block: None,
blocks: StatefulList::with_items(VecDeque::with_capacity(BLOCKS_MAX_LIMIT)),
events: StatefulList::with_items(VecDeque::with_capacity(EVENTS_MAX_LIMIT)),
tab_titles: vec![String::from("Blocks"), String::from("Events")],
index: 0,
pallets: StatefulList::with_items(pallets),
selected_pallet: None,
tab_titles: vec![String::from("Blocks"), String::from("Events"), String::from("Pallets")],
selected_tab: 0,
}
}

Expand Down Expand Up @@ -91,7 +94,7 @@ impl<CI: ChainInfo> DashBoard<CI> {

Some(vec_event_records_type_id)
}
let vec_event_records_type_id = vec_event_records_type_id(&mut self.metadata).unwrap();
let vec_event_records_type_id = vec_event_records_type_id(self.metadata).unwrap();

if let Ok(storage_data) = self.events_rev.try_recv() {
for data in storage_data {
Expand Down Expand Up @@ -129,35 +132,13 @@ impl<CI: ChainInfo> DashBoard<CI> {
}
}
}

fn next_tab(&mut self) {
self.index = (self.index + 1) % self.tab_titles.len();
}

fn previous_tab(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.tab_titles.len() - 1;
}
}

fn previous_block(&mut self) {
self.blocks.previous();
if let Some(i) = self.blocks.state.selected() {
self.selected_block = self.blocks.items.get(i).cloned();
}
}

fn next_block(&mut self) {
self.blocks.next();
if let Some(i) = self.blocks.state.selected() {
self.selected_block = self.blocks.items.get(i).cloned();
}
}
}

pub async fn run_dashboard<B, CI>(client: Arc<RpcClient<CI>>, terminal: &mut Terminal<B>, mut dash_board: DashBoard<CI>) -> io::Result<()>
pub async fn run_dashboard<'a, B, CI>(
client: Arc<RpcClient<CI>>,
terminal: &mut Terminal<B>,
mut dash_board: DashBoard<'a, CI>,
) -> io::Result<()>
where
B: Backend,
CI: ChainInfo,
Expand All @@ -171,38 +152,59 @@ where
if let Event::Key(key) = read()? {
if key.kind == KeyEventKind::Press {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Right => dash_board.next_tab(),
KeyCode::Left => dash_board.previous_tab(),
KeyCode::Up => dash_board.previous_block(),
KeyCode::Down => dash_board.next_block(),
KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
KeyCode::Tab => {
dash_board.selected_tab = (dash_board.selected_tab + 1) % dash_board.tab_titles.len();
},
KeyCode::Up => match dash_board.selected_tab {
0 => {
dash_board.blocks.previous();
if let Some(i) = dash_board.blocks.state.selected() {
dash_board.selected_block = dash_board.blocks.items.get(i).cloned();
}
},
2 => {
dash_board.pallets.previous();
if let Some(i) = dash_board.pallets.state.selected() {
dash_board.selected_pallet = dash_board.pallets.items.get(i).cloned();
}
},
_ => {},
},
KeyCode::Down => match dash_board.selected_tab {
0 => {
dash_board.blocks.next();
if let Some(i) = dash_board.blocks.state.selected() {
dash_board.selected_block = dash_board.blocks.items.get(i).cloned();
}
},
2 => {
dash_board.pallets.next();
if let Some(i) = dash_board.pallets.state.selected() {
dash_board.selected_pallet = dash_board.pallets.items.get(i).cloned();
}
},
_ => {},
},
_ => {},
}
}
}
}
}

fn ui<B, CI>(f: &mut Frame<B>, dash_board: &mut DashBoard<CI>)
where
B: Backend,
CI: ChainInfo,
{
fn ui<CI: ChainInfo>(f: &mut Frame, dash_board: &mut DashBoard<CI>) {
let size = f.size();
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Percentage(20), Constraint::Percentage(80)].as_ref())
.split(size);

draw_system(f, dash_board, chunks[0]);
tab_system::draw_system(f, dash_board, chunks[0]);
draw_tabs(f, dash_board, chunks[1]);
}

fn draw_tabs<B, CI>(f: &mut Frame<B>, dash_board: &mut DashBoard<CI>, area: Rect)
where
B: Backend,
CI: ChainInfo,
{
fn draw_tabs<CI: ChainInfo>(f: &mut Frame, dash_board: &mut DashBoard<CI>, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
Expand All @@ -213,15 +215,22 @@ where
.map(|t| Line::from(Span::styled(t, Style::default().fg(Color::Yellow).bold())))
.collect();
let tabs = Tabs::new(titles)
.block(Block::default().borders(Borders::ALL).title("Chain Data"))
.select(dash_board.index)
.block(
Block::default()
.title(" Chain Information ")
.title_style(Style::default().bold().italic())
.border_type(BorderType::Double)
.borders(Borders::ALL),
)
.select(dash_board.selected_tab)
.style(Style::default().fg(Color::Yellow))
.highlight_style(Style::default().fg(Color::Cyan));
f.render_widget(tabs, chunks[0]);

match dash_board.index {
0 => draw_blocks_tab(f, dash_board, chunks[1]),
1 => draw_events_tab(f, dash_board, chunks[1]),
match dash_board.selected_tab {
0 => tab_blocks::draw_blocks_tab(f, dash_board, chunks[1]),
1 => tab_events::draw_events_tab(f, dash_board, chunks[1]),
2 => tab_pallets::draw_pallets_tab(f, dash_board, chunks[1]),
_ => {},
};
}
Expand Down
40 changes: 24 additions & 16 deletions src/app/dashboard/tab_blocks.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// crates.io
use ratatui::{
prelude::{text::Line, Backend, Color, Constraint, Direction, Frame, Layout, Modifier, Rect, Span, Style},
prelude::{text::Line, Color, Constraint, Direction, Frame, Layout, Modifier, Rect, Span, Style},
style::Stylize,
widgets::*,
};
use sp_core::Encode;
Expand All @@ -12,16 +13,17 @@ use sp_runtime::{
use super::{DashBoard, BLOCKS_MAX_LIMIT};
use crate::networks::ChainInfo;

pub fn draw_blocks_tab<B, CI>(f: &mut Frame<B>, app: &mut DashBoard<CI>, area: Rect)
where
B: Backend,
CI: ChainInfo,
{
pub fn draw_blocks_tab<CI: ChainInfo>(f: &mut Frame, app: &mut DashBoard<CI>, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![Constraint::Percentage(30), Constraint::Percentage(70)])
.split(area);

render_block_list(f, app, chunks[0]);
render_block_details(f, app, chunks[1]);
}

fn render_block_list<CI: ChainInfo>(f: &mut Frame, app: &mut DashBoard<CI>, area: Rect) {
let blocks: Vec<ListItem> = app
.blocks
.items
Expand All @@ -37,21 +39,27 @@ where
let l = List::new(blocks)
.block(
Block::default()
.borders(Borders::ALL)
.title(format!("Latest {} Blocks", BLOCKS_MAX_LIMIT)),
.title(format!(" Latest {} Blocks ", BLOCKS_MAX_LIMIT))
.title_style(Style::default().bold().italic())
.border_type(BorderType::Double)
.borders(Borders::ALL),
)
.style(Style::default().fg(Color::Yellow))
.highlight_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
.highlight_symbol("> ");
f.render_stateful_widget(l, chunks[0], &mut app.blocks.state);
f.render_stateful_widget(l, area, &mut app.blocks.state);
}

let block = Block::default()
fn render_block_details<CI: ChainInfo>(f: &mut Frame, app: &mut DashBoard<CI>, area: Rect) {
let block_style = Block::default()
.title(" Block Details ")
.title_style(Style::default().bold().italic())
.borders(Borders::ALL)
.title("Block Details")
.border_type(BorderType::Double)
.style(Style::default().fg(Color::Yellow));

let selected_block = app.selected_block.clone().or(app.blocks.items.back().cloned());
if let Some(b) = selected_block {
// Fixed items
let mut items = vec![
ListItem::new(format!("ParentHash => {:?}", b.header().parent_hash())),
ListItem::new(format!("Number => {:?}", b.header().number())),
Expand Down Expand Up @@ -94,11 +102,11 @@ where
items.push(ListItem::new(format!(" ext{i} => {:?}", CI::Hashing::hash(&e.encode()))));
}

let l = List::new(items).block(block);
f.render_widget(l, chunks[1]);
let l = List::new(items).block(block_style);
f.render_widget(l, area);
} else {
let text = "Block Details Page".to_string();
let paragraph = Paragraph::new(text).block(block).wrap(Wrap { trim: true });
f.render_widget(paragraph, chunks[1]);
let paragraph = Paragraph::new(text).block(block_style).wrap(Wrap { trim: true });
f.render_widget(paragraph, area);
}
}
16 changes: 8 additions & 8 deletions src/app/dashboard/tab_events.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
// crates.io
use ratatui::{
prelude::{Backend, Color, Frame, Rect, Style},
prelude::{Color, Frame, Rect, Style},
style::Stylize,
widgets::*,
};
// this crate
use super::{DashBoard, EVENTS_MAX_LIMIT};
use crate::networks::ChainInfo;

pub fn draw_events_tab<B, CI>(f: &mut Frame<B>, app: &mut DashBoard<CI>, area: Rect)
where
B: Backend,
CI: ChainInfo,
{
pub fn draw_events_tab<CI: ChainInfo>(f: &mut Frame, app: &mut DashBoard<CI>, area: Rect) {
let mut text = "".to_string();
for e in &app.events.items {
text.push_str(&format!("{}\n", serde_json::to_string(e).unwrap_or("Decode Error Occurred.".to_string())));
}

let l = Paragraph::new(text)
.wrap(Wrap { trim: true })
.block(
Block::default()
.borders(Borders::ALL)
.title(format!("Latest {} Events", EVENTS_MAX_LIMIT)),
.title(format!(" Latest {} Events ", EVENTS_MAX_LIMIT))
.title_style(Style::default().bold().italic())
.border_type(BorderType::Double)
.borders(Borders::ALL),
)
.style(Style::default().fg(Color::Yellow));
f.render_widget(l, area);
Expand Down
Loading

0 comments on commit 3d036c3

Please sign in to comment.