Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
330 changes: 327 additions & 3 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,12 @@ tokio-tungstenite = { version = "0.26", features = ["native-tls"] }
futures-util = "0.3"
url = "2.5"
ratatui = "0.29"
crossterm = { version = "0.28", features = ["event-stream"] }
crossterm = "0.28"
chrono = "0.4"
anyhow = "1.0"
serde_yaml = "0.9"
dirs = "6.0"
arboard = "3.4"
dotenvy = "0.15.7"
async-trait = "0.1"
tracing = "0.1"
Expand All @@ -67,7 +68,6 @@ cadence = "1.4"
# alloy
alloy-primitives = { version = "1.5.2", default-features = false, features = [
"map-foldhash",
"serde",
] }
alloy-genesis = { version = "1.5.2", default-features = false }
alloy-eips = { version = "1.5.2", default-features = false }
Expand All @@ -90,6 +90,4 @@ op-alloy-consensus = { version = "0.22.0", default-features = false }

# base
base-flashtypes = { git = "https://github.com/base/base.git" }

# internal
basectl-cli = { path = "crates/basectl" }
42 changes: 15 additions & 27 deletions bin/basectl/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use basectl_cli::{
commands::{
config::{ConfigCommand, default_view, run_config},
flashblocks::{FlashblocksCommand, default_subscribe, run_flashblocks},
},
app::{ViewId, run_app, run_app_with_view},
config::ChainConfig,
tui::{HomeSelection, NavResult, run_homescreen},
};
use clap::{Parser, Subcommand};

Expand All @@ -24,16 +20,16 @@ struct Cli {
enum Commands {
/// Chain configuration operations
#[command(visible_alias = "c")]
Config {
#[command(subcommand)]
command: ConfigCommand,
},
Config,
/// Flashblocks operations
#[command(visible_alias = "f")]
Flashblocks {
#[command(subcommand)]
command: FlashblocksCommand,
},
Flashblocks,
/// DA (Data Availability) backlog monitor
#[command(visible_alias = "d")]
Da,
/// Command center (combined view)
#[command(visible_alias = "cc")]
CommandCenter,
}

#[tokio::main]
Expand All @@ -43,20 +39,12 @@ async fn main() -> anyhow::Result<()> {
let chain_config = ChainConfig::load(&cli.config)?;

match cli.command {
Some(Commands::Config { command }) => run_config(command, &chain_config).await,
Some(Commands::Flashblocks { command }) => run_flashblocks(command, &chain_config).await,
None => {
// Show homescreen when no command provided
loop {
let next = match run_homescreen()? {
HomeSelection::Config => default_view(&chain_config).await?,
HomeSelection::Flashblocks => default_subscribe(&chain_config).await?,
HomeSelection::Quit => return Ok(()),
};
if next == NavResult::Quit {
return Ok(());
}
}
Some(Commands::Config) => run_app_with_view(chain_config, ViewId::Config).await,
Some(Commands::Flashblocks) => run_app_with_view(chain_config, ViewId::Flashblocks).await,
Some(Commands::Da) => run_app_with_view(chain_config, ViewId::DaMonitor).await,
Some(Commands::CommandCenter) => {
run_app_with_view(chain_config, ViewId::CommandCenter).await
}
None => run_app(chain_config).await,
}
}
2 changes: 1 addition & 1 deletion crates/basectl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ license.workspace = true
workspace = true

[dependencies]
clap = { workspace = true }
tokio = { workspace = true }
serde_json = { workspace = true }
tokio-tungstenite = { workspace = true }
Expand All @@ -27,3 +26,4 @@ url = { workspace = true }
alloy-primitives = { workspace = true }
alloy-sol-types = { workspace = true }
alloy-contract = { workspace = true }
arboard = { workspace = true }
8 changes: 8 additions & 0 deletions crates/basectl/src/app/action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use super::ViewId;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Action {
None,
Quit,
SwitchView(ViewId),
}
118 changes: 118 additions & 0 deletions crates/basectl/src/app/core.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::io::Stdout;

use anyhow::Result;
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use ratatui::prelude::*;

use super::{Action, Resources, Router, View, ViewId};
use crate::{
commands::common::EVENT_POLL_TIMEOUT,
tui::{AppFrame, restore_terminal, setup_terminal},
};

#[derive(Debug)]
pub struct App {
router: Router,
resources: Resources,
show_help: bool,
}

impl App {
pub const fn new(resources: Resources, initial_view: ViewId) -> Self {
Self { router: Router::new(initial_view), resources, show_help: false }
}

pub async fn run<F>(mut self, mut view_factory: F) -> Result<()>
where
F: FnMut(ViewId) -> Box<dyn View>,
{
let mut terminal = setup_terminal()?;
let result = self.run_loop(&mut terminal, &mut view_factory).await;
restore_terminal(&mut terminal)?;
result
}

async fn run_loop<F>(
&mut self,
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
view_factory: &mut F,
) -> Result<()>
where
F: FnMut(ViewId) -> Box<dyn View>,
{
let mut current_view = view_factory(self.router.current());

loop {
self.resources.da.poll();
self.resources.flash.poll();
self.resources.poll_sys_config();

let action = current_view.tick(&mut self.resources);
if self.handle_action(action, &mut current_view, view_factory) {
break;
}

terminal.draw(|frame| {
let layout = AppFrame::split_layout(frame.area(), self.show_help);
current_view.render(frame, layout.content, &self.resources);
AppFrame::render(
frame,
&layout,
self.resources.chain_name(),
current_view.keybindings(),
);
})?;

if event::poll(EVENT_POLL_TIMEOUT)?
&& let Event::Key(key) = event::read()?
{
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
break;
}

let action = match key.code {
KeyCode::Char('?') => {
self.show_help = !self.show_help;
Action::None
}
KeyCode::Char('q') => Action::Quit,
KeyCode::Esc => {
if self.router.current() == ViewId::Home {
Action::Quit
} else {
Action::SwitchView(ViewId::Home)
}
}
_ => current_view.handle_key(key, &mut self.resources),
};

if self.handle_action(action, &mut current_view, view_factory) {
break;
}
}
}

Ok(())
}

fn handle_action<F>(
&mut self,
action: Action,
current_view: &mut Box<dyn View>,
view_factory: &mut F,
) -> bool
where
F: FnMut(ViewId) -> Box<dyn View>,
{
match action {
Action::None => false,
Action::Quit => true,
Action::SwitchView(view_id) => {
self.router.switch_to(view_id);
*current_view = view_factory(view_id);
self.show_help = false;
false
}
}
}
}
15 changes: 15 additions & 0 deletions crates/basectl/src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
mod action;
mod core;
mod resources;
mod router;
mod runner;
mod view;
pub mod views;

pub use core::App;

pub use action::Action;
pub use resources::{DaState, FlashState, Resources};
pub use router::{Router, ViewId};
pub use runner::{run_app, run_app_with_view};
pub use view::View;
Loading