Page MenuHomePhorge

No OneTemporary

Size
88 KB
Referenced Files
None
Subscribers
None
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d042e3a..b30e8fa 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,49 +1,49 @@
stages: [build, release]
-image: 'themackabu/rust:zigbuild-1.77.0'
+image: 'themackabu/rust:zigbuild-1.79.0'
before_script:
- mkdir binary
- apt-get update -yqq
- apt-get install -yqq zip clang llvm
- export CC="/usr/bin/clang"
- export CXX="/usr/bin/clang++"
build_linux_amd64:
stage: build
tags: [rust]
only: [/\d+\.\d+\.\d+.*$/]
script:
- cargo zigbuild -r --color always
- zip binary/pmc_${CI_COMMIT_TAG}_linux_amd64.zip target/release/pmc -j
artifacts:
paths: [binary/]
build_linux_aarch64:
stage: build
tags: [rust]
only: [/\d+\.\d+\.\d+.*$/]
script:
- cargo zigbuild -r --target aarch64-unknown-linux-gnu --color always
- zip binary/pmc_${CI_COMMIT_TAG}_linux_aarch64.zip target/aarch64-unknown-linux-gnu/release/pmc -j
artifacts:
paths: [binary/]
build_darwin_amd64:
stage: build
tags: [rust]
only: [/\d+\.\d+\.\d+.*$/]
script:
- cargo zigbuild -r --target x86_64-apple-darwin --color always
- zip binary/pmc_${CI_COMMIT_TAG}_darwin_amd64.zip target/x86_64-apple-darwin/release/pmc -j
artifacts:
paths: [binary/]
build_darwin_aarch64:
stage: build
tags: [rust]
only: [/\d+\.\d+\.\d+.*$/]
script:
- cargo zigbuild -r --target aarch64-apple-darwin --color always
- zip binary/pmc_${CI_COMMIT_TAG}_darwin_arm.zip target/aarch64-apple-darwin/release/pmc -j
artifacts:
paths: [binary/]
diff --git a/.harness-ci.yml b/.harness-ci.yml
index d0f6820..ce1d857 100644
--- a/.harness-ci.yml
+++ b/.harness-ci.yml
@@ -1,33 +1,33 @@
version: 1
kind: pipeline
spec:
stages:
- name: binary_x64
type: ci
spec:
steps:
- name: build
type: run
spec:
- container: themackabu/rust:zigbuild-1.77.0
+ container: themackabu/rust:zigbuild-1.79.0
script: |-
apt-get update -yqq
apt-get install -yqq zip clang llvm
export CC="/usr/bin/clang"
export CXX="/usr/bin/clang++"
cargo zigbuild -r -j 4
zip pmc_${{ build.commit }}-B${{ build.number }}.zip target/release/pmc -j
- spec:
inputs:
access_key: ${{ secrets.get("pmc_s3_key") }}
acl: read-write
bucket: themackabu-bun-cdn
region: us1
path_style: false
endpoint: https://gateway.storjshare.io
secret_key: ${{ secrets.get("pmc_s3_secret") }}
target: gitness
source: pmc_${{ build.commit }}-B${{ build.number }}.zip
name: s3
type: plugin
name: upload
diff --git a/build.rs b/build.rs
index 6c6ad36..cbe98d7 100644
--- a/build.rs
+++ b/build.rs
@@ -1,169 +1,168 @@
use chrono::Datelike;
use flate2::read::GzDecoder;
use reqwest;
use tar::Archive;
use std::{
env,
fs::{self, File},
io::{self, copy},
path::{Path, PathBuf},
process::Command,
};
const NODE_VERSION: &str = "20.11.0";
fn extract_tar_gz(tar: &PathBuf, download_dir: &PathBuf) -> io::Result<()> {
let file = File::open(tar)?;
let decoder = GzDecoder::new(file);
let mut archive = Archive::new(decoder);
archive.unpack(download_dir)?;
Ok(fs::remove_file(tar)?)
}
fn download_file(url: String, destination: &PathBuf, download_dir: &PathBuf) {
if !download_dir.exists() {
fs::create_dir_all(download_dir).unwrap();
}
let mut response = reqwest::blocking::get(url).expect("Failed to send request");
let mut file = File::create(destination).expect("Failed to create file");
copy(&mut response, &mut file).expect("Failed to copy content");
}
fn download_node() -> PathBuf {
#[cfg(target_os = "linux")]
let target_os = "linux";
#[cfg(all(target_os = "macos"))]
let target_os = "darwin";
#[cfg(all(target_arch = "arm"))]
let target_arch = "armv7l";
#[cfg(all(target_arch = "x86_64"))]
let target_arch = "x64";
#[cfg(all(target_arch = "aarch64"))]
let target_arch = "arm64";
let download_url = format!("https://nodejs.org/dist/v{NODE_VERSION}/node-v{NODE_VERSION}-{target_os}-{target_arch}.tar.gz");
/* paths */
let download_dir = Path::new("target").join("downloads");
let node_extract_dir = download_dir.join(format!("node-v{NODE_VERSION}-{target_os}-{target_arch}"));
if node_extract_dir.is_dir() {
return node_extract_dir;
}
/* download node */
let node_archive = download_dir.join(format!("node-v{}-{}.tar.gz", NODE_VERSION, target_os));
download_file(download_url, &node_archive, &download_dir);
/* extract node */
if let Err(err) = extract_tar_gz(&node_archive, &download_dir) {
panic!("Failed to extract Node.js: {:?}", err)
}
println!("cargo:rustc-env=NODE_HOME={}", node_extract_dir.to_str().unwrap());
return node_extract_dir;
}
fn download_then_build(node_extract_dir: PathBuf) {
let base_dir = match fs::canonicalize(node_extract_dir) {
Ok(path) => path,
Err(err) => panic!("{err}"),
};
let bin = &base_dir.join("bin");
let node = &bin.join("node");
let project_dir = &Path::new("src").join("webui");
let npm = &base_dir.join("lib/node_modules/npm/index.js");
/* set path */
let mut paths = match env::var_os("PATH") {
Some(paths) => env::split_paths(&paths).collect::<Vec<PathBuf>>(),
None => vec![],
};
paths.push(bin.clone());
let path = match env::join_paths(paths) {
Ok(joined) => joined,
Err(err) => panic!("{err}"),
};
/* install deps */
Command::new(node)
.args([npm.to_str().unwrap(), "ci"])
.current_dir(project_dir)
.env("PATH", &path)
.status()
.expect("Failed to install dependencies");
/* build frontend */
Command::new(node)
.args(["node_modules/astro/astro.js", "build"])
.current_dir(project_dir)
.env("PATH", &path)
.status()
.expect("Failed to build frontend");
}
fn main() {
#[cfg(target_os = "windows")]
compile_error!("This project is not supported on Windows.");
#[cfg(target_arch = "x86")]
compile_error!("This project is not supported on 32 bit.");
/* version attributes */
let date = chrono::Utc::now();
let profile = env::var("PROFILE").unwrap();
let output = Command::new("git").args(&["rev-parse", "--short=10", "HEAD"]).output().unwrap();
let output_full = Command::new("git").args(&["rev-parse", "HEAD"]).output().unwrap();
println!("cargo:rustc-env=TARGET={}", env::var("TARGET").unwrap());
println!("cargo:rustc-env=GIT_HASH={}", String::from_utf8(output.stdout).unwrap());
println!("cargo:rustc-env=GIT_HASH_FULL={}", String::from_utf8(output_full.stdout).unwrap());
println!("cargo:rustc-env=BUILD_DATE={}-{}-{}", date.year(), date.month(), date.day());
/* profile matching */
match profile.as_str() {
"debug" => println!("cargo:rustc-env=PROFILE=debug"),
- "release" => {
- /* cleanup */
- fs::remove_dir_all(format!("src/webui/dist")).ok();
- println!("cargo:rustc-env=PROFILE=release");
-
- /* pre-build */
- let path = download_node();
- download_then_build(path);
-
- /* cc linking */
- cxx_build::bridge("src/lib.rs")
- .file("lib/bridge.cc")
- .file("lib/process.cc")
- .file("lib/fork.cc")
- .include("lib/include")
- .flag_if_supported("-std=c++17")
- .compile("bridge");
- }
+ "release" => println!("cargo:rustc-env=PROFILE=release"),
_ => println!("cargo:rustc-env=PROFILE=none"),
}
+ /* cleanup */
+ fs::remove_dir_all(format!("src/webui/dist")).ok();
+
+ /* pre-build */
+ let path = download_node();
+ download_then_build(path);
+
+ /* cc linking */
+ cxx_build::bridge("src/lib.rs")
+ .file("lib/bridge.cc")
+ .file("lib/process.cc")
+ .file("lib/fork.cc")
+ .include("lib/include")
+ .flag_if_supported("-std=c++17")
+ .compile("bridge");
+
let watched = vec![
"lib",
"src/lib.rs",
"lib/include",
"src/webui/src",
"src/webui/links.ts",
"src/webui/package.json",
"src/webui/tsconfig.json",
"src/webui/astro.config.mjs",
"src/webui/tailwind.config.mjs",
];
watched.iter().for_each(|file| println!("cargo:rerun-if-changed={file}"));
}
diff --git a/src/cli/args.rs b/src/cli/args.rs
new file mode 100644
index 0000000..a5a04fa
--- /dev/null
+++ b/src/cli/args.rs
@@ -0,0 +1,34 @@
+pub trait Validatable {
+ fn from_id(id: usize) -> Self;
+ fn from_string(s: String) -> Self;
+}
+
+#[derive(Clone)]
+pub enum Args {
+ Id(usize),
+ Script(String),
+}
+
+#[derive(Clone)]
+pub enum Item {
+ Id(usize),
+ Name(String),
+}
+
+impl Validatable for Args {
+ fn from_id(id: usize) -> Self { Args::Id(id) }
+ fn from_string(s: String) -> Self { Args::Script(s) }
+}
+
+impl Validatable for Item {
+ fn from_id(id: usize) -> Self { Item::Id(id) }
+ fn from_string(s: String) -> Self { Item::Name(s) }
+}
+
+pub fn validate<T: Validatable>(s: &str) -> Result<T, String> {
+ if let Ok(id) = s.parse::<usize>() {
+ Ok(T::from_id(id))
+ } else {
+ Ok(T::from_string(s.to_owned()))
+ }
+}
diff --git a/src/cli/internal.rs b/src/cli/internal.rs
index 1fc9b74..681dc93 100644
--- a/src/cli/internal.rs
+++ b/src/cli/internal.rs
@@ -1,131 +1,578 @@
-use macros_rs::{crashln, string};
-use pmc::{config, file, helpers, log, process::Runner};
+use colored::Colorize;
+use macros_rs::{crashln, string, ternary};
+use psutil::process::{MemoryInfo, Process};
use regex::Regex;
+use serde::Serialize;
+use serde_json::json;
+
+use pmc::{
+ config, file,
+ helpers::{self, ColoredString},
+ log,
+ process::{http, ItemSingle, Runner},
+};
+
+use tabled::{
+ settings::{
+ object::{Columns, Rows},
+ style::{BorderColor, Style},
+ themes::Colorization,
+ Color, Modify, Rotate, Width,
+ },
+ Table, Tabled,
+};
pub struct Internal<'i> {
pub id: usize,
pub runner: Runner,
pub kind: String,
- pub server_name: &'i String,
+ pub server_name: &'i str,
}
impl<'i> Internal<'i> {
pub fn create(mut self, script: &String, name: &Option<String>, watch: &Option<String>) {
let config = config::read();
let name = match name {
Some(name) => string!(name),
None => string!(script.split_whitespace().next().unwrap_or_default()),
};
- if matches!(&**self.server_name, "internal" | "local") {
+ if matches!(self.server_name, "internal" | "local") {
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);
self.runner.start(&name, &script, file::cwd(), watch).save();
} else {
self.runner.start(&name, script, file::cwd(), watch).save();
}
} else {
let Some(servers) = config::servers().servers else {
crashln!("{} Failed to read servers", *helpers::FAIL)
};
if let Some(server) = servers.get(self.server_name) {
- match Runner::connect(self.server_name.clone(), server.get(), false) {
+ match Runner::connect(self.server_name.into(), server.get(), false) {
Some(mut remote) => remote.start(&name, script, file::cwd(), watch),
None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
};
} else {
crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name,)
};
}
println!("{} Creating {}process with ({name})", *helpers::SUCCESS, self.kind);
println!("{} {}created ({name}) ✓", *helpers::SUCCESS, self.kind);
}
pub fn restart(self, name: &Option<String>, watch: &Option<String>) {
println!("{} Applying {}action restartProcess on ({})", *helpers::SUCCESS, self.kind, self.id);
- if matches!(&**self.server_name, "internal" | "local") {
+ if matches!(self.server_name, "internal" | "local") {
let mut item = self.runner.get(self.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={})", self.id);
} else {
let Some(servers) = config::servers().servers else {
crashln!("{} Failed to read servers", *helpers::FAIL)
};
if let Some(server) = servers.get(self.server_name) {
- match Runner::connect(self.server_name.clone(), server.get(), false) {
+ match Runner::connect(self.server_name.into(), server.get(), false) {
Some(remote) => {
let mut item = remote.get(self.id);
name.as_ref().map(|n| item.rename(n.trim().replace("\n", "")));
item.restart();
}
None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
}
} else {
crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
};
}
println!("{} restarted {}({}) ✓", *helpers::SUCCESS, self.kind, self.id);
}
pub fn stop(mut self) {
println!("{} Applying {}action stopProcess on ({})", *helpers::SUCCESS, self.kind, self.id);
- if !matches!(&**self.server_name, "internal" | "local") {
+ if !matches!(self.server_name, "internal" | "local") {
let Some(servers) = config::servers().servers else {
crashln!("{} Failed to read servers", *helpers::FAIL)
};
if let Some(server) = servers.get(self.server_name) {
- self.runner = match Runner::connect(self.server_name.clone(), server.get(), false) {
+ self.runner = match Runner::connect(self.server_name.into(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
};
} else {
crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
};
}
self.runner.get(self.id).stop();
println!("{} stopped {}({}) ✓", *helpers::SUCCESS, self.kind, self.id);
log!("process stopped {}(id={})", self.kind, self.id);
}
pub fn remove(mut self) {
println!("{} Applying {}action removeProcess on ({})", *helpers::SUCCESS, self.kind, self.id);
- if !matches!(&**self.server_name, "internal" | "local") {
+ if !matches!(self.server_name, "internal" | "local") {
let Some(servers) = config::servers().servers else {
crashln!("{} Failed to read servers", *helpers::FAIL)
};
if let Some(server) = servers.get(self.server_name) {
- self.runner = match Runner::connect(self.server_name.clone(), server.get(), false) {
+ self.runner = match Runner::connect(self.server_name.into(), server.get(), false) {
Some(remote) => remote,
None => crashln!("{} Failed to remove (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
};
} else {
crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
};
}
self.runner.remove(self.id);
println!("{} removed {}({}) ✓", *helpers::SUCCESS, self.kind, self.id);
log!("process removed (id={})", self.id);
}
+
+ pub fn info(&self, 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,
+ children: 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,
+ "hash": &self.hash.trim(),
+ "watch": &self.watch.trim(),
+ "children": &self.children,
+ "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 ({})", self.kind, self.id).on_bright_white().black());
+ println!(" {}", format!("Use `pmc logs {} [--lines <num>]` to display logs", self.id).white());
+ println!(" {}", format!("Use `pmc env {}` to display environment variables", self.id).white());
+ }
+ };
+ };
+ };
+
+ if matches!(self.server_name, "internal" | "local") {
+ if let Some(home) = home::home_dir() {
+ let config = config::read().runner;
+ let mut runner = Runner::new();
+ let item = runner.process(self.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();
+ let children = if item.children.is_empty() { "none".to_string() } else { format!("{:?}", item.children) };
+
+ 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 {
+ children,
+ cpu_percent,
+ memory_usage,
+ id: string!(self.id),
+ restarts: item.restarts,
+ name: item.name.clone(),
+ log_out: item.logs().out,
+ path: format!("{} ", path),
+ log_error: item.logs().error,
+ status: ColoredString(status),
+ 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 data: (pmc::process::Process, Runner);
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Failed to read servers", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(self.server_name) {
+ data = match Runner::connect(self.server_name.into(), server.get(), false) {
+ Some(mut remote) => (remote.process(self.id).clone(), remote),
+ None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
+ };
+ } else {
+ crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
+ };
+
+ let (item, remote) = data;
+ let remote = remote.remote.unwrap();
+ let info = http::info(&remote, self.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 children = if item.children.is_empty() { "none".to_string() } else { format!("{:?}", item.children) };
+
+ let cpu_percent = match stats.cpu_percent {
+ Some(percent) => format!("{percent:.2}%"),
+ None => string!("0%"),
+ };
+
+ let memory_usage = match stats.memory_usage {
+ Some(usage) => helpers::format_memory(usage.rss),
+ None => string!("0b"),
+ };
+
+ let data = vec![Info {
+ children,
+ cpu_percent,
+ memory_usage,
+ id: string!(self.id),
+ path: path.clone(),
+ status: status.into(),
+ restarts: item.restarts,
+ name: item.name.clone(),
+ pid: ternary!(item.running, format!("{pid}", pid = item.pid), string!("n/a")),
+ log_out: format!("{}/{}-out.log", remote.config.log_path, item.name),
+ log_error: format!("{}/{}-error.log", remote.config.log_path, item.name),
+ 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(mut self, lines: &usize) {
+ if !matches!(self.server_name, "internal" | "local") {
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Failed to read servers", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(self.server_name) {
+ self.runner = match Runner::connect(self.server_name.into(), server.get(), false) {
+ Some(remote) => remote,
+ None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
+ };
+ } else {
+ crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
+ };
+
+ let item = self.runner.info(self.id).unwrap_or_else(|| crashln!("{} Process ({}) not found", *helpers::FAIL, self.id));
+ println!(
+ "{}",
+ format!("Showing last {lines} lines for {}process [{}] (change the value with --lines option)", self.kind, self.id).yellow()
+ );
+
+ for kind in vec!["error", "out"] {
+ let logs = http::logs(&self.runner.remote.as_ref().unwrap(), self.id, kind);
+
+ if let Ok(log) = logs {
+ if log.lines.is_empty() {
+ println!("{} No logs found for {}/{kind}", *helpers::FAIL, item.name);
+ continue;
+ }
+
+ file::logs_internal(log.lines, *lines, log.path, self.id, kind, &item.name)
+ }
+ }
+ } else {
+ let item = self.runner.info(self.id).unwrap_or_else(|| crashln!("{} Process ({}) not found", *helpers::FAIL, self.id));
+ println!(
+ "{}",
+ format!("Showing last {lines} lines for {}process [{}] (change the value with --lines option)", self.kind, self.id).yellow()
+ );
+
+ file::logs(item, *lines, "error");
+ file::logs(item, *lines, "out");
+ }
+ }
+
+ pub fn env(mut self) {
+ println!("{}", format!("Showing env for {}process {}:\n", self.kind, self.id).bright_yellow());
+
+ if !matches!(self.server_name, "internal" | "local") {
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Failed to read servers", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(self.server_name) {
+ self.runner = match Runner::connect(self.server_name.into(), server.get(), false) {
+ Some(remote) => remote,
+ None => crashln!("{} Failed to connect (name={}, address={})", *helpers::FAIL, self.server_name, server.address),
+ };
+ } else {
+ crashln!("{} Server '{}' does not exist", *helpers::FAIL, self.server_name)
+ };
+ }
+
+ let item = self.runner.process(self.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: status.into(),
+ cpu: format!("{cpu_percent} "),
+ mem: format!("{memory_usage} "),
+ id: id.to_string().cyan().bold().into(),
+ restarts: format!("{} ", item.restarts),
+ name: format!("{} ", item.name.clone()),
+ 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.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" | "global" | "local") {
+ if *server_name == "all" || *server_name == "global" {
+ 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" || *server_name == "global" {
+ for (name, server) in servers {
+ 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/cli/mod.rs b/src/cli/mod.rs
index fa6b59e..ca89bdb 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -1,520 +1,107 @@
+mod args;
+pub use args::*;
+
pub(crate) mod internal;
pub(crate) mod server;
-use colored::Colorize;
use internal::Internal;
use macros_rs::{crashln, string, ternary};
-use psutil::process::{MemoryInfo, Process};
-use serde::Serialize;
-use serde_json::json;
+use pmc::{helpers, process::Runner};
use std::env;
-use pmc::{
- config, file,
- helpers::{self, ColoredString},
- log,
- process::{http, 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),
-}
-
-#[derive(Clone, Debug)]
-pub enum Item {
- Id(usize),
- Name(String),
-}
-
fn format(server_name: &String) -> (String, String) {
let kind = ternary!(matches!(&**server_name, "internal" | "local"), "", "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 => match env!("GIT_HASH") {
"" => format!("{} ({}) [{}]", env!("CARGO_PKG_VERSION"), env!("BUILD_DATE"), env!("PROFILE")),
hash => format!("{} ({} {hash}) [{}]", env!("CARGO_PKG_VERSION"), env!("BUILD_DATE"), env!("PROFILE")),
},
};
}
pub fn start(name: &Option<String>, args: &Args, watch: &Option<String>, server_name: &String) {
let runner = Runner::new();
let (kind, list_name) = format(server_name);
match args {
Args::Id(id) => Internal { id: *id, runner, server_name, kind }.restart(name, watch),
- Args::Script(script) => match runner.find(&script) {
+ Args::Script(script) => match runner.find(&script, server_name) {
Some(id) => Internal { id, runner, server_name, kind }.restart(name, watch),
None => Internal { id: 0, runner, server_name, kind }.create(script, name, watch),
},
}
- list(&string!("default"), &list_name);
+ Internal::list(&string!("default"), &list_name);
}
pub fn stop(item: &Item, server_name: &String) {
let runner: Runner = Runner::new();
let (kind, list_name) = format(server_name);
match item {
Item::Id(id) => Internal { id: *id, runner, server_name, kind }.stop(),
- Item::Name(name) => match runner.find(&name) {
+ Item::Name(name) => match runner.find(&name, server_name) {
Some(id) => Internal { id, runner, server_name, kind }.stop(),
None => crashln!("{} Process ({name}) not found", *helpers::FAIL),
},
}
- list(&string!("default"), &list_name);
+ Internal::list(&string!("default"), &list_name);
}
pub fn remove(item: &Item, server_name: &String) {
let runner: Runner = Runner::new();
let (kind, _) = format(server_name);
match item {
Item::Id(id) => Internal { id: *id, runner, server_name, kind }.remove(),
- Item::Name(name) => match runner.find(&name) {
+ Item::Name(name) => match runner.find(&name, server_name) {
Some(id) => Internal { id, runner, server_name, kind }.remove(),
None => crashln!("{} Process ({name}) not found", *helpers::FAIL),
},
}
}
-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,
- children: 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,
- "hash": &self.hash.trim(),
- "watch": &self.watch.trim(),
- "children": &self.children,
- "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 matches!(&**server_name, "internal" | "local") {
- 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();
- let children = if item.children.is_empty() { "none".to_string() } else { format!("{:?}", item.children) };
-
- 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 {
- children,
- cpu_percent,
- memory_usage,
- id: string!(id),
- restarts: item.restarts,
- name: item.name.clone(),
- log_out: item.logs().out,
- path: format!("{} ", path),
- log_error: item.logs().error,
- status: ColoredString(status),
- 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 data: (pmc::process::Process, Runner);
- let Some(servers) = config::servers().servers else {
- crashln!("{} Failed to read servers", *helpers::FAIL)
- };
-
- if let Some(server) = servers.get(server_name) {
- data = match Runner::connect(server_name.clone(), server.get(), false) {
- Some(mut remote) => (remote.process(*id).clone(), remote),
- None => crashln!("{} Failed to connect (name={server_name}, address={})", *helpers::FAIL, server.address),
- };
- } else {
- crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
- };
-
- let (item, remote) = data;
- 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 children = if item.children.is_empty() { "none".to_string() } else { format!("{:?}", item.children) };
-
- let cpu_percent = match stats.cpu_percent {
- Some(percent) => format!("{percent:.2}%"),
- None => string!("0%"),
- };
-
- let memory_usage = match stats.memory_usage {
- Some(usage) => helpers::format_memory(usage.rss),
- None => string!("0b"),
- };
-
- let data = vec![Info {
- children,
- cpu_percent,
- memory_usage,
- id: string!(id),
- path: path.clone(),
- status: status.into(),
- restarts: item.restarts,
- name: item.name.clone(),
- pid: ternary!(item.running, format!("{pid}", pid = item.pid), string!("n/a")),
- log_out: format!("{}/{}-out.log", remote.config.log_path, item.name),
- log_error: format!("{}/{}-error.log", remote.config.log_path, item.name),
- 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 !matches!(&**server_name, "internal" | "local") {
- let Some(servers) = config::servers().servers else {
- crashln!("{} Failed to read servers", *helpers::FAIL)
- };
-
- if let Some(server) = servers.get(server_name) {
- 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),
- };
- } else {
- crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
- };
-
- let item = runner.info(*id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL));
- println!("{}", format!("Showing last {lines} lines for process [{id}] (change the value with --lines option)").yellow());
-
- for kind in vec!["error", "out"] {
- let logs = http::logs(&runner.remote.as_ref().unwrap(), *id, kind);
-
- if let Ok(log) = logs {
- if log.lines.is_empty() {
- println!("{} No logs found for {}/{kind}", *helpers::FAIL, item.name);
- continue;
- }
-
- file::logs_internal(log.lines, *lines, log.path, *id, kind, &item.name)
- }
- }
- } else {
- let item = runner.info(*id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL));
- println!("{}", format!("Showing last {lines} lines for process [{id}] (change the value with --lines option)").yellow());
+pub fn info(item: &Item, format: &String, server_name: &String) {
+ let runner: Runner = Runner::new();
+ let (kind, _) = self::format(server_name);
- file::logs(item, *lines, "error");
- file::logs(item, *lines, "out");
+ match item {
+ Item::Id(id) => Internal { id: *id, runner, server_name, kind }.info(format),
+ Item::Name(name) => match runner.find(&name, server_name) {
+ Some(id) => Internal { id, runner, server_name, kind }.info(format),
+ None => crashln!("{} Process ({name}) not found", *helpers::FAIL),
+ },
}
}
-pub fn env(id: &usize, server_name: &String) {
- let mut runner: Runner = Runner::new();
-
- if !matches!(&**server_name, "internal" | "local") {
- let Some(servers) = config::servers().servers else {
- crashln!("{} Failed to read servers", *helpers::FAIL)
- };
+pub fn logs(item: &Item, lines: &usize, server_name: &String) {
+ let runner: Runner = Runner::new();
+ let (kind, _) = format(server_name);
- if let Some(server) = servers.get(server_name) {
- 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),
- };
- } else {
- crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
- };
+ match item {
+ Item::Id(id) => Internal { id: *id, runner, server_name, kind }.logs(lines),
+ Item::Name(name) => match runner.find(&name, server_name) {
+ Some(id) => Internal { id, runner, server_name, kind }.logs(lines),
+ None => crashln!("{} Process ({name}) not found", *helpers::FAIL),
+ },
}
-
- 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: status.into(),
- cpu: format!("{cpu_percent} "),
- mem: format!("{memory_usage} "),
- id: id.to_string().cyan().bold().into(),
- restarts: format!("{} ", item.restarts),
- name: format!("{} ", item.name.clone()),
- 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.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" | "global" | "local") {
- if *server_name == "all" || *server_name == "global" {
- 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" || *server_name == "global" {
- for (name, server) in servers {
- match Runner::connect(name.clone(), server.get(), true) {
- Some(mut remote) => render_list(&mut remote, false),
- None => failed.push((name, server.address)),
- }
- }
- }
+pub fn env(item: &Item, server_name: &String) {
+ let runner: Runner = Runner::new();
+ let (kind, _) = format(server_name);
- 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);
+ match item {
+ Item::Id(id) => Internal { id: *id, runner, server_name, kind }.env(),
+ Item::Name(name) => match runner.find(&name, server_name) {
+ Some(id) => Internal { id, runner, server_name, kind }.env(),
+ None => crashln!("{} Process ({name}) not found", *helpers::FAIL),
+ },
}
}
diff --git a/src/cli/server.rs b/src/cli/server.rs
index 53403da..5d4ea58 100644
--- a/src/cli/server.rs
+++ b/src/cli/server.rs
@@ -1,143 +1,143 @@
use colored::Colorize;
use inquire::{Confirm, Password, PasswordDisplayMode, Select, Text};
use macros_rs::{crashln, string};
use std::{collections::BTreeMap, fs::write};
use pmc::{
config::{
self,
structs::{Server, Servers},
},
helpers,
};
fn save(servers: BTreeMap<String, Server>) {
match home::home_dir() {
Some(path) => {
let path = path.display();
let config_path = format!("{path}/.pmc/servers.toml");
let contents = match toml::to_string(&Servers { servers: Some(servers) }) {
Ok(contents) => contents,
Err(err) => crashln!("{} Cannot parse servers.\n{}", *helpers::FAIL, string!(err).white()),
};
if let Err(err) = write(&config_path, contents) {
crashln!("{} Error writing servers.\n{}", *helpers::FAIL, string!(err).white())
}
}
None => crashln!("{} Impossible to get your home directory", *helpers::FAIL),
}
}
#[derive(Debug)]
struct ServerOption {
name: String,
formatted: String,
}
impl std::fmt::Display for ServerOption {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.formatted, f) }
}
pub fn list(format: &String, log_level: Option<log::Level>) {
let servers = config::servers().servers.take().unwrap_or_else(BTreeMap::new);
let options: Vec<_> = servers
.iter()
.map(|(key, server)| {
let verbose = match log_level {
Some(_) => format!("({})", server.address),
None => string!(),
};
ServerOption {
name: key.clone(),
formatted: format!("{} {}", format!("{key}").bright_yellow(), verbose.white()),
}
})
.collect();
match Select::new("Select a server:", options).prompt() {
- Ok(server) => super::list(format, &server.name),
+ Ok(server) => super::internal::Internal::list(format, &server.name),
Err(_) => crashln!("{}", "Canceled...".white()),
}
}
pub fn new() {
let (name, address, token);
let mut servers = config::servers().servers.take().unwrap_or_else(BTreeMap::new);
match Text::new("Server Name:").prompt() {
Ok(ans) => name = ans,
Err(_) => crashln!("{}", "Canceled...".white()),
}
match Text::new("Server Address:").prompt() {
Ok(ans) => address = ans,
Err(_) => crashln!("{}", "Canceled...".white()),
}
match Password::new("Server Token:")
.with_display_toggle_enabled()
.with_formatter(&|_| String::from("[hidden]"))
.with_display_mode(PasswordDisplayMode::Masked)
.without_confirmation()
.prompt()
{
Ok(ans) => match ans.as_str() {
"" => token = None,
ans => token = Some(string!(ans)),
},
Err(_) => crashln!("{}", "Canceled...".white()),
}
match Confirm::new("Add server? (y/n)").prompt() {
Err(_) => crashln!("{}", "Canceled...".white()),
Ok(false) => {}
Ok(true) => {
if name == "" || address == "" {
crashln!("{} Failed to add new server", *helpers::FAIL)
} else {
servers.insert(name, Server { address, token });
save(servers);
println!("{} Added new server", *helpers::SUCCESS)
}
}
}
}
pub fn remove(name: &String) {
let mut servers = config::servers().servers.take().unwrap_or_else(BTreeMap::new);
if servers.contains_key(name) {
match Confirm::new(&format!("Remove server {name}? (y/n)")).prompt() {
Err(_) => crashln!("{}", "Canceled...".white()),
Ok(false) => {}
Ok(true) => {
servers.remove(name);
save(servers);
println!("{} Removed server (name={name})", *helpers::SUCCESS);
}
}
} else {
println!("{} Server {name} does not exist", *helpers::FAIL);
}
}
pub fn default(name: &Option<String>) {
let servers = config::servers().servers.take().unwrap_or_else(BTreeMap::new);
let name = match name {
Some(name) => name.as_str(),
None => "local",
};
if servers.contains_key(name) || name == "internal" || name == "local" {
config::read().set_default(string!(name)).save();
println!("{} Set default server to {name}", *helpers::SUCCESS)
} else {
println!("{} Server {name} does not exist", *helpers::FAIL);
}
}
diff --git a/src/daemon/fork.rs b/src/daemon/fork.rs
index 6f8941b..b7589c9 100644
--- a/src/daemon/fork.rs
+++ b/src/daemon/fork.rs
@@ -1,61 +1,62 @@
use global_placeholders::global;
use std::{ffi::CString, process::exit};
+#[allow(dead_code)]
pub enum Fork {
Parent(libc::pid_t),
Child,
}
pub fn chdir() -> Result<libc::c_int, i32> {
let dir = CString::new(global!("pmc.base")).expect("CString::new failed");
let res = unsafe { libc::chdir(dir.as_ptr()) };
match res {
-1 => Err(-1),
res => Ok(res),
}
}
pub fn fork() -> Result<Fork, i32> {
let res = unsafe { libc::fork() };
match res {
-1 => Err(-1),
0 => Ok(Fork::Child),
res => Ok(Fork::Parent(res)),
}
}
pub fn setsid() -> Result<libc::pid_t, i32> {
let res = unsafe { libc::setsid() };
match res {
-1 => Err(-1),
res => Ok(res),
}
}
pub fn close_fd() -> Result<i32, i32> {
let mut res = false;
for i in 0..=2 {
res |= unsafe { libc::close(i) } == -1;
}
return match res {
true => Err(-1),
false => Ok(1),
};
}
pub fn daemon(nochdir: bool, noclose: bool) -> Result<Fork, i32> {
match fork() {
Ok(Fork::Parent(_)) => exit(0),
Ok(Fork::Child) => setsid().and_then(|_| {
if !nochdir {
chdir()?;
}
if !noclose {
close_fd()?;
}
fork()
}),
Err(n) => Err(n),
}
}
diff --git a/src/main.rs b/src/main.rs
index b48f573..c9a5f9b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,238 +1,223 @@
mod cli;
mod daemon;
mod globals;
mod webui;
use clap::{Parser, Subcommand};
use clap_verbosity_flag::{LogLevel, Verbosity};
use macros_rs::{str, string, then};
use update_informer::{registry, Check};
use crate::{
- cli::{Args, Item},
+ cli::{internal::Internal, Args, Item},
globals::defaults,
};
-// migrate to helpers
-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()))
- }
-}
-
-// migrate to helpers
-fn validate_item(s: &str) -> Result<Item, String> {
- if let Ok(id) = s.parse::<usize>() {
- Ok(Item::Id(id))
- } else {
- Ok(Item::Name(s.to_owned()))
- }
-}
-
#[derive(Copy, Clone, Debug, Default)]
struct NoneLevel;
impl LogLevel for NoneLevel {
fn default() -> Option<log::Level> { None }
}
#[derive(Parser)]
#[command(version = str!(cli::get_version(false)))]
struct Cli {
#[command(subcommand)]
command: Commands,
#[clap(flatten)]
verbose: Verbosity<NoneLevel>,
}
#[derive(Subcommand)]
enum Daemon {
/// Reset process index
#[command(visible_alias = "clean")]
Reset,
/// Stop daemon
#[command(visible_alias = "kill")]
Stop,
/// Restart daemon
#[command(visible_alias = "restart", visible_alias = "start")]
Restore {
/// Daemon api
#[arg(long)]
api: bool,
/// WebUI using api
#[arg(long)]
webui: bool,
},
/// Check daemon
#[command(visible_alias = "info", visible_alias = "status")]
Health {
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
},
}
#[derive(Subcommand)]
enum Server {
/// Add new server
#[command(visible_alias = "add")]
New,
/// List servers
#[command(visible_alias = "ls")]
List {
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
},
/// Remove server
#[command(visible_alias = "rm")]
Remove {
/// Server name
name: String,
},
/// Set default server
#[command(visible_alias = "set")]
Default {
/// Server name
name: Option<String>,
},
}
// add pmc restore command
#[derive(Subcommand)]
enum Commands {
/// Start/Restart a process
#[command(visible_alias = "restart")]
Start {
/// Process name
#[arg(long)]
name: Option<String>,
- #[clap(value_parser = validate_id_script)]
+ #[clap(value_parser = cli::validate::<Args>)]
args: Args,
/// Watch to reload path
#[arg(long)]
watch: Option<String>,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Stop/Kill a process
#[command(visible_alias = "kill")]
Stop {
- #[clap(value_parser = validate_item)]
+ #[clap(value_parser = cli::validate::<Item>)]
item: Item,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Stop then remove a process
#[command(visible_alias = "rm")]
Remove {
- #[clap(value_parser = validate_item)]
+ #[clap(value_parser = cli::validate::<Item>)]
item: Item,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Get env of a process
#[command(visible_alias = "cmdline")]
Env {
- id: usize,
+ #[clap(value_parser = cli::validate::<Item>)]
+ item: Item,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Get information of a process
#[command(visible_alias = "info")]
Details {
- id: usize,
+ #[clap(value_parser = cli::validate::<Item>)]
+ item: Item,
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// List all processes
#[command(visible_alias = "ls")]
List {
/// Format output
#[arg(long, default_value_t = string!("default"))]
format: String,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Get logs from a process
Logs {
- id: usize,
+ #[clap(value_parser = cli::validate::<Item>)]
+ item: Item,
#[arg(long, default_value_t = 15, help = "")]
lines: usize,
/// Server
#[arg(short, long)]
server: Option<String>,
},
/// Daemon management
#[command(visible_alias = "agent", visible_alias = "bgd")]
Daemon {
#[command(subcommand)]
command: Daemon,
},
/// Server management
#[command(visible_alias = "remote", visible_alias = "srv")]
Server {
#[command(subcommand)]
command: Server,
},
}
fn main() {
let cli = Cli::parse();
let mut env = env_logger::Builder::new();
let level = cli.verbose.log_level_filter();
let informer = update_informer::new(registry::Crates, "pmc", env!("CARGO_PKG_VERSION"));
if let Some(version) = informer.check_version().ok().flatten() {
println!("{} New version is available: {version}", *pmc::helpers::WARN);
}
globals::init();
env.filter_level(level).init();
match &cli.command {
Commands::Start { name, args, watch, server } => cli::start(name, args, watch, &defaults(server)),
Commands::Stop { item, server } => cli::stop(item, &defaults(server)),
Commands::Remove { item, server } => cli::remove(item, &defaults(server)),
- Commands::Env { id, server } => cli::env(id, &defaults(server)),
- Commands::Details { id, format, server } => cli::info(id, format, &defaults(server)),
- Commands::List { format, server } => cli::list(format, &defaults(server)),
- Commands::Logs { id, lines, server } => cli::logs(id, lines, &defaults(server)),
+ Commands::Env { item, server } => cli::env(item, &defaults(server)),
+ Commands::Details { item, format, server } => cli::info(item, format, &defaults(server)),
+ Commands::List { format, server } => Internal::list(format, &defaults(server)),
+ Commands::Logs { item, lines, server } => cli::logs(item, lines, &defaults(server)),
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() != "OFF"),
},
Commands::Server { command } => match command {
Server::New => cli::server::new(),
Server::Remove { name } => cli::server::remove(name),
Server::Default { name } => cli::server::default(name),
Server::List { format } => cli::server::list(format, cli.verbose.log_level()),
},
};
if !matches!(&cli.command, Commands::Daemon { .. }) && !matches!(&cli.command, Commands::Server { .. }) {
then!(!daemon::pid::exists(), daemon::restart(&false, &false, false));
}
}
diff --git a/src/process/mod.rs b/src/process/mod.rs
index 7743222..6ea086a 100644
--- a/src/process/mod.rs
+++ b/src/process/mod.rs
@@ -1,576 +1,595 @@
mod unix;
use crate::{
config,
config::structs::Server,
file, helpers,
service::{run, stop, ProcessMetadata},
};
use std::{
env,
path::PathBuf,
sync::{Arc, Mutex},
};
use nix::{
sys::signal::{kill, Signal},
unistd::Pid,
};
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 std::collections::BTreeMap;
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,
pub children: Vec<i64>,
}
#[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(Clone)]
pub struct LogInfo {
pub out: String,
pub error: String,
}
#[derive(Serialize, Deserialize, 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>>,
}
type Env = BTreeMap<String, String>;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Process {
pub id: usize,
pub pid: i64,
pub env: Env,
pub name: String,
pub path: PathBuf,
pub script: String,
pub restarts: u64,
pub running: bool,
pub crash: Crash,
pub watch: Watch,
pub children: Vec<i64>,
#[serde(with = "ts_milliseconds")]
pub started: DateTime<Utc>,
}
#[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,
}
}
}
macro_rules! lock {
($runner:expr) => {{
match $runner.lock() {
Ok(runner) => runner,
Err(err) => crashln!("Unable to lock mutex: {err}"),
}
}};
}
fn kill_children(children: Vec<i64>) {
for pid in children {
if let Err(err) = kill(Pid::from_raw(pid as i32), Signal::SIGTERM) {
log::error!("Failed to stop pid {pid}: {err:?}");
};
}
}
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, token.as_deref()) {
Ok(config) => config,
Err(err) => {
log::error!("{err}");
return None;
}
};
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,
env: unix::env(),
});
self.list.insert(
id,
Process {
id,
pid,
path,
watch,
crash,
restarts: 0,
running: true,
children: vec![],
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();
kill_children(process.children.clone());
stop(process.pid);
if let Err(err) = std::env::set_current_dir(&path) {
crashln!("{} Failed to set working directory {:?}\nError: {:#?}", *helpers::FAIL, path, err);
};
process.pid = run(ProcessMetadata {
args: config.args,
name: name.clone(),
shell: config.shell,
log_path: config.log_path,
command: script.to_string(),
env: unix::env(),
});
process.running = true;
process.children = vec![];
process.started = Utc::now();
process.crash.crashed = false;
process.env = env::vars().collect();
then!(dead, process.restarts += 1);
then!(dead, process.crash.value += 1);
then!(!dead, process.crash.value = 0);
}
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(&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(&self, id: usize) -> bool { self.list.contains_key(&id) }
pub fn info(&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 pid(&self, id: usize) -> i64 { self.list.get(&id).unwrap_or_else(|| crashln!("{} Process ({id}) not found", *helpers::FAIL)).pid }
- pub fn find(&self, name: &str) -> Option<usize> { self.list.iter().find(|(_, p)| p.name == name).map(|(id, _)| *id) }
-
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 set_children(&mut self, id: usize, children: Vec<i64>) -> &mut Self {
self.process(id).children = children;
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);
kill_children(process.children.clone());
stop(process.pid);
process.running = false;
process.crash.crashed = false;
process.crash.value = 0;
process.children = vec![];
}
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 find(&self, name: &str, server_name: &String) -> Option<usize> {
+ let mut runner = self.clone();
+
+ if !matches!(&**server_name, "internal" | "local") {
+ let Some(servers) = config::servers().servers else {
+ crashln!("{} Failed to read servers", *helpers::FAIL)
+ };
+
+ if let Some(server) = servers.get(server_name) {
+ 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),
+ };
+ } else {
+ crashln!("{} Server '{server_name}' does not exist", *helpers::FAIL)
+ };
+ }
+
+ runner.list.iter().find(|(_, p)| p.name == name).map(|(id, _)| *id)
+ }
+
pub fn fetch(&self) -> Vec<ProcessItem> {
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),
});
}
return processes;
}
}
impl Process {
/// Get a log paths of the process item
pub fn logs(&self) -> LogInfo {
let name = self.name.replace(" ", "_");
LogInfo {
out: global!("pmc.logs.out", name.as_str()),
error: global!("pmc.logs.error", name.as_str()),
}
}
}
impl ProcessWrapper {
/// Stop the process item
pub fn stop(&mut self) { lock!(self.runner).stop(self.id).save(); }
/// Restart the process item
pub fn restart(&mut self) { lock!(self.runner).restart(self.id, false).save(); }
/// Rename the process item
pub fn rename(&mut self, name: String) { lock!(self.runner).rename(self.id, name).save(); }
/// Enable watching a path on the process item
pub fn watch(&mut self, path: &str) { lock!(self.runner).watch(self.id, path, true).save(); }
/// Disable watching on the process item
pub fn disable_watch(&mut self) { lock!(self.runner).watch(self.id, "", false).save(); }
/// Set the process item as crashed
pub fn crashed(&mut self) { lock!(self.runner).restart(self.id, true).save(); }
/// Get a json dump of the process item
pub fn fetch(&self) -> ItemSingle {
let mut runner = lock!(self.runner);
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"),
}
};
ItemSingle {
info: Info {
status,
id: item.id,
pid: item.pid,
name: item.name.clone(),
path: item.path.clone(),
children: item.children.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: item.logs().out,
error: item.logs().error,
},
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, 3:00 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
494812
Default Alt Text
(88 KB)

Event Timeline