/src/main.rs
Rust | 294 lines | 273 code | 21 blank | 0 comment | 33 complexity | 17f818c02be00d5586c74059f01dc37c MD5 | raw file
- extern crate rustbox;
- use rustbox::RustBox;
- use rustbox::Color;
- use rustbox::Style;
- use rustbox::keyboard::Key;
- use std::process::Command;
- use std::process::ChildStdout;
- use std::process::Stdio;
- use std::io::Read;
- use std::iter::Iterator;
- #[derive(Clone)]
- struct LxcContainer {
- state: ContainerState,
- name: String,
- details: Option<ContainerDetails>,
- }
- #[derive(Clone)]
- struct ContainerDetails {
- ip: Option<String>,
- disk_use: Option<String>,
- mem_use: Option<String>,
- }
- #[derive(Clone)]
- enum ContainerState {
- Running,
- Stopped,
- }
- fn main() {
- let rustbox = match RustBox::init(Default::default()) {
- Result::Ok(v) => v,
- Result::Err(e) => panic!("{}", e),
- };
- let running_containers = refresh_containers(ContainerState::Running);
- let stopped_conatiners = refresh_containers(ContainerState::Stopped);
- let mut gui: GuiList = GuiList {
- selected: 0,
- selected_detail: None,
- running: running_containers,
- stopped: stopped_conatiners,
- };
- let mut needs_refresh: bool = false;
- let mut should_stop: bool = false;
- while !should_stop {
- if needs_refresh {
- gui = GuiList {
- selected: gui.selected,
- selected_detail: None,
- running: refresh_containers(ContainerState::Running),
- stopped: refresh_containers(ContainerState::Stopped),
- };
- needs_refresh = false;
- }
- rustbox.clear();
- rustbox.draw_container_list(&gui);
- rustbox.draw_details(&gui);
- rustbox.present();
- match rustbox.poll_event(false) {
- Ok(rustbox::Event::KeyEvent(key)) => {
- match key {
- Key::Char('r') => needs_refresh = true,
- Key::Up => {
- match gui.selected_detail {
- Some(sel) => if sel > 0 { gui.selected_detail = Some(sel - 1) },
- _ => if gui.selected > 0 { gui.selected = gui.selected - 1 },
- }
- },
- Key::Down => {
- match gui.selected_detail {
- Some(sel) => {
- let max = {
- let container = gui.get_selected().unwrap();
- container.state.actions(container).len() - 1
- };
- if sel < max {
- gui.selected_detail = Some(sel + 1);
- }
- },
- _ => if gui.selected < gui.len() - 1 { gui.selected = gui.selected + 1 },
- }
- },
- Key::Right => if gui.selected_detail.is_none() { gui.selected_detail = Some(0) },
- Key::Left => gui.selected_detail = None,
- Key::Enter => {
- if let Some(sel) = gui.selected_detail {
- let container = gui.get_selected().unwrap();
- if let Some(action) = container.state.actions(container).get(sel) {
- let _ = Command::new("sh")
- .arg("-c")
- .arg(&action.cmd)
- .stdout(Stdio::null())
- .stderr(Stdio::null())
- .spawn()
- .expect(&format!("Unable to run the command for {}", action.name));
- }
- needs_refresh = true;
- }
- },
- Key::Char('q') => should_stop = true,
- Key::Esc => should_stop = true,
- _ => {},
- }
- },
- _ => {},
- }
- }
- }
- trait ScreenWriter {
- fn draw_container_list(&self, gui: &GuiList);
- fn draw_details(&self, gui: &GuiList);
- }
- fn style() -> Style { Style::empty() }
- const FG: Color = Color::White;
- const BG: Color = Color::Black;
- impl ScreenWriter for RustBox {
- fn draw_container_list(&self, gui: &GuiList) {
- let style = style();
- let y_start = 2;
- let mut y = y_start;
- let x = 2;
- for c in &gui.running {
- if gui.selected == y - y_start {
- self.print(x, y, style, BG, FG, &c.name);
- } else {
- self.print(x, y, style, FG, BG, &c.name);
- }
- y += 1;
- }
- let y_spacer = 1;
- y = y + y_spacer;
- for c in &gui.stopped {
- if gui.selected == y - (y_start + y_spacer) {
- self.print(x, y, style, BG, FG, &c.name);
- } else {
- self.print(x, y, style, FG, BG, &c.name);
- }
- y += 1;
- }
- }
- fn draw_details(&self, gui: &GuiList) {
- let container = gui.get_selected().unwrap();
- let indent = gui.widest_name() + 4;
- let style = style();
- let x = indent;
- let mut y = 2;
- self.print(x, y, style, FG, BG, &format!("Details for {}", container.name).to_string());
- y += 2;
- for details in container.details.iter() {
- for ip in &details.ip {
- self.print(x, y, style, FG, BG, &format!("IP: {}", ip));
- y += 1;
- }
- for disk_use in &details.disk_use {
- self.print(x, y, style, FG, BG, &format!("Disk: {}", disk_use));
- y += 1;
- }
- for mem_use in &details.mem_use {
- self.print(x, y, style, FG, BG, &format!("Mem: {}", mem_use));
- y += 1;
- }
- }
- y += 1;
- let mut i = 0;
- for cmd in container.state.actions(container) {
- match gui.selected_detail {
- Some(sel) if sel == i => self.print(x, y, style, BG, FG, &cmd.name),
- _ => self.print(x, y, style, FG, BG, &cmd.name),
- }
- y += 1;
- i += 1;
- }
- }
- }
- fn refresh_containers(state: ContainerState) -> Vec<LxcContainer> {
- let containers_cmd = Command::new("sh")
- .arg("-c")
- .arg(format!("sudo lxc-ls -1 {}", state.flag()))
- .stdout(Stdio::piped())
- .spawn()
- .expect("Unable to get containers");
- let mut containers_str: String = String::new();
- let _ = containers_cmd.stdout.map(|mut item: ChildStdout| item.read_to_string(&mut containers_str));
- let split = containers_str.split_whitespace();
- split.map(|line| LxcContainer::new(line.to_string(), state.clone())).collect::<Vec<LxcContainer>>()
- }
- impl LxcContainer {
- fn new(name: String, state: ContainerState) -> LxcContainer {
- let mut ct = LxcContainer {
- name: name,
- state: state,
- details: None,
- };
- ct.generate_details();
- ct
- }
- fn generate_details(&mut self) {
- let cmd = Command::new("sh")
- .arg("-c")
- .arg(format!("sudo lxc-info -n {}", self.name))
- .stdout(Stdio::piped())
- .spawn()
- .expect("Unable to get details");
- let mut info: String = String::new();
- let _ = cmd.stdout.map(|mut item: ChildStdout| item.read_to_string(&mut info));
- let split = info.split("\n");
- let mut details = ContainerDetails {
- ip: None,
- disk_use: None,
- mem_use: None,
- };
- for line in split {
- if line.starts_with("IP:") {
- details.ip = Some(line.split_at(3).1.trim().to_string());
- } else if line.starts_with("BlkIO use:") {
- details.disk_use = Some(line.split_at(10).1.trim().to_string());
- } else if line.starts_with("Memory use:") {
- details.mem_use = Some(line.split_at(11).1.trim().to_string());
- }
- }
- self.details = Some(details);
- }
- }
- impl ContainerState {
- fn flag(&self) -> String {
- match *self {
- ContainerState::Running => "--active".to_string(),
- ContainerState::Stopped => "--stopped".to_string(),
- }
- }
- fn actions(&self, container: &LxcContainer) -> Vec<CtAction> {
- match *self {
- ContainerState::Running => {
- vec!(
- CtAction{ name: "Attach".to_string(), cmd: format!("tmux new-window -n {} -- sudo lxc-attach -n {}", container.name, container.name).to_string(), },
- CtAction{ name: "Stop".to_string(), cmd: format!("sudo lxc-stop -n {}", container.name).to_string(), },
- )
- },
- ContainerState::Stopped => {
- vec!(
- CtAction{ name: "Start".to_string(), cmd: format!("sudo lxc-start -n {}", container.name).to_string(), },
- )
- },
- }
- }
- }
- struct CtAction {
- name: String,
- cmd: String,
- }
- struct GuiList {
- selected: usize,
- selected_detail: Option<usize>,
- running: Vec<LxcContainer>,
- stopped: Vec<LxcContainer>,
- }
- impl GuiList {
- fn len(&self) -> usize {
- self.running.len() + self.stopped.len()
- }
- fn widest_name(&self) -> usize {
- use std::cmp::max;
- max(
- self.running.iter().fold(0, |maxv, value| { max(maxv, value.name.chars().count()) }),
- self.stopped.iter().fold(0, |maxv, value| { max(maxv, value.name.chars().count()) }),
- )
- }
- fn get_selected(&self) -> Option<&LxcContainer> {
- if self.selected < self.running.len() {
- self.running.get(self.selected)
- } else {
- self.stopped.get(self.selected - self.running.len())
- }
- }
- }