add simple ui implementation for docker
This commit is contained in:
+4
-31
@@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::proxy::ProxyType;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum AppEvent {
|
||||
GeoDbTotal(Option<u64>),
|
||||
GeoDbDownloaded(usize),
|
||||
@@ -17,36 +16,10 @@ pub(crate) enum AppEvent {
|
||||
}
|
||||
|
||||
pub(crate) enum Event {
|
||||
#[cfg(feature = "tui")]
|
||||
Tick,
|
||||
#[cfg(feature = "tui")]
|
||||
Crossterm(crossterm::event::Event),
|
||||
#[allow(dead_code)]
|
||||
App(AppEvent),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum AppMode {
|
||||
#[default]
|
||||
Running,
|
||||
Done,
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AppState {
|
||||
pub(crate) mode: AppMode,
|
||||
|
||||
pub(crate) geodb_total: u64,
|
||||
pub(crate) geodb_downloaded: usize,
|
||||
|
||||
pub(crate) sources_total: HashMap<ProxyType, usize>,
|
||||
pub(crate) sources_scraped: HashMap<ProxyType, usize>,
|
||||
|
||||
pub(crate) proxies_total: HashMap<ProxyType, usize>,
|
||||
pub(crate) proxies_checked: HashMap<ProxyType, usize>,
|
||||
pub(crate) proxies_working: HashMap<ProxyType, usize>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn new() -> Self {
|
||||
AppState::default()
|
||||
}
|
||||
}
|
||||
|
||||
+12
-21
@@ -9,8 +9,9 @@ pub(crate) mod proxy;
|
||||
pub(crate) mod raw_config;
|
||||
pub(crate) mod scraper;
|
||||
pub(crate) mod storage;
|
||||
pub(crate) mod tui;
|
||||
pub(crate) mod ui;
|
||||
pub(crate) mod utils;
|
||||
use ui::UI;
|
||||
|
||||
pub(crate) const APP_DIRECTORY_NAME: &str = "proxy_scraper_checker";
|
||||
pub(crate) const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; \
|
||||
@@ -31,9 +32,16 @@ fn create_reqwest_client() -> reqwest::Result<reqwest::Client> {
|
||||
.build()
|
||||
}
|
||||
|
||||
async fn run(terminal: ratatui::DefaultTerminal) -> color_eyre::Result<()> {
|
||||
#[tokio::main]
|
||||
async fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::eyre::Context::wrap_err(
|
||||
color_eyre::install(),
|
||||
"failed to install color_eyre hooks",
|
||||
)?;
|
||||
let ui_impl = ui::UIImpl::new()?;
|
||||
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let ui_task = tokio::task::spawn(tui::run(terminal, tx.clone(), rx));
|
||||
let ui_task = tokio::task::spawn(ui_impl.run(tx.clone(), rx));
|
||||
|
||||
let http_client = color_eyre::eyre::Context::wrap_err(
|
||||
create_reqwest_client(),
|
||||
@@ -52,7 +60,7 @@ async fn run(terminal: ratatui::DefaultTerminal) -> color_eyre::Result<()> {
|
||||
)?);
|
||||
|
||||
if !config.debug {
|
||||
tui_logger::set_default_level(log::LevelFilter::Info);
|
||||
ui::UIImpl::set_log_level(log::LevelFilter::Info);
|
||||
}
|
||||
|
||||
let maybe_geodb_task = if config.enable_geolocation {
|
||||
@@ -96,20 +104,3 @@ async fn run(terminal: ratatui::DefaultTerminal) -> color_eyre::Result<()> {
|
||||
ui_task.await??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> color_eyre::Result<()> {
|
||||
color_eyre::eyre::Context::wrap_err(
|
||||
color_eyre::install(),
|
||||
"failed to install color_eyre hooks",
|
||||
)?;
|
||||
color_eyre::eyre::Context::wrap_err(
|
||||
tui_logger::init_logger(log::LevelFilter::Debug),
|
||||
"failed to initialize logger",
|
||||
)?;
|
||||
tui_logger::set_default_level(log::LevelFilter::Debug);
|
||||
let terminal = ratatui::init();
|
||||
let result = run(terminal).await;
|
||||
ratatui::restore();
|
||||
result
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::event::Event;
|
||||
|
||||
pub(crate) struct DummyUI {}
|
||||
|
||||
impl super::UI for DummyUI {
|
||||
fn new() -> color_eyre::Result<Self> {
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
fn set_log_level(_log_level: log::LevelFilter) {}
|
||||
|
||||
async fn run(
|
||||
self,
|
||||
_tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
mut rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
||||
) -> color_eyre::Result<()> {
|
||||
while rx.recv().await.is_some() {}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::event::Event;
|
||||
|
||||
pub(crate) struct LoggerUI {}
|
||||
|
||||
impl super::UI for LoggerUI {
|
||||
fn new() -> color_eyre::Result<Self> {
|
||||
env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init();
|
||||
Ok(Self {})
|
||||
}
|
||||
|
||||
fn set_log_level(log_level: log::LevelFilter) {
|
||||
log::set_max_level(log_level);
|
||||
}
|
||||
|
||||
async fn run(
|
||||
self,
|
||||
_tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
mut rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
||||
) -> color_eyre::Result<()> {
|
||||
while rx.recv().await.is_some() {}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::event::Event;
|
||||
|
||||
pub(crate) trait UI {
|
||||
fn new() -> color_eyre::Result<Self>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
|
||||
fn set_log_level(log_level: log::LevelFilter);
|
||||
|
||||
async fn run(
|
||||
self,
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
||||
) -> color_eyre::Result<()>;
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(feature="tui", feature="logger"))] {
|
||||
compile_error!("feature \"logger\" and feature \"tui\" cannot be enaled at the same time");
|
||||
} else if #[cfg(feature="tui" )] {
|
||||
mod tui;
|
||||
pub(crate) use self::tui::Tui as UIImpl;
|
||||
} else if #[cfg(feature = "logger")] {
|
||||
mod logger;
|
||||
pub(crate) use self::logger::LoggerUI as UIImpl;
|
||||
} else {
|
||||
mod dummy;
|
||||
pub(crate) use self::dummy::DummyUI as UIImpl;
|
||||
}
|
||||
}
|
||||
+97
-31
@@ -1,10 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use color_eyre::eyre::WrapErr;
|
||||
use crossterm::event::{
|
||||
Event as CrosstermEvent, KeyCode, KeyModifiers, MouseEventKind,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
DefaultTerminal, Frame,
|
||||
layout::{Alignment, Constraint, Direction, Flex, Layout},
|
||||
style::{Color, Style},
|
||||
text::{Line, Text},
|
||||
@@ -13,13 +15,96 @@ use ratatui::{
|
||||
use tui_logger::{TuiLoggerWidget, TuiWidgetEvent, TuiWidgetState};
|
||||
|
||||
use crate::{
|
||||
event::{AppEvent, AppMode, AppState, Event},
|
||||
event::{AppEvent, Event},
|
||||
proxy::ProxyType,
|
||||
utils::is_docker,
|
||||
};
|
||||
|
||||
const FPS: f64 = 30.0;
|
||||
|
||||
pub(crate) struct Tui {
|
||||
terminal: DefaultTerminal,
|
||||
}
|
||||
|
||||
impl super::UI for Tui {
|
||||
fn new() -> color_eyre::Result<Self> {
|
||||
tui_logger::init_logger(log::LevelFilter::Debug)
|
||||
.wrap_err("failed to initialize logger")?;
|
||||
tui_logger::set_default_level(log::LevelFilter::Debug);
|
||||
Ok(Self { terminal: ratatui::init() })
|
||||
}
|
||||
|
||||
fn set_log_level(log_level: log::LevelFilter) {
|
||||
log::set_max_level(log_level);
|
||||
tui_logger::set_default_level(log_level);
|
||||
}
|
||||
|
||||
async fn run(
|
||||
mut self,
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
mut rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
||||
) -> color_eyre::Result<()> {
|
||||
let tick_task = tokio::spawn(tick_event_listener(tx.clone()));
|
||||
let crossterm_task = tokio::spawn(crossterm_event_listener(tx));
|
||||
let mut app_state = AppState::new();
|
||||
let logger_state = TuiWidgetState::default();
|
||||
while !matches!(app_state.mode, AppMode::Quit) {
|
||||
if let Some(event) = rx.recv().await {
|
||||
if handle_event(event, &mut app_state, &logger_state).await {
|
||||
self.terminal
|
||||
.draw(|frame| draw(frame, &app_state, &logger_state))
|
||||
.wrap_err("failed to draw tui")?;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
drop(rx);
|
||||
tick_task.await.wrap_err("failed to spawn tui tick task")??;
|
||||
crossterm_task
|
||||
.await
|
||||
.wrap_err("failed to spawn tui crossterm task")??;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Tui {
|
||||
fn drop(&mut self) {
|
||||
ratatui::restore();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) enum AppMode {
|
||||
#[default]
|
||||
Running,
|
||||
/// Wait for the user confirmation to close the UI
|
||||
Done,
|
||||
/// Close the UI
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct AppState {
|
||||
pub(crate) mode: AppMode,
|
||||
|
||||
pub(crate) geodb_total: u64,
|
||||
pub(crate) geodb_downloaded: usize,
|
||||
|
||||
pub(crate) sources_total: HashMap<ProxyType, usize>,
|
||||
pub(crate) sources_scraped: HashMap<ProxyType, usize>,
|
||||
|
||||
pub(crate) proxies_total: HashMap<ProxyType, usize>,
|
||||
pub(crate) proxies_checked: HashMap<ProxyType, usize>,
|
||||
pub(crate) proxies_working: HashMap<ProxyType, usize>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub(crate) fn new() -> Self {
|
||||
AppState::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn tick_event_listener(
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
) -> color_eyre::Result<()> {
|
||||
@@ -55,33 +140,6 @@ async fn crossterm_event_listener(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(
|
||||
mut terminal: ratatui::DefaultTerminal,
|
||||
tx: tokio::sync::mpsc::UnboundedSender<Event>,
|
||||
mut rx: tokio::sync::mpsc::UnboundedReceiver<Event>,
|
||||
) -> color_eyre::Result<()> {
|
||||
let tick_task = tokio::spawn(tick_event_listener(tx.clone()));
|
||||
let crossterm_task = tokio::spawn(crossterm_event_listener(tx));
|
||||
let mut app_state = AppState::new();
|
||||
let logger_state = TuiWidgetState::default();
|
||||
let is_docker = is_docker().await;
|
||||
while (is_docker && matches!(app_state.mode, AppMode::Running))
|
||||
|| (!is_docker && !matches!(app_state.mode, AppMode::Quit))
|
||||
{
|
||||
if let Some(event) = rx.recv().await {
|
||||
if handle_event(event, &mut app_state, &logger_state) {
|
||||
terminal
|
||||
.draw(|frame| draw(frame, &app_state, &logger_state))
|
||||
.wrap_err("failed to draw tui")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(rx);
|
||||
tick_task.await.wrap_err("failed to spawn tui tick task")??;
|
||||
crossterm_task.await.wrap_err("failed to spawn tui crossterm task")??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
@@ -250,7 +308,11 @@ fn draw(f: &mut Frame, state: &AppState, logger_state: &TuiWidgetState) {
|
||||
f.render_widget(table, result_layout[1]);
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
async fn is_interactive() -> bool {
|
||||
is_docker().await
|
||||
}
|
||||
|
||||
async fn handle_event(
|
||||
event: Event,
|
||||
state: &mut AppState,
|
||||
logger_state: &TuiWidgetState,
|
||||
@@ -318,7 +380,11 @@ fn handle_event(
|
||||
*state.proxies_working.entry(proxy_type).or_default() += 1;
|
||||
}
|
||||
AppEvent::Done => {
|
||||
state.mode = AppMode::Done;
|
||||
state.mode = if is_interactive().await {
|
||||
AppMode::Quit
|
||||
} else {
|
||||
AppMode::Done
|
||||
};
|
||||
}
|
||||
}
|
||||
false
|
||||
Reference in New Issue
Block a user