Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2707400
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
88 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Feb 1, 3:00 PM (1 d, 6 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
494812
Default Alt Text
(88 KB)
Attached To
Mode
rPMC Process Management Controller
Attached
Detach File
Event Timeline
Log In to Comment