mirror of
https://github.com/Noratrieb/service-manager.git
synced 2026-01-14 08:25:04 +01:00
start with cmd
This commit is contained in:
parent
3722a458eb
commit
6d572fc9d0
7 changed files with 170 additions and 37 deletions
49
Cargo.lock
generated
49
Cargo.lock
generated
|
|
@ -165,6 +165,24 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.10"
|
||||
|
|
@ -194,6 +212,20 @@ name = "serde"
|
|||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "service-manager"
|
||||
|
|
@ -241,6 +273,17 @@ version = "1.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.6"
|
||||
|
|
@ -288,6 +331,12 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
crossterm = "0.22.1"
|
||||
serde = "1.0.132"
|
||||
serde = { version = "1.0.132", features = ["derive"] }
|
||||
toml = "0.5.8"
|
||||
tui = { version = "0.16.0", features = ["crossterm"] }
|
||||
|
|
|
|||
|
|
@ -6,4 +6,8 @@ command = "sleep 48590234"
|
|||
|
||||
[environment]
|
||||
command = "echo $HELLO && sleep 37852375"
|
||||
env = { HELLO = "uwu hi ヾ(•ω•`)o" }
|
||||
env = { HELLO = "uwu hi ヾ(•ω•`)o" }
|
||||
|
||||
[pwd]
|
||||
command = "pwd"
|
||||
workdir = "./src"
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
use crate::model::{AppState, AppStateFullView, Service, ServiceStatus};
|
||||
use crate::model::config::Config;
|
||||
use crate::model::{AppState, Service, ServiceStatus};
|
||||
use crate::{view, App};
|
||||
use crossterm::event;
|
||||
use crossterm::event::{Event, KeyCode};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::process::{Command, Stdio};
|
||||
use tui::backend::Backend;
|
||||
use tui::widgets::TableState;
|
||||
use tui::Terminal;
|
||||
|
|
@ -17,6 +21,7 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Resu
|
|||
Some(_) => app.leave_service(),
|
||||
None => return Ok(()),
|
||||
},
|
||||
KeyCode::Char('r') => app.run_service(),
|
||||
KeyCode::Down => app.next(),
|
||||
KeyCode::Up => app.previous(),
|
||||
KeyCode::Enter => app.select_service(),
|
||||
|
|
@ -28,24 +33,23 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Resu
|
|||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> App {
|
||||
pub fn new(config: Config) -> App {
|
||||
App {
|
||||
table: AppState {
|
||||
table_state: TableState::default(),
|
||||
items: vec![
|
||||
Service {
|
||||
name: "backend".to_string(),
|
||||
status: ServiceStatus::Running,
|
||||
},
|
||||
Service {
|
||||
name: "frontend".to_string(),
|
||||
status: ServiceStatus::Exited,
|
||||
},
|
||||
Service {
|
||||
name: "database".to_string(),
|
||||
status: ServiceStatus::Failed(1),
|
||||
},
|
||||
],
|
||||
items: config
|
||||
.into_iter()
|
||||
.map(|(name, service)| Service {
|
||||
command: service.command,
|
||||
name,
|
||||
workdir: service
|
||||
.workdir
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap()),
|
||||
env: service.env.unwrap_or_else(HashMap::new),
|
||||
status: ServiceStatus::NotStarted,
|
||||
child: None,
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
selected: None,
|
||||
}
|
||||
|
|
@ -58,7 +62,7 @@ impl App {
|
|||
fn select_service(&mut self) {
|
||||
if self.is_table() {
|
||||
if let Some(selected) = self.table.table_state.selected() {
|
||||
self.selected = Some(AppStateFullView { index: selected });
|
||||
self.selected = Some(selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -94,4 +98,27 @@ impl App {
|
|||
};
|
||||
self.table.table_state.select(Some(i));
|
||||
}
|
||||
|
||||
fn run_service(&mut self) {
|
||||
if let Some(selected) = self.selected {
|
||||
self.start_service(selected)
|
||||
} else if let Some(selected) = self.table.table_state.selected() {
|
||||
self.start_service(selected)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_service(&mut self, service: usize) {
|
||||
let service = &mut self.table.items[service];
|
||||
service.status = ServiceStatus::Running;
|
||||
|
||||
let mut cmd = Command::new("sh");
|
||||
|
||||
cmd.args(["-c", &service.command]);
|
||||
cmd.envs(service.env.iter());
|
||||
|
||||
cmd.stdout(Stdio::piped());
|
||||
let child = cmd.spawn();
|
||||
|
||||
service.child = Some(child);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
src/main.rs
46
src/main.rs
|
|
@ -7,23 +7,44 @@ use crossterm::execute;
|
|||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use std::{error::Error, io};
|
||||
use std::io::StdoutLock;
|
||||
use std::{env, error::Error, fs, io};
|
||||
use tui::backend::CrosstermBackend;
|
||||
use tui::Terminal;
|
||||
|
||||
use crate::model::config::Config;
|
||||
use crate::model::App;
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let file_path = env::args()
|
||||
.nth(1)
|
||||
.or_else(|| env::var("SERVICE_MANAGER_CONFIG_PATH").ok())
|
||||
.unwrap_or_else(|| {
|
||||
eprintln!(
|
||||
"error: config file not found
|
||||
usage: <filepath>
|
||||
or use the environment variable SERVICE_MANAGER_CONFIG_PATH"
|
||||
);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let config_file = fs::read(file_path).unwrap_or_else(|e| {
|
||||
eprintln!("error: failed to read file: {}", e);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let config = toml::from_slice::<Config>(&config_file).unwrap_or_else(|e| {
|
||||
eprintln!("error: invalid config file: {}", e);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
let stdout = stdout.lock();
|
||||
|
||||
let mut terminal = setup_terminal(stdout)?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new();
|
||||
let app = App::new(config);
|
||||
let res = controller::run_app(&mut terminal, app);
|
||||
|
||||
// restore terminal
|
||||
|
|
@ -41,3 +62,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_terminal(mut stdout: StdoutLock) -> io::Result<Terminal<CrosstermBackend<StdoutLock>>> {
|
||||
enable_raw_mode()?;
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let terminal = Terminal::new(backend)?;
|
||||
Ok(terminal)
|
||||
}
|
||||
|
|
|
|||
32
src/model.rs
32
src/model.rs
|
|
@ -1,9 +1,13 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Child;
|
||||
use tui::widgets::TableState;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct App {
|
||||
pub table: AppState,
|
||||
pub selected: Option<AppStateFullView>,
|
||||
pub selected: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -12,20 +16,36 @@ pub struct AppState {
|
|||
pub items: Vec<Service>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AppStateFullView {
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Service {
|
||||
pub command: String,
|
||||
pub name: String,
|
||||
pub workdir: PathBuf,
|
||||
pub env: HashMap<String, String>,
|
||||
pub status: ServiceStatus,
|
||||
pub child: Option<io::Result<Child>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServiceStatus {
|
||||
NotStarted,
|
||||
Running,
|
||||
Exited,
|
||||
Failed(u8),
|
||||
}
|
||||
|
||||
pub mod config {
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub type Config = BTreeMap<String, Service>;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Service {
|
||||
pub command: String,
|
||||
pub workdir: Option<PathBuf>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
src/view.rs
11
src/view.rs
|
|
@ -6,7 +6,7 @@ use tui::text::Spans;
|
|||
use tui::widgets::{Block, Borders, Cell, Paragraph, Row, Table};
|
||||
use tui::Frame;
|
||||
|
||||
use crate::model::{AppState, AppStateFullView, ServiceStatus};
|
||||
use crate::model::{AppState, ServiceStatus};
|
||||
use crate::App;
|
||||
|
||||
pub fn render_ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||
|
|
@ -24,7 +24,7 @@ pub fn render_ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
|||
None => {
|
||||
render_table(f, &mut app.table, chunks[0]);
|
||||
}
|
||||
Some(AppStateFullView { index }) => {
|
||||
Some(index) => {
|
||||
let name = &app.table.items[index].name;
|
||||
|
||||
f.render_widget(
|
||||
|
|
@ -73,9 +73,11 @@ fn render_help_footer<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
|
|||
let block = Block::default().title("help").borders(Borders::ALL);
|
||||
|
||||
let paragraph = Paragraph::new(if app.is_table() {
|
||||
vec![Spans::from("q-quit down-down up-up enter-select")]
|
||||
vec![Spans::from(
|
||||
"q-quit down-down up-up enter-select r-run service",
|
||||
)]
|
||||
} else {
|
||||
vec![Spans::from("q-back esc-back")]
|
||||
vec![Spans::from("q-back esc-back r-run service")]
|
||||
})
|
||||
.block(block);
|
||||
|
||||
|
|
@ -88,6 +90,7 @@ impl Display for ServiceStatus {
|
|||
ServiceStatus::Running => f.write_str("running"),
|
||||
ServiceStatus::Exited => f.write_str("exited (0)"),
|
||||
ServiceStatus::Failed(code) => write!(f, "failed ({})", code),
|
||||
ServiceStatus::NotStarted => f.write_str("not started"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue