mirror of
https://github.com/Noratrieb/service-manager.git
synced 2026-01-14 16:35:05 +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",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
|
@ -194,6 +212,20 @@ name = "serde"
|
||||||
version = "1.0.132"
|
version = "1.0.132"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
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]]
|
[[package]]
|
||||||
name = "service-manager"
|
name = "service-manager"
|
||||||
|
|
@ -241,6 +273,17 @@ version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
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]]
|
[[package]]
|
||||||
name = "termion"
|
name = "termion"
|
||||||
version = "1.5.6"
|
version = "1.5.6"
|
||||||
|
|
@ -288,6 +331,12 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.22.1"
|
crossterm = "0.22.1"
|
||||||
serde = "1.0.132"
|
serde = { version = "1.0.132", features = ["derive"] }
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
tui = { version = "0.16.0", features = ["crossterm"] }
|
tui = { version = "0.16.0", features = ["crossterm"] }
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,8 @@ command = "sleep 48590234"
|
||||||
|
|
||||||
[environment]
|
[environment]
|
||||||
command = "echo $HELLO && sleep 37852375"
|
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 crate::{view, App};
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
use tui::backend::Backend;
|
use tui::backend::Backend;
|
||||||
use tui::widgets::TableState;
|
use tui::widgets::TableState;
|
||||||
use tui::Terminal;
|
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(),
|
Some(_) => app.leave_service(),
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
},
|
},
|
||||||
|
KeyCode::Char('r') => app.run_service(),
|
||||||
KeyCode::Down => app.next(),
|
KeyCode::Down => app.next(),
|
||||||
KeyCode::Up => app.previous(),
|
KeyCode::Up => app.previous(),
|
||||||
KeyCode::Enter => app.select_service(),
|
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 {
|
impl App {
|
||||||
pub fn new() -> App {
|
pub fn new(config: Config) -> App {
|
||||||
App {
|
App {
|
||||||
table: AppState {
|
table: AppState {
|
||||||
table_state: TableState::default(),
|
table_state: TableState::default(),
|
||||||
items: vec![
|
items: config
|
||||||
Service {
|
.into_iter()
|
||||||
name: "backend".to_string(),
|
.map(|(name, service)| Service {
|
||||||
status: ServiceStatus::Running,
|
command: service.command,
|
||||||
},
|
name,
|
||||||
Service {
|
workdir: service
|
||||||
name: "frontend".to_string(),
|
.workdir
|
||||||
status: ServiceStatus::Exited,
|
.unwrap_or_else(|| std::env::current_dir().unwrap()),
|
||||||
},
|
env: service.env.unwrap_or_else(HashMap::new),
|
||||||
Service {
|
status: ServiceStatus::NotStarted,
|
||||||
name: "database".to_string(),
|
child: None,
|
||||||
status: ServiceStatus::Failed(1),
|
})
|
||||||
},
|
.collect(),
|
||||||
],
|
|
||||||
},
|
},
|
||||||
selected: None,
|
selected: None,
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +62,7 @@ impl App {
|
||||||
fn select_service(&mut self) {
|
fn select_service(&mut self) {
|
||||||
if self.is_table() {
|
if self.is_table() {
|
||||||
if let Some(selected) = self.table.table_state.selected() {
|
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));
|
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::{
|
use crossterm::terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
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::backend::CrosstermBackend;
|
||||||
use tui::Terminal;
|
use tui::Terminal;
|
||||||
|
|
||||||
|
use crate::model::config::Config;
|
||||||
use crate::model::App;
|
use crate::model::App;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
// setup terminal
|
let file_path = env::args()
|
||||||
enable_raw_mode()?;
|
.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 stdout = io::stdout();
|
||||||
let mut stdout = stdout.lock();
|
let stdout = stdout.lock();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let mut terminal = setup_terminal(stdout)?;
|
||||||
let mut terminal = Terminal::new(backend)?;
|
|
||||||
|
|
||||||
// create app and run it
|
// create app and run it
|
||||||
let app = App::new();
|
let app = App::new(config);
|
||||||
let res = controller::run_app(&mut terminal, app);
|
let res = controller::run_app(&mut terminal, app);
|
||||||
|
|
||||||
// restore terminal
|
// restore terminal
|
||||||
|
|
@ -41,3 +62,12 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
Ok(())
|
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;
|
use tui::widgets::TableState;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub table: AppState,
|
pub table: AppState,
|
||||||
pub selected: Option<AppStateFullView>,
|
pub selected: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -12,20 +16,36 @@ pub struct AppState {
|
||||||
pub items: Vec<Service>,
|
pub items: Vec<Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AppStateFullView {
|
|
||||||
pub index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
|
pub command: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub workdir: PathBuf,
|
||||||
|
pub env: HashMap<String, String>,
|
||||||
pub status: ServiceStatus,
|
pub status: ServiceStatus,
|
||||||
|
pub child: Option<io::Result<Child>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ServiceStatus {
|
pub enum ServiceStatus {
|
||||||
|
NotStarted,
|
||||||
Running,
|
Running,
|
||||||
Exited,
|
Exited,
|
||||||
Failed(u8),
|
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::widgets::{Block, Borders, Cell, Paragraph, Row, Table};
|
||||||
use tui::Frame;
|
use tui::Frame;
|
||||||
|
|
||||||
use crate::model::{AppState, AppStateFullView, ServiceStatus};
|
use crate::model::{AppState, ServiceStatus};
|
||||||
use crate::App;
|
use crate::App;
|
||||||
|
|
||||||
pub fn render_ui<B: Backend>(f: &mut Frame<B>, app: &mut 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 => {
|
None => {
|
||||||
render_table(f, &mut app.table, chunks[0]);
|
render_table(f, &mut app.table, chunks[0]);
|
||||||
}
|
}
|
||||||
Some(AppStateFullView { index }) => {
|
Some(index) => {
|
||||||
let name = &app.table.items[index].name;
|
let name = &app.table.items[index].name;
|
||||||
|
|
||||||
f.render_widget(
|
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 block = Block::default().title("help").borders(Borders::ALL);
|
||||||
|
|
||||||
let paragraph = Paragraph::new(if app.is_table() {
|
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 {
|
} else {
|
||||||
vec![Spans::from("q-back esc-back")]
|
vec![Spans::from("q-back esc-back r-run service")]
|
||||||
})
|
})
|
||||||
.block(block);
|
.block(block);
|
||||||
|
|
||||||
|
|
@ -88,6 +90,7 @@ impl Display for ServiceStatus {
|
||||||
ServiceStatus::Running => f.write_str("running"),
|
ServiceStatus::Running => f.write_str("running"),
|
||||||
ServiceStatus::Exited => f.write_str("exited (0)"),
|
ServiceStatus::Exited => f.write_str("exited (0)"),
|
||||||
ServiceStatus::Failed(code) => write!(f, "failed ({})", code),
|
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