PageRenderTime 48ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main.rs

https://gitlab.com/guodman/mux-launcher
Rust | 294 lines | 273 code | 21 blank | 0 comment | 33 complexity | 17f818c02be00d5586c74059f01dc37c MD5 | raw file
  1. extern crate rustbox;
  2. use rustbox::RustBox;
  3. use rustbox::Color;
  4. use rustbox::Style;
  5. use rustbox::keyboard::Key;
  6. use std::process::Command;
  7. use std::process::ChildStdout;
  8. use std::process::Stdio;
  9. use std::io::Read;
  10. use std::iter::Iterator;
  11. #[derive(Clone)]
  12. struct LxcContainer {
  13. state: ContainerState,
  14. name: String,
  15. details: Option<ContainerDetails>,
  16. }
  17. #[derive(Clone)]
  18. struct ContainerDetails {
  19. ip: Option<String>,
  20. disk_use: Option<String>,
  21. mem_use: Option<String>,
  22. }
  23. #[derive(Clone)]
  24. enum ContainerState {
  25. Running,
  26. Stopped,
  27. }
  28. fn main() {
  29. let rustbox = match RustBox::init(Default::default()) {
  30. Result::Ok(v) => v,
  31. Result::Err(e) => panic!("{}", e),
  32. };
  33. let running_containers = refresh_containers(ContainerState::Running);
  34. let stopped_conatiners = refresh_containers(ContainerState::Stopped);
  35. let mut gui: GuiList = GuiList {
  36. selected: 0,
  37. selected_detail: None,
  38. running: running_containers,
  39. stopped: stopped_conatiners,
  40. };
  41. let mut needs_refresh: bool = false;
  42. let mut should_stop: bool = false;
  43. while !should_stop {
  44. if needs_refresh {
  45. gui = GuiList {
  46. selected: gui.selected,
  47. selected_detail: None,
  48. running: refresh_containers(ContainerState::Running),
  49. stopped: refresh_containers(ContainerState::Stopped),
  50. };
  51. needs_refresh = false;
  52. }
  53. rustbox.clear();
  54. rustbox.draw_container_list(&gui);
  55. rustbox.draw_details(&gui);
  56. rustbox.present();
  57. match rustbox.poll_event(false) {
  58. Ok(rustbox::Event::KeyEvent(key)) => {
  59. match key {
  60. Key::Char('r') => needs_refresh = true,
  61. Key::Up => {
  62. match gui.selected_detail {
  63. Some(sel) => if sel > 0 { gui.selected_detail = Some(sel - 1) },
  64. _ => if gui.selected > 0 { gui.selected = gui.selected - 1 },
  65. }
  66. },
  67. Key::Down => {
  68. match gui.selected_detail {
  69. Some(sel) => {
  70. let max = {
  71. let container = gui.get_selected().unwrap();
  72. container.state.actions(container).len() - 1
  73. };
  74. if sel < max {
  75. gui.selected_detail = Some(sel + 1);
  76. }
  77. },
  78. _ => if gui.selected < gui.len() - 1 { gui.selected = gui.selected + 1 },
  79. }
  80. },
  81. Key::Right => if gui.selected_detail.is_none() { gui.selected_detail = Some(0) },
  82. Key::Left => gui.selected_detail = None,
  83. Key::Enter => {
  84. if let Some(sel) = gui.selected_detail {
  85. let container = gui.get_selected().unwrap();
  86. if let Some(action) = container.state.actions(container).get(sel) {
  87. let _ = Command::new("sh")
  88. .arg("-c")
  89. .arg(&action.cmd)
  90. .stdout(Stdio::null())
  91. .stderr(Stdio::null())
  92. .spawn()
  93. .expect(&format!("Unable to run the command for {}", action.name));
  94. }
  95. needs_refresh = true;
  96. }
  97. },
  98. Key::Char('q') => should_stop = true,
  99. Key::Esc => should_stop = true,
  100. _ => {},
  101. }
  102. },
  103. _ => {},
  104. }
  105. }
  106. }
  107. trait ScreenWriter {
  108. fn draw_container_list(&self, gui: &GuiList);
  109. fn draw_details(&self, gui: &GuiList);
  110. }
  111. fn style() -> Style { Style::empty() }
  112. const FG: Color = Color::White;
  113. const BG: Color = Color::Black;
  114. impl ScreenWriter for RustBox {
  115. fn draw_container_list(&self, gui: &GuiList) {
  116. let style = style();
  117. let y_start = 2;
  118. let mut y = y_start;
  119. let x = 2;
  120. for c in &gui.running {
  121. if gui.selected == y - y_start {
  122. self.print(x, y, style, BG, FG, &c.name);
  123. } else {
  124. self.print(x, y, style, FG, BG, &c.name);
  125. }
  126. y += 1;
  127. }
  128. let y_spacer = 1;
  129. y = y + y_spacer;
  130. for c in &gui.stopped {
  131. if gui.selected == y - (y_start + y_spacer) {
  132. self.print(x, y, style, BG, FG, &c.name);
  133. } else {
  134. self.print(x, y, style, FG, BG, &c.name);
  135. }
  136. y += 1;
  137. }
  138. }
  139. fn draw_details(&self, gui: &GuiList) {
  140. let container = gui.get_selected().unwrap();
  141. let indent = gui.widest_name() + 4;
  142. let style = style();
  143. let x = indent;
  144. let mut y = 2;
  145. self.print(x, y, style, FG, BG, &format!("Details for {}", container.name).to_string());
  146. y += 2;
  147. for details in container.details.iter() {
  148. for ip in &details.ip {
  149. self.print(x, y, style, FG, BG, &format!("IP: {}", ip));
  150. y += 1;
  151. }
  152. for disk_use in &details.disk_use {
  153. self.print(x, y, style, FG, BG, &format!("Disk: {}", disk_use));
  154. y += 1;
  155. }
  156. for mem_use in &details.mem_use {
  157. self.print(x, y, style, FG, BG, &format!("Mem: {}", mem_use));
  158. y += 1;
  159. }
  160. }
  161. y += 1;
  162. let mut i = 0;
  163. for cmd in container.state.actions(container) {
  164. match gui.selected_detail {
  165. Some(sel) if sel == i => self.print(x, y, style, BG, FG, &cmd.name),
  166. _ => self.print(x, y, style, FG, BG, &cmd.name),
  167. }
  168. y += 1;
  169. i += 1;
  170. }
  171. }
  172. }
  173. fn refresh_containers(state: ContainerState) -> Vec<LxcContainer> {
  174. let containers_cmd = Command::new("sh")
  175. .arg("-c")
  176. .arg(format!("sudo lxc-ls -1 {}", state.flag()))
  177. .stdout(Stdio::piped())
  178. .spawn()
  179. .expect("Unable to get containers");
  180. let mut containers_str: String = String::new();
  181. let _ = containers_cmd.stdout.map(|mut item: ChildStdout| item.read_to_string(&mut containers_str));
  182. let split = containers_str.split_whitespace();
  183. split.map(|line| LxcContainer::new(line.to_string(), state.clone())).collect::<Vec<LxcContainer>>()
  184. }
  185. impl LxcContainer {
  186. fn new(name: String, state: ContainerState) -> LxcContainer {
  187. let mut ct = LxcContainer {
  188. name: name,
  189. state: state,
  190. details: None,
  191. };
  192. ct.generate_details();
  193. ct
  194. }
  195. fn generate_details(&mut self) {
  196. let cmd = Command::new("sh")
  197. .arg("-c")
  198. .arg(format!("sudo lxc-info -n {}", self.name))
  199. .stdout(Stdio::piped())
  200. .spawn()
  201. .expect("Unable to get details");
  202. let mut info: String = String::new();
  203. let _ = cmd.stdout.map(|mut item: ChildStdout| item.read_to_string(&mut info));
  204. let split = info.split("\n");
  205. let mut details = ContainerDetails {
  206. ip: None,
  207. disk_use: None,
  208. mem_use: None,
  209. };
  210. for line in split {
  211. if line.starts_with("IP:") {
  212. details.ip = Some(line.split_at(3).1.trim().to_string());
  213. } else if line.starts_with("BlkIO use:") {
  214. details.disk_use = Some(line.split_at(10).1.trim().to_string());
  215. } else if line.starts_with("Memory use:") {
  216. details.mem_use = Some(line.split_at(11).1.trim().to_string());
  217. }
  218. }
  219. self.details = Some(details);
  220. }
  221. }
  222. impl ContainerState {
  223. fn flag(&self) -> String {
  224. match *self {
  225. ContainerState::Running => "--active".to_string(),
  226. ContainerState::Stopped => "--stopped".to_string(),
  227. }
  228. }
  229. fn actions(&self, container: &LxcContainer) -> Vec<CtAction> {
  230. match *self {
  231. ContainerState::Running => {
  232. vec!(
  233. CtAction{ name: "Attach".to_string(), cmd: format!("tmux new-window -n {} -- sudo lxc-attach -n {}", container.name, container.name).to_string(), },
  234. CtAction{ name: "Stop".to_string(), cmd: format!("sudo lxc-stop -n {}", container.name).to_string(), },
  235. )
  236. },
  237. ContainerState::Stopped => {
  238. vec!(
  239. CtAction{ name: "Start".to_string(), cmd: format!("sudo lxc-start -n {}", container.name).to_string(), },
  240. )
  241. },
  242. }
  243. }
  244. }
  245. struct CtAction {
  246. name: String,
  247. cmd: String,
  248. }
  249. struct GuiList {
  250. selected: usize,
  251. selected_detail: Option<usize>,
  252. running: Vec<LxcContainer>,
  253. stopped: Vec<LxcContainer>,
  254. }
  255. impl GuiList {
  256. fn len(&self) -> usize {
  257. self.running.len() + self.stopped.len()
  258. }
  259. fn widest_name(&self) -> usize {
  260. use std::cmp::max;
  261. max(
  262. self.running.iter().fold(0, |maxv, value| { max(maxv, value.name.chars().count()) }),
  263. self.stopped.iter().fold(0, |maxv, value| { max(maxv, value.name.chars().count()) }),
  264. )
  265. }
  266. fn get_selected(&self) -> Option<&LxcContainer> {
  267. if self.selected < self.running.len() {
  268. self.running.get(self.selected)
  269. } else {
  270. self.stopped.get(self.selected - self.running.len())
  271. }
  272. }
  273. }