Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2706593
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
38 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/cli.rs b/src/cli.rs
index 5c69d5f..b0ecd41 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,375 +1,426 @@
use colored::Colorize;
use global_placeholders::global;
use macros_rs::{crashln, string, ternary};
use psutil::process::{MemoryInfo, Process};
use serde::Serialize;
use serde_json::json;
use std::env;
use pmc::{
config,
file::{self, Exists},
helpers::{self, ColoredString},
log,
process::Runner,
};
use tabled::{
settings::{
object::{Columns, Rows},
style::{BorderColor, Style},
themes::Colorization,
Color, Modify, Rotate, Width,
},
Table, Tabled,
};
#[derive(Clone, Debug)]
pub enum Args {
Id(usize),
Script(String),
}
+fn format(server_name: &String) -> (String, String) {
+ let kind = ternary!(server_name == "internal", "", "remote ").to_string();
+ let list_name = ternary!(*server_name == "internal", "all", &*server_name).to_string();
+
+ return (kind, list_name);
+}
+
pub fn get_version(short: bool) -> String {
return match short {
true => format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
false => format!("{} ({} {}) [{}]", env!("CARGO_PKG_VERSION"), env!("GIT_HASH"), env!("BUILD_DATE"), env!("PROFILE")),
};
}
-pub fn start(name: &Option<String>, args: &Option<Args>, watch: &Option<String>) {
+pub fn start(name: &Option<String>, args: &Option<Args>, watch: &Option<String>, server_name: &String) {
let mut runner = Runner::new();
let config = config::read();
match args {
Some(Args::Id(id)) => {
- println!("{} Applying action restartProcess on ({id})", *helpers::SUCCESS);
- let item = runner.get(*id);
+ let (kind, list_name) = format(server_name);
+ println!("{} Applying {kind}action restartProcess on ({id})", *helpers::SUCCESS);
- match watch {
- Some(path) => item.watch(path),
- None => item.disable_watch(),
- }
+ if *server_name == "internal" {
+ let item = runner.get(*id);
- name.as_ref().map(|n| item.rename(n.trim().replace("\n", "")));
- item.restart();
+ match watch {
+ Some(path) => item.watch(path),
+ None => item.disable_watch(),
+ }
+
+ name.as_ref().map(|n| item.rename(n.trim().replace("\n", "")));
+ item.restart();
- println!("{} restarted ({id}) ✓", *helpers::SUCCESS);
- log!("process started (id={id})");
- list(&string!("default"));
+ log!("process started (id={id})");
+ } else {
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(server_name) {
+ match Runner::connect(server_name.clone(), server.clone(), false) {
+ Some(mut runner) => runner.get(*id).restart(),
+ None => println!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
+ }
+ }
+ }
+
+ println!("{} restarted {kind}({id}) ✓", *helpers::SUCCESS);
+ list(&string!("default"), &list_name);
}
Some(Args::Script(script)) => {
let name = match name {
Some(name) => string!(name),
None => string!(script.split_whitespace().next().unwrap_or_default()),
};
// fix
println!("{} Creating process with ({name})", *helpers::SUCCESS);
if name.ends_with(".ts") || name.ends_with(".js") {
let script = format!("{} {script}", config.runner.node);
runner.start(&name, &script, watch).save();
} else {
runner.start(&name, script, watch).save();
}
println!("{} created ({name}) ✓", *helpers::SUCCESS);
log!("process created (name={name})");
- list(&string!("default"));
+ list(&string!("default"), &string!("all"));
}
None => {}
}
}
-pub fn stop(id: &usize) {
- println!("{} Applying action stopProcess on ({id})", *helpers::SUCCESS);
- let mut runner = Runner::new();
- runner.get(*id).stop();
+pub fn stop(id: &usize, server_name: &String) {
+ let (kind, list_name) = format(server_name);
+ println!("{} Applying {kind}action stopProcess on ({id})", *helpers::SUCCESS);
+
+ if *server_name == "internal" {
+ let mut runner = Runner::new();
+ runner.get(*id).stop();
+ log!("process stopped (id={id})");
+ } else {
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(server_name) {
+ match Runner::connect(server_name.clone(), server.clone(), false) {
+ Some(mut runner) => runner.get(*id).stop(),
+ None => println!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
+ }
+ }
+ }
- println!("{} stopped ({id}) ✓", *helpers::SUCCESS);
- log!("process stopped (id={id})");
- list(&string!("default"));
+ println!("{} stopped {kind}({id}) ✓", *helpers::SUCCESS);
+ list(&string!("default"), &list_name);
}
pub fn remove(id: &usize) {
println!("{} Applying action removeProcess on ({id})", *helpers::SUCCESS);
Runner::new().remove(*id);
println!("{} removed ({id}) ✓", *helpers::SUCCESS);
log!("process removed (id={id})");
}
pub fn info(id: &usize, format: &String) {
#[derive(Clone, Debug, Tabled)]
struct Info {
#[tabled(rename = "error log path ")]
log_error: String,
#[tabled(rename = "out log path")]
log_out: String,
#[tabled(rename = "cpu percent")]
cpu_percent: String,
#[tabled(rename = "memory usage")]
memory_usage: String,
#[tabled(rename = "path hash")]
hash: String,
#[tabled(rename = "watching")]
watch: String,
#[tabled(rename = "exec cwd")]
path: String,
#[tabled(rename = "script command ")]
command: String,
#[tabled(rename = "script id")]
id: String,
restarts: u64,
uptime: String,
pid: String,
name: String,
status: ColoredString,
}
impl Serialize for Info {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let trimmed_json = json!({
"id": &self.id.trim(),
"pid": &self.pid.trim(),
"name": &self.name.trim(),
"path": &self.path.trim(),
"restarts": &self.restarts,
"watch": &self.watch.trim(),
"watch": &self.hash.trim(),
"uptime": &self.uptime.trim(),
"status": &self.status.0.trim(),
"log_out": &self.log_out.trim(),
"cpu": &self.cpu_percent.trim(),
"command": &self.command.trim(),
"mem": &self.memory_usage.trim(),
"log_error": &self.log_error.trim(),
});
trimmed_json.serialize(serializer)
}
}
if let Some(home) = home::home_dir() {
let config = config::read().runner;
let item = Runner::new().get(*id).clone();
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = Process::new(item.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let cpu_percent =
match cpu_percent {
Some(percent) => format!("{:.2}%", percent),
None => string!("0%"),
};
let memory_usage = match memory_usage {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
let status =
if item.running {
"online ".green().bold()
} else {
match item.crash.crashed {
true => "crashed ",
false => "stopped ",
}
.red()
.bold()
};
let path = file::make_relative(&item.path, &home)
.map(|relative_path| relative_path.to_string_lossy().into_owned())
.unwrap_or_else(|| crashln!("{} Unable to get your current directory", *helpers::FAIL));
let data = vec![Info {
cpu_percent,
memory_usage,
id: string!(id),
restarts: item.restarts,
name: item.name.clone(),
path: format!("{} ", path),
status: ColoredString(status),
log_out: global!("pmc.logs.out", item.name.as_str()),
log_error: global!("pmc.logs.error", item.name.as_str()),
pid: ternary!(item.running, format!("{}", item.pid), string!("n/a")),
command: format!("{} {} '{}'", config.shell, config.args.join(" "), item.script),
hash: ternary!(item.watch.enabled, format!("{} ", item.watch.hash), string!("none ")),
watch: ternary!(item.watch.enabled, format!("{path}/{} ", item.watch.path), string!("disabled ")),
uptime: ternary!(item.running, format!("{}", helpers::format_duration(item.started)), string!("none")),
}];
let table = Table::new(data.clone())
.with(Rotate::Left)
.with(Style::rounded().remove_horizontals())
.with(Colorization::exact([Color::FG_CYAN], Columns::first()))
.with(BorderColor::filled(Color::FG_BRIGHT_BLACK))
.to_string();
if let Ok(json) = serde_json::to_string(&data[0]) {
match format.as_str() {
"raw" => println!("{:?}", data[0]),
"json" => println!("{json}"),
_ => {
println!("{}\n{table}\n", format!("Describing process with id ({id})").on_bright_white().black());
println!(" {}", format!("Use `pmc logs {id} [--lines <num>]` to display logs").white());
println!(" {}", format!("Use `pmc env {id}` to display environment variables").white());
}
};
};
} else {
crashln!("{} Impossible to get your home directory", *helpers::FAIL);
}
}
pub fn logs(id: &usize, lines: &usize) {
let item = Runner::new().get(*id).clone();
let log_error = global!("pmc.logs.error", item.name.as_str());
let log_out = global!("pmc.logs.out", item.name.as_str());
if Exists::file(log_error.clone()).unwrap() && Exists::file(log_out.clone()).unwrap() {
println!("{}", format!("Showing last {lines} lines for process [{id}] (change the value with --lines option)").yellow());
file::logs(*lines, &log_error, *id, "error", &item.name);
file::logs(*lines, &log_out, *id, "out", &item.name);
} else {
crashln!("{} Logs for process ({id}) not found", *helpers::FAIL);
}
}
pub fn env(id: &usize) {
let item = Runner::new().get(*id).clone();
for (key, value) in item.env.iter() {
println!("{}: {}", key, value.green());
}
}
-pub fn list(format: &String) {
+pub fn list(format: &String, server_name: &String) {
let render_list = |runner: &mut Runner| {
let mut processes: Vec<ProcessItem> = Vec::new();
#[derive(Tabled, Debug)]
struct ProcessItem {
id: ColoredString,
name: String,
pid: String,
uptime: String,
#[tabled(rename = "↺")]
restarts: String,
status: ColoredString,
cpu: String,
mem: String,
#[tabled(rename = "watching")]
watch: String,
}
impl serde::Serialize for ProcessItem {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let trimmed_json = json!({
"cpu": &self.cpu.trim(),
"mem": &self.mem.trim(),
"id": &self.id.0.trim(),
"pid": &self.pid.trim(),
"name": &self.name.trim(),
"watch": &self.watch.trim(),
"uptime": &self.uptime.trim(),
"status": &self.status.0.trim(),
"restarts": &self.restarts.trim(),
});
trimmed_json.serialize(serializer)
}
}
if runner.is_empty() {
println!("{} Process table empty", *helpers::SUCCESS);
} else {
for (id, item) in runner.items() {
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = Process::new(item.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let cpu_percent = match cpu_percent {
Some(percent) => format!("{:.0}%", percent),
None => string!("0%"),
};
let memory_usage = match memory_usage {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
let status = if item.running {
"online ".green().bold()
} else {
match item.crash.crashed {
true => "crashed ",
false => "stopped ",
}
.red()
.bold()
};
processes.push(ProcessItem {
status: ColoredString(status),
cpu: format!("{cpu_percent} "),
mem: format!("{memory_usage} "),
restarts: format!("{} ", item.restarts),
name: format!("{} ", item.name.clone()),
id: ColoredString(id.to_string().cyan().bold()),
pid: ternary!(item.running, format!("{} ", item.pid), string!("n/a ")),
watch: ternary!(item.watch.enabled, format!("{} ", item.watch.path), string!("disabled ")),
uptime: ternary!(item.running, format!("{} ", helpers::format_duration(item.started)), string!("none ")),
});
}
let table = Table::new(&processes)
.with(Style::rounded().remove_verticals())
.with(BorderColor::filled(Color::FG_BRIGHT_BLACK))
.with(Colorization::exact([Color::FG_BRIGHT_CYAN], Rows::first()))
.with(Modify::new(Columns::single(1)).with(Width::truncate(35).suffix("... ")))
.to_string();
if let Ok(json) = serde_json::to_string(&processes) {
match format.as_str() {
"raw" => println!("{:?}", processes),
"json" => println!("{json}"),
"default" => println!("{table}"),
_ => {}
};
};
}
};
if let Some(servers) = config::servers().servers {
let mut failed: Vec<(String, String)> = vec![];
- println!("{} Internal daemon", *helpers::SUCCESS);
- render_list(&mut Runner::new());
-
- for (name, server) in servers {
- match Runner::connect(name.clone(), server.clone()) {
+ if let Some(server) = servers.get(server_name) {
+ match Runner::connect(server_name.clone(), server.clone(), true) {
Some(mut runner) => render_list(&mut runner),
- None => failed.push((name.clone(), server.address.clone())),
+ None => println!("{} Failed to fetch (name={server_name}, address={})", *helpers::FAIL, server.address),
+ }
+ } else {
+ if matches!(&**server_name, "internal" | "all") {
+ println!("{} Internal daemon", *helpers::SUCCESS);
+ render_list(&mut Runner::new());
+ } else {
+ crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL);
+ }
+ }
+
+ if *server_name == "all" {
+ for (name, server) in servers {
+ match Runner::connect(name.clone(), server.clone(), true) {
+ Some(mut runner) => render_list(&mut runner),
+ None => failed.push((name, server.address)),
+ }
}
}
if !failed.is_empty() {
println!("{} Failed servers:", *helpers::FAIL);
failed
.iter()
.for_each(|server| println!(" {} {} {}", "-".yellow(), format!("{}", server.0), format!("[{}]", server.1).white()));
}
} else {
render_list(&mut Runner::new());
}
}
diff --git a/src/main.rs b/src/main.rs
index b76881f..ebe1f69 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,142 +1,169 @@
mod cli;
mod daemon;
mod globals;
mod webui;
use crate::cli::Args;
use clap::{Parser, Subcommand};
use clap_verbosity_flag::Verbosity;
use macros_rs::{str, string, then};
fn validate_id_script(s: &str) -> Result<Args, String> {
if let Ok(id) = s.parse::<usize>() {
Ok(Args::Id(id))
} else {
Ok(Args::Script(s.to_owned()))
}
}
#[derive(Parser)]
#[command(version = str!(cli::get_version(false)))]
struct Cli {
#[command(subcommand)]
command: Commands,
#[clap(flatten)]
verbose: Verbosity,
}
#[derive(Subcommand)]
enum Daemon {
/// Reset process index
#[command(alias = "clean")]
Reset,
/// Stop daemon
#[command(alias = "kill")]
Stop,
/// Restart daemon
#[command(alias = "restart", alias = "start")]
Restore {
/// Daemon api
#[arg(long)]
api: bool,
/// WebUI using api
#[arg(long)]
webui: bool,
},
/// Check daemon
#[command(alias = "info", alias = "status")]
Health {
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
},
}
// add pmc restore command
#[derive(Subcommand)]
enum Commands {
/// Start/Restart a process
#[command(alias = "restart")]
Start {
/// Process name
#[arg(long)]
name: Option<String>,
#[clap(value_parser = validate_id_script)]
args: Option<Args>,
/// Watch to reload path
#[arg(long)]
watch: Option<String>,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
},
/// Stop/Kill a process
#[command(alias = "kill")]
- Stop { id: usize },
+ Stop {
+ id: usize,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
+ },
/// Stop then remove a process
#[command(alias = "rm")]
- Remove { id: usize },
+ Remove {
+ id: usize,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
+ },
/// Get env of a process
#[command(alias = "cmdline")]
- Env { id: usize },
+ Env {
+ id: usize,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
+ },
/// Get information of a process
#[command(alias = "info")]
Details {
id: usize,
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
},
/// List all processes
#[command(alias = "ls")]
List {
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
+ /// Server
+ #[arg(short, long, default_value_t = string!("all"))]
+ server: String,
},
/// Get logs from a process
Logs {
id: usize,
#[arg(long, default_value_t = 15, help = "")]
lines: usize,
+ /// Server
+ #[arg(short, long, default_value_t = string!("internal"))]
+ server: String,
},
/// Daemon management
Daemon {
#[command(subcommand)]
command: Daemon,
},
}
fn main() {
let cli = Cli::parse();
let mut env = env_logger::Builder::new();
let level = cli.verbose.log_level_filter();
globals::init();
env.filter_level(level).init();
match &cli.command {
- Commands::Start { name, args, watch } => cli::start(name, args, watch),
- Commands::Stop { id } => cli::stop(id),
- Commands::Remove { id } => cli::remove(id),
- Commands::Env { id } => cli::env(id),
- Commands::Details { id, format } => cli::info(id, format),
- Commands::List { format } => cli::list(format),
- Commands::Logs { id, lines } => cli::logs(id, lines),
+ Commands::Start { name, args, watch, server } => cli::start(name, args, watch, server),
+ Commands::Stop { id, server } => cli::stop(id, server),
+ Commands::Remove { id, server } => cli::remove(id),
+ Commands::Env { id, server } => cli::env(id),
+ Commands::Details { id, format, server } => cli::info(id, format),
+ Commands::List { format, server } => cli::list(format, server),
+ Commands::Logs { id, lines, server } => cli::logs(id, lines),
Commands::Daemon { command } => match command {
Daemon::Stop => daemon::stop(),
Daemon::Reset => daemon::reset(),
Daemon::Health { format } => daemon::health(format),
Daemon::Restore { api, webui } => daemon::restart(api, webui, level.as_str() != "ERROR"),
},
};
if !matches!(&cli.command, Commands::Daemon { .. }) {
then!(!daemon::pid::exists(), daemon::start(false));
}
}
diff --git a/src/process/http.rs b/src/process/http.rs
index d86a56e..d47ab91 100644
--- a/src/process/http.rs
+++ b/src/process/http.rs
@@ -1,18 +1,43 @@
use crate::process::{Process, Remote};
-use macros_rs::fmtstr;
-use reqwest::blocking::Client;
+use macros_rs::{fmtstr, string};
+use reqwest::blocking::{Client, Response};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
+use serde::Serialize;
use std::collections::BTreeMap;
-pub fn list(remote: &Remote) -> Result<BTreeMap<usize, Process>, anyhow::Error> {
+#[derive(Serialize)]
+struct ActionBody {
+ pub method: String,
+}
+
+fn client(token: &Option<String>) -> (Client, HeaderMap) {
let client = Client::new();
let mut headers = HeaderMap::new();
- let Remote { address, token } = remote;
if let Some(token) = token {
headers.insert(AUTHORIZATION, HeaderValue::from_static(fmtstr!("token {token}")));
}
- let response = client.get(fmtstr!("{address}/list")).headers(headers).send()?;
- Ok(response.json()?)
+ return (client, headers);
+}
+
+pub fn restart(Remote { address, token }: &Remote, id: usize) -> Result<Response, anyhow::Error> {
+ let (client, headers) = client(token);
+ let content = ActionBody { method: string!("restart") };
+
+ Ok(client.post(fmtstr!("{address}/process/{id}/action")).json(&content).headers(headers).send()?)
+}
+
+pub fn stop(Remote { address, token }: &Remote, id: usize) -> Result<Response, anyhow::Error> {
+ let (client, headers) = client(token);
+ let content = ActionBody { method: string!("stop") };
+
+ Ok(client.post(fmtstr!("{address}/process/{id}/action")).json(&content).headers(headers).send()?)
+}
+
+pub fn remove(Remote { address, token }: &Remote, id: usize) -> Result<Response, anyhow::Error> {
+ let (client, headers) = client(token);
+ let content = ActionBody { method: string!("remove") };
+
+ Ok(client.post(fmtstr!("{address}/process/{id}/action")).json(&content).headers(headers).send()?)
}
diff --git a/src/process/mod.rs b/src/process/mod.rs
index a23f0f6..7ef1d6a 100644
--- a/src/process/mod.rs
+++ b/src/process/mod.rs
@@ -1,418 +1,434 @@
mod http;
use crate::{
config,
config::structs::Server,
file, helpers,
service::{run, stop, ProcessMetadata},
};
use chrono::serde::ts_milliseconds;
use chrono::{DateTime, Utc};
use global_placeholders::global;
use macros_rs::{crashln, string, ternary, then};
use psutil::process::{self, MemoryInfo};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::{BTreeMap, HashMap};
use std::{env, path::PathBuf};
use utoipa::ToSchema;
#[derive(Serialize, ToSchema)]
pub struct ItemSingle {
info: Info,
stats: Stats,
watch: Watch,
log: Log,
raw: Raw,
}
#[derive(Serialize, ToSchema)]
pub struct Info {
id: usize,
pid: i64,
name: String,
status: String,
#[schema(value_type = String, example = "/path")]
path: PathBuf,
uptime: String,
command: String,
}
#[derive(Serialize, ToSchema)]
pub struct Stats {
restarts: u64,
start_time: i64,
cpu_percent: Option<f32>,
memory_usage: Option<MemoryInfo>,
}
#[derive(Serialize, ToSchema)]
pub struct Log {
out: String,
error: String,
}
#[derive(Serialize, ToSchema)]
pub struct Raw {
running: bool,
crashed: bool,
crashes: u64,
}
#[derive(Serialize, ToSchema)]
pub struct ProcessItem {
pid: i64,
id: usize,
cpu: String,
mem: String,
name: String,
restarts: u64,
status: String,
uptime: String,
#[schema(example = "/path")]
watch_path: String,
#[schema(value_type = String, example = "2000-01-01T01:00:00.000Z")]
start_time: DateTime<Utc>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Process {
pub id: usize,
pub pid: i64,
pub name: String,
pub path: PathBuf,
pub script: String,
pub env: HashMap<String, String>,
#[serde(with = "ts_milliseconds")]
pub started: DateTime<Utc>,
pub restarts: u64,
pub running: bool,
pub crash: Crash,
pub watch: Watch,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Crash {
pub crashed: bool,
pub value: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct Watch {
pub enabled: bool,
#[schema(example = "/path")]
pub path: String,
pub hash: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Runner {
pub id: id::Id,
#[serde(skip)]
pub remote: Option<Remote>,
pub list: BTreeMap<usize, Process>,
}
#[derive(Clone, Debug)]
pub struct Remote {
address: String,
token: Option<String>,
}
pub enum Status {
Offline,
Running,
}
impl Status {
pub fn to_bool(&self) -> bool {
match self {
Status::Offline => false,
Status::Running => true,
}
}
}
impl Runner {
pub fn new() -> Self { dump::read() }
- pub fn connect(name: String, server: Server) -> Option<Self> {
- let Server { address, token } = server;
-
+ pub fn connect(name: String, Server { address, token }: Server, verbose: bool) -> Option<Self> {
match dump::from(&address, token.as_deref()) {
Ok(dump) => {
- println!("{} Fetched remote (name={name}, address={address})", *helpers::SUCCESS);
+ then!(verbose, println!("{} Fetched remote (name={name}, address={address})", *helpers::SUCCESS));
return Some(Runner {
remote: Some(Remote { token, address: string!(address) }),
..dump
});
}
Err(err) => {
log::debug!("{err}");
return None;
}
}
}
pub fn start(&mut self, name: &String, command: &String, watch: &Option<String>) -> &mut Self {
let id = self.id.next();
let config = config::read().runner;
let crash = Crash { crashed: false, value: 0 };
let watch = match watch {
Some(watch) => Watch {
enabled: true,
path: string!(watch),
hash: hash::create(file::cwd().join(watch)),
},
None => {
Watch {
enabled: false,
path: string!(""),
hash: string!(""),
}
}
};
let pid = run(ProcessMetadata {
args: config.args,
name: name.clone(),
shell: config.shell,
command: command.clone(),
log_path: config.log_path,
});
self.list.insert(
id,
Process {
id,
pid,
watch,
crash,
restarts: 0,
running: true,
path: file::cwd(),
name: name.clone(),
started: Utc::now(),
script: command.clone(),
env: env::vars().collect(),
},
);
return self;
}
pub fn restart(&mut self, id: usize, dead: bool) -> &mut Self {
- let item = self.get(id);
- let Process { path, script, name, .. } = item.clone();
+ if let Some(remote) = &self.remote {
+ if let Err(err) = http::restart(remote, id) {
+ crashln!("{} Failed to start process {id}\nError: {:#?}", *helpers::FAIL, err);
+ };
+ } else {
+ let item = self.get(id);
+ let Process { path, script, name, .. } = item.clone();
- if let Err(err) = std::env::set_current_dir(&item.path) {
- crashln!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err);
- };
+ if let Err(err) = std::env::set_current_dir(&item.path) {
+ crashln!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err);
+ };
- item.stop();
+ item.stop();
- let config = config::read().runner;
+ let config = config::read().runner;
- item.crash.crashed = false;
- item.pid = run(ProcessMetadata {
- command: script,
- args: config.args,
- name: name.clone(),
- shell: config.shell,
- log_path: config.log_path,
- });
+ item.crash.crashed = false;
+ item.pid = run(ProcessMetadata {
+ command: script,
+ args: config.args,
+ name: name.clone(),
+ shell: config.shell,
+ log_path: config.log_path,
+ });
- item.running = true;
- item.started = Utc::now();
- then!(dead, item.restarts += 1);
+ item.running = true;
+ item.started = Utc::now();
+ then!(dead, item.restarts += 1);
+ }
return self;
}
pub fn remove(&mut self, id: usize) {
- self.stop(id);
- self.list.remove(&id);
- dump::write(&self);
+ if let Some(remote) = &self.remote {
+ if let Err(err) = http::remove(remote, id) {
+ crashln!("{} Failed to stop remove {id}\nError: {:#?}", *helpers::FAIL, err);
+ };
+ } else {
+ self.stop(id);
+ self.list.remove(&id);
+ dump::write(&self);
+ }
}
pub fn set_id(&mut self, id: id::Id) {
self.id = id;
self.id.next();
dump::write(&self);
}
pub fn set_status(&mut self, id: usize, status: Status) {
self.get(id).running = status.to_bool();
dump::write(&self);
}
pub fn items(&mut self) -> BTreeMap<usize, Process> { self.list.clone() }
pub fn items_mut(&mut self) -> &mut BTreeMap<usize, Process> { &mut self.list }
pub fn save(&self) { then!(self.remote.is_none(), dump::write(&self)) }
pub fn count(&mut self) -> usize { self.list().count() }
pub fn is_empty(&self) -> bool { self.list.is_empty() }
pub fn exists(&mut self, id: usize) -> bool { self.list.contains_key(&id) }
pub fn info(&mut self, id: usize) -> Option<&Process> { self.list.get(&id) }
pub fn list<'l>(&'l mut self) -> impl Iterator<Item = (&'l usize, &'l mut Process)> { self.list.iter_mut().map(|(k, v)| (k, v)) }
pub fn get(&mut self, id: usize) -> &mut Process { self.list.get_mut(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)) }
pub fn set_crashed(&mut self, id: usize) -> &mut Self {
self.get(id).crash.crashed = true;
return self;
}
pub fn new_crash(&mut self, id: usize) -> &mut Self {
self.get(id).crash.value += 1;
return self;
}
pub fn stop(&mut self, id: usize) -> &mut Self {
- let item = self.get(id);
- stop(item.pid);
+ if let Some(remote) = &self.remote {
+ if let Err(err) = http::stop(remote, id) {
+ crashln!("{} Failed to stop process {id}\nError: {:#?}", *helpers::FAIL, err);
+ };
+ } else {
+ let item = self.get(id);
+ stop(item.pid);
- item.running = false;
- item.crash.crashed = false;
- item.crash.value = 0;
+ item.running = false;
+ item.crash.crashed = false;
+ item.crash.value = 0;
+ }
return self;
}
pub fn rename(&mut self, id: usize, name: String) -> &mut Self {
self.get(id).name = name;
return self;
}
pub fn watch(&mut self, id: usize, path: &str, enabled: bool) -> &mut Self {
let item = self.get(id);
item.watch = Watch {
enabled,
path: string!(path),
hash: ternary!(enabled, hash::create(item.path.join(path)), string!("")),
};
return self;
}
pub fn json(&mut self) -> Value {
let mut processes: Vec<ProcessItem> = Vec::new();
for (id, item) in self.items() {
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = process::Process::new(item.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let cpu_percent = match cpu_percent {
Some(percent) => format!("{:.2}%", percent),
None => string!("0.00%"),
};
let memory_usage = match memory_usage {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
let status =
if item.running {
string!("online")
} else {
match item.crash.crashed {
true => string!("crashed"),
false => string!("stopped"),
}
};
processes.push(ProcessItem {
id,
status,
pid: item.pid,
cpu: cpu_percent,
mem: memory_usage,
restarts: item.restarts,
name: item.name.clone(),
start_time: item.started,
watch_path: item.watch.path.clone(),
uptime: helpers::format_duration(item.started),
});
}
json!(processes)
}
}
impl Process {
pub fn stop(&mut self) { Runner::new().stop(self.id).save(); }
pub fn watch(&mut self, path: &str) { Runner::new().watch(self.id, path, true).save(); }
pub fn disable_watch(&mut self) { Runner::new().watch(self.id, "", false).save(); }
pub fn rename(&mut self, name: String) { Runner::new().rename(self.id, name).save(); }
pub fn restart(&mut self) { Runner::new().restart(self.id, false).save(); }
pub fn crashed(&mut self) -> &mut Process {
Runner::new().new_crash(self.id).save();
Runner::new().restart(self.id, true).save();
return self;
}
pub fn json(&mut self) -> Value {
let config = config::read().runner;
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
if let Ok(mut process) = process::Process::new(self.pid as u32) {
memory_usage = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
}
let status = if self.running {
string!("online")
} else {
match self.crash.crashed {
true => string!("crashed"),
false => string!("stopped"),
}
};
json!(ItemSingle {
info: Info {
status,
id: self.id,
pid: self.pid,
name: self.name.clone(),
path: self.path.clone(),
uptime: helpers::format_duration(self.started),
command: format!("{} {} '{}'", config.shell, config.args.join(" "), self.script.clone()),
},
stats: Stats {
cpu_percent,
memory_usage,
restarts: self.restarts,
start_time: self.started.timestamp_millis(),
},
watch: Watch {
enabled: self.watch.enabled,
hash: self.watch.hash.clone(),
path: self.watch.path.clone(),
},
log: Log {
out: global!("pmc.logs.out", self.name.as_str()),
error: global!("pmc.logs.error", self.name.as_str()),
},
raw: Raw {
running: self.running,
crashed: self.crash.crashed,
crashes: self.crash.value,
}
})
}
}
pub mod dump;
pub mod hash;
pub mod id;
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Feb 1, 11:26 AM (9 h, 37 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
494683
Default Alt Text
(38 KB)
Attached To
Mode
rPMC Process Management Controller
Attached
Detach File
Event Timeline
Log In to Comment