• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kdash-rs / kdash / 3750513782

pending completion
3750513782

push

github

Deepu
fix tests

982 of 4441 branches covered (22.11%)

Branch coverage included in aggregate %.

2731 of 4724 relevant lines covered (57.81%)

8.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.37
/src/main.rs
1
#![warn(rust_2018_idioms)]
52✔
2
#[deny(clippy::shadow_unrelated)]
3
mod app;
4
mod banner;
5
mod cmd;
6
mod event;
7
mod handlers;
8
mod network;
9
mod ui;
10

11
use std::{
12
  io::{self, stdout, Stdout},
13
  panic::{self, PanicInfo},
14
  sync::Arc,
15
};
16

17
use anyhow::{anyhow, Result};
18

19
use app::App;
20
use banner::BANNER;
21
use clap::Parser;
22
use cmd::{CmdRunner, IoCmdEvent};
23
use crossterm::{
24
  execute,
25
  terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
26
};
27
use event::Key;
28
use network::{
29
  get_client,
30
  stream::{IoStreamEvent, NetworkStream},
31
  IoEvent, Network,
32
};
33
use tokio::sync::{mpsc, Mutex};
34
use tui::{
35
  backend::{Backend, CrosstermBackend},
36
  Terminal,
37
};
38

39
/// kdash CLI
40
#[derive(Parser, Debug)]
×
41
#[command(author, version, about, long_about = None, override_usage = "Press `?` while running the app to see keybindings", before_help = BANNER)]
×
42
pub struct Cli {
43
  /// Set the tick rate (milliseconds): the lower the number the higher the FPS.
×
44
  #[arg(short, long, value_parser, default_value_t = 250)]
×
45
  pub tick_rate: u64,
×
46
  /// Set the network call polling rate (milliseconds, should be multiples of tick-rate):
×
47
  /// the lower the number the higher the network calls.
48
  #[arg(short, long, value_parser, default_value_t = 5000)]
×
49
  pub poll_rate: u64,
×
50
  /// whether unicode symbols are used to improve the overall look of the app
×
51
  #[arg(short, long, value_parser, default_value_t = true)]
×
52
  pub enhanced_graphics: bool,
×
53
}
54

55
#[tokio::main]
56
async fn main() -> Result<()> {
×
57
  panic::set_hook(Box::new(|info| {
×
58
    panic_hook(info);
×
59
  }));
×
60

61
  // parse CLI arguments
62
  let cli = Cli::parse();
×
63

64
  if cli.tick_rate >= 1000 {
×
65
    panic!("Tick rate must be below 1000");
×
66
  }
67
  if (cli.poll_rate % cli.tick_rate) > 0u64 {
×
68
    panic!("Poll rate must be multiple of tick-rate");
×
69
  }
70

71
  // channels for communication between network/cmd threads & UI thread
72
  let (sync_io_tx, sync_io_rx) = mpsc::channel::<IoEvent>(500);
×
73
  let (sync_io_stream_tx, sync_io_stream_rx) = mpsc::channel::<IoStreamEvent>(500);
×
74
  let (sync_io_cmd_tx, sync_io_cmd_rx) = mpsc::channel::<IoCmdEvent>(500);
×
75

76
  // Initialize app state
77
  let app = Arc::new(Mutex::new(App::new(
×
78
    sync_io_tx,
79
    sync_io_stream_tx,
80
    sync_io_cmd_tx,
81
    cli.enhanced_graphics,
×
82
    cli.poll_rate / cli.tick_rate,
×
83
  )));
84

85
  // make copies for the network/cli threads
86
  let app_nw = Arc::clone(&app);
×
87
  let app_stream = Arc::clone(&app);
×
88
  let app_cli = Arc::clone(&app);
×
89

90
  // Launch network thread
91
  std::thread::spawn(move || {
×
92
    start_network(sync_io_rx, &app_nw);
×
93
  });
×
94
  // Launch network thread for streams
95
  std::thread::spawn(move || {
×
96
    start_stream_network(sync_io_stream_rx, &app_stream);
×
97
  });
×
98
  // Launch thread for cmd runner
99
  std::thread::spawn(move || {
×
100
    start_cmd_runner(sync_io_cmd_rx, &app_cli);
×
101
  });
×
102
  // Launch the UI asynchronously
103
  // The UI must run in the "main" thread
104
  start_ui(cli, &app).await?;
×
105

106
  Ok(())
×
107
}
×
108

109
#[tokio::main]
110
async fn start_network(mut io_rx: mpsc::Receiver<IoEvent>, app: &Arc<Mutex<App>>) {
×
111
  match get_client(None).await {
×
112
    Ok(client) => {
×
113
      let mut network = Network::new(client, app);
×
114

115
      while let Some(io_event) = io_rx.recv().await {
×
116
        network.handle_network_event(io_event).await;
×
117
      }
118
    }
×
119
    Err(e) => {
×
120
      let mut app = app.lock().await;
×
121
      app.handle_error(anyhow!("Unable to obtain Kubernetes client. {:?}", e));
×
122
    }
×
123
  }
×
124
}
×
125

126
#[tokio::main]
127
async fn start_stream_network(mut io_rx: mpsc::Receiver<IoStreamEvent>, app: &Arc<Mutex<App>>) {
×
128
  match get_client(None).await {
×
129
    Ok(client) => {
×
130
      let mut network = NetworkStream::new(client, app);
×
131

132
      while let Some(io_event) = io_rx.recv().await {
×
133
        network.handle_network_stream_event(io_event).await;
×
134
      }
135
    }
×
136
    Err(e) => {
×
137
      let mut app = app.lock().await;
×
138
      app.handle_error(anyhow!("Unable to obtain Kubernetes client. {:?}", e));
×
139
    }
×
140
  }
×
141
}
×
142

143
#[tokio::main]
144
async fn start_cmd_runner(mut io_rx: mpsc::Receiver<IoCmdEvent>, app: &Arc<Mutex<App>>) {
×
145
  let mut cmd = CmdRunner::new(app);
×
146

147
  while let Some(io_event) = io_rx.recv().await {
×
148
    cmd.handle_cmd_event(io_event).await;
×
149
  }
×
150
}
×
151

152
async fn start_ui(cli: Cli, app: &Arc<Mutex<App>>) -> Result<()> {
×
153
  // Terminal initialization
154
  let mut stdout = stdout();
×
155
  // not capturing mouse to make text select/copy possible
156
  execute!(stdout, EnterAlternateScreen)?;
×
157
  // see https://docs.rs/crossterm/0.17.7/crossterm/terminal/#raw-mode
158
  enable_raw_mode()?;
×
159
  // terminal backend for cross platform support
160
  let backend = CrosstermBackend::new(stdout);
×
161
  let mut terminal = Terminal::new(backend)?;
×
162
  terminal.clear()?;
×
163
  terminal.hide_cursor()?;
×
164
  // custom events
165
  let events = event::Events::new(cli.tick_rate);
×
166
  let mut is_first_render = true;
×
167
  // main UI loop
168
  loop {
×
169
    let mut app = app.lock().await;
×
170
    // Get the size of the screen on each loop to account for resize event
171
    if let Ok(size) = terminal.backend().size() {
×
172
      // Reset the help menu if the terminal was resized
173
      if app.refresh || app.size != size {
×
174
        app.size = size;
×
175

176
        // Based on the size of the terminal, adjust how many cols are
177
        // displayed in the tables
178
        if app.size.width > 8 {
×
179
          app.table_cols = app.size.width - 1;
×
180
        } else {
181
          app.table_cols = 2;
×
182
        }
183
      }
184
    };
×
185

186
    // draw the UI layout
187
    terminal.draw(|f| ui::draw(f, &mut app))?;
×
188

189
    // handle key events
190
    match events.next()? {
×
191
      event::Event::Input(key) => {
×
192
        // quit on CTRL + C
193
        if key == Key::Ctrl('c') {
×
194
          break;
195
        }
196
        // handle all other keys
197
        handlers::handle_key_events(key, &mut app).await
×
198
      }
199
      // handle mouse events
200
      event::Event::MouseInput(mouse) => handlers::handle_mouse_events(mouse, &mut app).await,
×
201
      // handle tick events
202
      event::Event::Tick => {
×
203
        app.on_tick(is_first_render).await;
×
204
      }
205
    }
206

207
    is_first_render = false;
×
208

209
    if app.should_quit {
×
210
      break;
211
    }
212
  }
×
213

214
  terminal.show_cursor()?;
×
215
  shutdown(terminal)?;
×
216

217
  Ok(())
×
218
}
×
219

220
// shutdown the CLI and show terminal
221
fn shutdown(mut terminal: Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
×
222
  disable_raw_mode()?;
×
223
  execute!(terminal.backend_mut(), LeaveAlternateScreen,)?;
×
224
  terminal.show_cursor()?;
×
225
  Ok(())
×
226
}
×
227

228
#[cfg(debug_assertions)]
229
fn panic_hook(info: &PanicInfo<'_>) {
×
230
  use backtrace::Backtrace;
231
  use crossterm::style::Print;
232

233
  let location = info.location().unwrap();
×
234

235
  let msg = match info.payload().downcast_ref::<&'static str>() {
×
236
    Some(s) => *s,
×
237
    None => match info.payload().downcast_ref::<String>() {
×
238
      Some(s) => &s[..],
×
239
      None => "Box<Any>",
×
240
    },
×
241
  };
242

243
  let stacktrace: String = format!("{:?}", Backtrace::new()).replace('\n', "\n\r");
×
244

245
  disable_raw_mode().unwrap();
×
246
  execute!(
×
247
    io::stdout(),
×
248
    LeaveAlternateScreen,
249
    Print(format!(
250
      "thread '<unnamed>' panicked at '{}', {}\n\r{}",
251
      msg, location, stacktrace
252
    )),
253
  )
254
  .unwrap();
255
}
×
256

257
#[cfg(not(debug_assertions))]
258
fn panic_hook(info: &PanicInfo<'_>) {
259
  use human_panic::{handle_dump, print_msg, Metadata};
260

261
  let meta = Metadata {
262
    version: env!("CARGO_PKG_VERSION").into(),
263
    name: env!("CARGO_PKG_NAME").into(),
264
    authors: env!("CARGO_PKG_AUTHORS").replace(":", ", ").into(),
265
    homepage: env!("CARGO_PKG_HOMEPAGE").into(),
266
  };
267
  let file_path = handle_dump(&meta, info);
268
  disable_raw_mode().unwrap();
269
  execute!(io::stdout(), LeaveAlternateScreen).unwrap();
270
  print_msg(file_path, &meta).expect("human-panic: printing error message to console failed");
271
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc