Page MenuHomePhorge

No OneTemporary

Size
41 KB
Referenced Files
None
Subscribers
None
diff --git a/src/cli.rs b/src/cli.rs
index a1f33bf..9c99466 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,594 +1,594 @@
use colored::Colorize;
use global_placeholders::global;
use macros_rs::{crashln, string, ternary};
use psutil::process::{MemoryInfo, Process};
use regex::Regex;
use serde::Serialize;
use serde_json::json;
use std::env;
use pmc::{
config,
file::{self, Exists},
helpers::{self, ColoredString},
log,
process::{
http::{self, LogResponse},
ItemSingle, 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();
return (kind, server_name.to_string());
}
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>, server_name: &String) {
let mut runner = Runner::new();
let config = config::read();
let (kind, list_name) = format(server_name);
match args {
Some(Args::Id(id)) => {
let runner: Runner = Runner::new();
println!("{} Applying {kind}action restartProcess on ({id})", *helpers::SUCCESS);
if *server_name == "internal" {
let mut item = runner.get(*id);
match watch {
Some(path) => item.watch(path),
None => item.disable_watch(),
}
name.as_ref().map(|n| item.rename(n.trim().replace("\n", "")));
item.restart();
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) {
+ match Runner::connect(server_name.clone(), server.get(), false) {
Some(remote) => {
let mut item = remote.get(*id);
name.as_ref().map(|n| item.rename(n.trim().replace("\n", "")));
item.restart();
}
None => crashln!("{} 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()),
};
if *server_name == "internal" {
let pattern = Regex::new(r"(?m)^[a-zA-Z0-9]+(/[a-zA-Z0-9]+)*(\.js|\.ts)?$").unwrap();
if pattern.is_match(script) {
let script = format!("{} {script}", config.runner.node);
runner.start(&name, &script, file::cwd(), watch).save();
} else {
runner.start(&name, script, file::cwd(), watch).save();
}
log!("process created (name={name})");
} 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) {
+ match Runner::connect(server_name.clone(), server.get(), false) {
Some(mut remote) => remote.start(&name, script, file::cwd(), watch),
None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
}
println!("{} Creating {kind}process with ({name})", *helpers::SUCCESS);
println!("{} {kind}created ({name}) ✓", *helpers::SUCCESS);
list(&string!("default"), &list_name);
}
None => {}
}
}
pub fn stop(id: &usize, server_name: &String) {
let mut runner: Runner = Runner::new();
let (kind, list_name) = format(server_name);
println!("{} Applying {kind}action stopProcess on ({id})", *helpers::SUCCESS);
if *server_name != "internal" {
let Some(servers) = config::servers().servers else {
crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
};
if let Some(server) = servers.get(server_name) {
- runner = match Runner::connect(server_name.clone(), server.clone(), false) {
+ runner = match Runner::connect(server_name.clone(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
}
runner.get(*id).stop();
println!("{} stopped {kind}({id}) ✓", *helpers::SUCCESS);
log!("process stopped {kind}(id={id})");
list(&string!("default"), &list_name);
}
pub fn remove(id: &usize, server_name: &String) {
let mut runner: Runner = Runner::new();
let (kind, _) = format(server_name);
println!("{} Applying {kind}action removeProcess on ({id})", *helpers::SUCCESS);
if *server_name != "internal" {
let Some(servers) = config::servers().servers else {
crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
};
if let Some(server) = servers.get(server_name) {
- runner = match Runner::connect(server_name.clone(), server.clone(), false) {
+ runner = match Runner::connect(server_name.clone(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to remove (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
}
runner.remove(*id);
println!("{} removed {kind}({id}) ✓", *helpers::SUCCESS);
log!("process removed (id={id})");
}
pub fn info(id: &usize, format: &String, server_name: &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)
}
}
let render_info = |data: Vec<Info>| {
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());
}
};
};
};
if *server_name == "internal" {
if let Some(home) = home::home_dir() {
let config = config::read().runner;
let mut runner = Runner::new();
let item = runner.process(*id);
let mut memory_usage: Option<MemoryInfo> = None;
let mut cpu_percent: Option<f32> = None;
let path = file::make_relative(&item.path, &home).to_string_lossy().into_owned();
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 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")),
}];
render_info(data)
} else {
crashln!("{} Impossible to get your home directory", *helpers::FAIL);
}
} else {
let mut item: Option<(pmc::process::Process, Runner)> = None;
let Some(servers) = config::servers().servers else {
crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
};
if let Some(server) = servers.get(server_name) {
- item = match Runner::connect(server_name.clone(), server.clone(), false) {
+ item = match Runner::connect(server_name.clone(), server.get(), false) {
Some(mut remote) => Some((remote.process(*id).clone(), remote)),
None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
if let Some((item, remote)) = item {
let remote = remote.remote.unwrap();
let info = http::info(&remote, *id);
let path = item.path.to_string_lossy().into_owned();
let status = if item.running {
"online ".green().bold()
} else {
match item.crash.crashed {
true => "crashed ",
false => "stopped ",
}
.red()
.bold()
};
if let Ok(info) = info {
let stats = info.json::<ItemSingle>().unwrap().stats;
let cpu_percent = match stats.cpu_percent {
Some(percent) => format!("{:.2}%", percent),
None => string!("0%"),
};
let memory_usage = match stats.memory_usage {
Some(usage) => helpers::format_memory(usage.rss),
None => string!("0b"),
};
let data = vec![Info {
cpu_percent,
memory_usage,
id: string!(id),
path: path.clone(),
restarts: item.restarts,
name: item.name.clone(),
status: ColoredString(status),
log_out: format!("{}/{{}}-out.log", remote.config.log_path),
log_error: format!("{}/{{}}-error.log", remote.config.log_path),
pid: ternary!(item.running, format!("{}", item.pid), string!("n/a")),
hash: ternary!(item.watch.enabled, format!("{} ", item.watch.hash), string!("none ")),
command: format!("{} {} '{}'", remote.config.shell, remote.config.args.join(" "), item.script),
watch: ternary!(item.watch.enabled, format!("{path}/{} ", item.watch.path), string!("disabled ")),
uptime: ternary!(item.running, format!("{}", helpers::format_duration(item.started)), string!("none")),
}];
render_info(data)
}
}
}
}
pub fn logs(id: &usize, lines: &usize, server_name: &String) {
let mut runner: Runner = Runner::new();
if *server_name != "internal" {
let Some(servers) = config::servers().servers else {
crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
};
if let Some(server) = servers.get(server_name) {
- runner = match Runner::connect(server_name.clone(), server.clone(), false) {
+ runner = match Runner::connect(server_name.clone(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
let item = runner.clone().process(*id).clone();
let log_out = http::logs(&runner.remote.as_ref().unwrap(), *id, "out");
let log_error = http::logs(&runner.remote.as_ref().unwrap(), *id, "error");
println!("{}", format!("Showing last {lines} lines for process [{id}] (change the value with --lines option)").yellow());
if let Ok(logs) = log_error {
let logs = logs.json::<LogResponse>().unwrap().logs;
file::logs_internal(logs, *lines, &item.name, *id, "error", &item.name)
}
if let Ok(logs) = log_out {
let logs = logs.json::<LogResponse>().unwrap().logs;
file::logs_internal(logs, *lines, &item.name, *id, "out", &item.name)
}
} else {
let item = runner.process(*id);
let log_out = global!("pmc.logs.out", item.name.as_str());
let log_error = global!("pmc.logs.error", item.name.as_str());
if Exists::check(&log_error).file() && Exists::check(&log_out).file() {
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, server_name: &String) {
let mut runner: Runner = Runner::new();
if *server_name != "internal" {
let Some(servers) = config::servers().servers else {
crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
};
if let Some(server) = servers.get(server_name) {
- runner = match Runner::connect(server_name.clone(), server.clone(), false) {
+ runner = match Runner::connect(server_name.clone(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
};
}
}
let item = runner.process(*id);
item.env.iter().for_each(|(key, value)| println!("{}: {}", key, value.green()));
}
pub fn list(format: &String, server_name: &String) {
let render_list = |runner: &mut Runner, internal: bool| {
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 cpu_percent: String = string!("0%");
let mut memory_usage: String = string!("0b");
if internal {
let mut usage_internals: (Option<f32>, Option<MemoryInfo>) = (None, None);
if let Ok(mut process) = Process::new(item.pid as u32) {
usage_internals = (process.cpu_percent().ok(), process.memory_info().ok());
}
cpu_percent = match usage_internals.0 {
Some(percent) => format!("{:.0}%", percent),
None => string!("0%"),
};
memory_usage = match usage_internals.1 {
Some(usage) => helpers::format_memory(usage.rss()),
None => string!("0b"),
};
} else {
let info = http::info(&runner.remote.as_ref().unwrap(), id);
if let Ok(info) = info {
let stats = info.json::<ItemSingle>().unwrap().stats;
cpu_percent = match stats.cpu_percent {
Some(percent) => format!("{:.2}%", percent),
None => string!("0%"),
};
memory_usage = match stats.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![];
if let Some(server) = servers.get(server_name) {
- match Runner::connect(server_name.clone(), server.clone(), true) {
+ match Runner::connect(server_name.clone(), server.get(), true) {
Some(mut remote) => render_list(&mut remote, false),
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(), true);
} 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) {
+ match Runner::connect(name.clone(), server.get(), true) {
Some(mut remote) => render_list(&mut remote, false),
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(), true);
}
}
diff --git a/src/config/structs.rs b/src/config/structs.rs
index c92e219..c20ab31 100644
--- a/src/config/structs.rs
+++ b/src/config/structs.rs
@@ -1,51 +1,60 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub runner: Runner,
pub daemon: Daemon,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Runner {
pub shell: String,
pub args: Vec<String>,
pub node: String,
pub log_path: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Daemon {
pub restarts: u64,
pub interval: u64,
pub kind: String,
pub web: Web,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Web {
pub ui: bool,
pub api: bool,
pub address: String,
pub port: u64,
pub secure: Secure,
pub path: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Secure {
pub enabled: bool,
pub token: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Servers {
pub servers: Option<BTreeMap<String, Server>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Server {
pub address: String,
pub token: Option<String>,
}
+
+impl Server {
+ pub fn get(&self) -> Self {
+ Self {
+ token: self.token.clone(),
+ address: self.address.trim_end_matches('/').to_string(),
+ }
+ }
+}
diff --git a/src/process/mod.rs b/src/process/mod.rs
index b99ab6b..4b50cd3 100644
--- a/src/process/mod.rs
+++ b/src/process/mod.rs
@@ -1,523 +1,523 @@
use crate::{
config,
config::structs::Server,
file, helpers,
service::{run, stop, ProcessMetadata},
};
use std::{
env,
path::PathBuf,
sync::{Arc, Mutex},
};
use chrono::serde::ts_milliseconds;
use chrono::{DateTime, Utc};
use global_placeholders::global;
use macros_rs::{crashln, string, ternary, then};
use psutil::process;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::{BTreeMap, HashMap};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct ItemSingle {
pub info: Info,
pub stats: Stats,
pub watch: Watch,
pub log: Log,
pub raw: Raw,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Info {
pub id: usize,
pub pid: i64,
pub name: String,
pub status: String,
#[schema(value_type = String, example = "/path")]
pub path: PathBuf,
pub uptime: String,
pub command: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Stats {
pub restarts: u64,
pub start_time: i64,
pub cpu_percent: Option<f32>,
pub memory_usage: Option<MemoryInfo>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct MemoryInfo {
pub rss: u64,
pub vms: u64,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Log {
pub out: String,
pub error: String,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct Raw {
pub running: bool,
pub crashed: bool,
pub 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)]
pub struct ProcessWrapper {
pub id: usize,
pub runner: Arc<Mutex<Runner>>,
}
#[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 config: RemoteConfig,
}
#[derive(Clone, Debug, Deserialize)]
pub struct RemoteConfig {
pub shell: String,
pub args: Vec<String>,
pub log_path: 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 { address, token }: Server, verbose: bool) -> Option<Self> {
- let remote_config = match config::from(&address.trim_end_matches('/'), token.as_deref()) {
+ let remote_config = match config::from(&address, token.as_deref()) {
Ok(config) => config,
Err(err) => {
log::error!("{err}");
return None;
}
};
- if let Ok(dump) = dump::from(&address.trim_end_matches('/'), token.as_deref()) {
+ if let Ok(dump) = dump::from(&address, token.as_deref()) {
then!(verbose, println!("{} Fetched remote (name={name}, address={address})", *helpers::SUCCESS));
Some(Runner {
remote: Some(Remote {
token,
address: string!(address),
config: remote_config,
}),
..dump
})
} else {
None
}
}
pub fn start(&mut self, name: &String, command: &String, path: PathBuf, watch: &Option<String>) -> &mut Self {
if let Some(remote) = &self.remote {
if let Err(err) = http::create(remote, name, command, path, watch) {
crashln!("{} Failed to start create {name}\nError: {:#?}", *helpers::FAIL, err);
};
} else {
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,
path,
watch,
crash,
restarts: 0,
running: true,
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 {
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 process = self.process(id);
let config = config::read().runner;
let Process { path, script, name, .. } = process.clone();
if let Err(err) = std::env::set_current_dir(&process.path) {
crashln!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err);
};
stop(process.pid);
process.running = false;
process.crash.crashed = false;
process.pid = run(ProcessMetadata {
args: config.args,
name: name.clone(),
shell: config.shell,
log_path: config.log_path,
command: script.to_string(),
});
process.running = true;
process.started = Utc::now();
then!(!dead, process.crash.value = 0);
then!(dead, process.restarts += 1);
}
return self;
}
pub fn remove(&mut self, id: usize) {
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.process(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 process(&mut self, id: usize) -> &mut Process { self.list.get_mut(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)) }
pub fn get(self, id: usize) -> ProcessWrapper {
ProcessWrapper {
id,
runner: Arc::new(Mutex::new(self)),
}
}
pub fn set_crashed(&mut self, id: usize) -> &mut Self {
self.process(id).crash.crashed = true;
return self;
}
pub fn new_crash(&mut self, id: usize) -> &mut Self {
self.process(id).crash.value += 1;
return self;
}
pub fn stop(&mut self, id: usize) -> &mut Self {
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 process = self.process(id);
stop(process.pid);
process.running = false;
process.crash.crashed = false;
process.crash.value = 0;
}
return self;
}
pub fn rename(&mut self, id: usize, name: String) -> &mut Self {
if let Some(remote) = &self.remote {
if let Err(err) = http::rename(remote, id, name) {
crashln!("{} Failed to rename process {id}\nError: {:#?}", *helpers::FAIL, err);
};
} else {
self.process(id).name = name;
}
return self;
}
pub fn watch(&mut self, id: usize, path: &str, enabled: bool) -> &mut Self {
let process = self.process(id);
process.watch = Watch {
enabled,
path: string!(path),
hash: ternary!(enabled, hash::create(process.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) {
let mem_info_psutil = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
memory_usage = Some(MemoryInfo {
rss: mem_info_psutil.as_ref().unwrap().rss(),
vms: mem_info_psutil.as_ref().unwrap().vms(),
});
}
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 ProcessWrapper {
pub fn stop(&mut self) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.stop(self.id).save();
}
pub fn watch(&mut self, path: &str) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.watch(self.id, path, true).save();
}
pub fn disable_watch(&mut self) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.watch(self.id, "", false).save();
}
pub fn rename(&mut self, name: String) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.rename(self.id, name).save();
}
pub fn restart(&mut self) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.restart(self.id, false).save();
}
pub fn crashed(&mut self) {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
runner.new_crash(self.id).save();
runner.restart(self.id, true).save();
}
pub fn json(&mut self) -> Value {
let runner_arc = Arc::clone(&self.runner);
let mut runner = runner_arc.lock().unwrap();
let item = runner.process(self.id);
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(item.pid as u32) {
let mem_info_psutil = process.memory_info().ok();
cpu_percent = process.cpu_percent().ok();
memory_usage = Some(MemoryInfo {
rss: mem_info_psutil.as_ref().unwrap().rss(),
vms: mem_info_psutil.as_ref().unwrap().vms(),
});
}
let status = if item.running {
string!("online")
} else {
match item.crash.crashed {
true => string!("crashed"),
false => string!("stopped"),
}
};
json!(ItemSingle {
info: Info {
status,
id: item.id,
pid: item.pid,
name: item.name.clone(),
path: item.path.clone(),
uptime: helpers::format_duration(item.started),
command: format!("{} {} '{}'", config.shell, config.args.join(" "), item.script.clone()),
},
stats: Stats {
cpu_percent,
memory_usage,
restarts: item.restarts,
start_time: item.started.timestamp_millis(),
},
watch: Watch {
enabled: item.watch.enabled,
hash: item.watch.hash.clone(),
path: item.watch.path.clone(),
},
log: Log {
out: global!("pmc.logs.out", item.name.as_str()),
error: global!("pmc.logs.error", item.name.as_str()),
},
raw: Raw {
running: item.running,
crashed: item.crash.crashed,
crashes: item.crash.value,
}
})
}
}
pub mod dump;
pub mod hash;
pub mod http;
pub mod id;

File Metadata

Mime Type
text/x-diff
Expires
Sun, Feb 1, 1:32 PM (1 d, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
494759
Default Alt Text
(41 KB)

Event Timeline