Done
This commit is contained in:
commit
964f93b2d0
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "rasp_mgr"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.3"
|
||||
|
||||
tide = "0.16.0"
|
||||
async-std = { version = "1.6.0", features = ["attributes"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
humantime = "2.1.0"
|
||||
|
||||
sys-info = "0.9.0"
|
||||
libc = "0.2.97"
|
||||
mnt = "0.3.1"
|
||||
|
||||
libmedium = "0.5.5"
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
use std::process::Command;
|
||||
|
||||
use tide::Request;
|
||||
|
||||
fn exec(cmd: &mut Command) -> String {
|
||||
let out = cmd.output();
|
||||
|
||||
if out.is_err() {
|
||||
return "Failed to execute command!".to_owned();
|
||||
}
|
||||
|
||||
match String::from_utf8(out.unwrap().stdout) {
|
||||
Ok(out) => return out,
|
||||
Err(_) => return "Request timeout".to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ip_addr(_: Request<()>) -> tide::Result {
|
||||
Ok(exec(Command::new("ip").arg("addr")).into())
|
||||
}
|
||||
|
||||
pub async fn ps(_: Request<()>) -> tide::Result {
|
||||
Ok(exec(&mut Command::new("ps").arg("-aux")).into())
|
||||
}
|
||||
|
||||
pub async fn lsblk(_: Request<()>) -> tide::Result {
|
||||
Ok(exec(&mut Command::new("lsblk")).into())
|
||||
}
|
||||
|
||||
pub async fn arp(_: Request<()>) -> tide::Result {
|
||||
Ok(exec(&mut Command::new("arp")).into())
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
use clap::{App, Arg};
|
||||
|
||||
|
||||
pub struct Config {
|
||||
pub static_dirs: Option<String>,
|
||||
pub addr: String,
|
||||
pub port: String
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn generate() -> Config {
|
||||
let matches = App::new("Rasp Manager")
|
||||
.about("Manager for raspberry pi")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.arg(Arg::with_name("addr")
|
||||
.short("a")
|
||||
.long("addr")
|
||||
.value_name("ADDR")
|
||||
.required(true)
|
||||
.help("Address to listen port on"))
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.value_name("PORT")
|
||||
.required(true)
|
||||
.help("Port to listen"))
|
||||
.arg(Arg::with_name("static_dir")
|
||||
.short("s")
|
||||
.long("static_dir")
|
||||
.value_name("DIR")
|
||||
.help("Directory to host as static"))
|
||||
.get_matches();
|
||||
|
||||
|
||||
Config {
|
||||
static_dirs: match matches.value_of("static_dir") {
|
||||
Some(val) => Some(val.to_owned()),
|
||||
None => None
|
||||
},
|
||||
addr: matches.value_of("addr").unwrap().to_owned(),
|
||||
port: matches.value_of("port").unwrap().to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
|
||||
use std::{ffi::CString, os::raw::c_char};
|
||||
use std::{fmt, error::Error};
|
||||
|
||||
use libc::statvfs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Disk {
|
||||
pub mount: String,
|
||||
pub available: u64,
|
||||
pub total: u64
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DiskStatsError(i32);
|
||||
|
||||
impl fmt::Display for DiskStatsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Failed to get information!")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DiskStatsError {}
|
||||
|
||||
pub fn get_disk_info(mount_path: &str) -> Result<Disk, DiskStatsError> {
|
||||
let mut stat: statvfs = unsafe { std::mem::zeroed() };
|
||||
let ptr = CString::new(mount_path).unwrap();
|
||||
let path = ptr.as_ptr() as *const c_char;
|
||||
unsafe {
|
||||
let o = libc::statvfs(path, &mut stat);
|
||||
if o != 0 {
|
||||
return Err(DiskStatsError(*libc::__errno_location().clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Disk {
|
||||
mount: mount_path.to_owned(),
|
||||
available: stat.f_bsize * stat.f_bavail,
|
||||
total: stat.f_bsize * stat.f_blocks
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_disks_info() -> Vec<Disk> {
|
||||
let mut disks = Vec::new();
|
||||
for x in mnt::get_submounts("").unwrap() {
|
||||
if x.spec.starts_with("/dev") {
|
||||
if let Ok(val) = get_disk_info(x.file.to_str().unwrap()) {
|
||||
disks.push(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
disks
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
use std::{path::Path, process::Command};
|
||||
|
||||
use tide::prelude::*;
|
||||
use tide::{Request, log::warn};
|
||||
|
||||
use libmedium::{parse_hwmons,sensors::{Input, Sensor}};
|
||||
|
||||
mod config;
|
||||
mod command;
|
||||
mod disks;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct SystemInfo {
|
||||
hardware: String,
|
||||
system_name: String,
|
||||
os_version: Option<String>,
|
||||
kernel_ver: String,
|
||||
last_uadate: Option<String>,
|
||||
hostname: String,
|
||||
boot_time: String,
|
||||
cpu_cores_count: u32,
|
||||
cpu_load_avg: f64, // one minute
|
||||
mem_total: f64,
|
||||
mem_used: f64,
|
||||
swap_total: f64,
|
||||
swap_used: f64,
|
||||
disk: Vec<Disk>,
|
||||
temperature: Vec<Temprature>
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Disk {
|
||||
mount: String,
|
||||
total: f64,
|
||||
available: f64
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Temprature {
|
||||
label: String,
|
||||
temp: f64
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
let conf = config::Config::generate();
|
||||
|
||||
tide::log::start();
|
||||
let mut app = tide::new();
|
||||
|
||||
if let Some(val) = conf.static_dirs {
|
||||
let path = Path::new(&val);
|
||||
if path.exists() && path.is_dir() {
|
||||
app.at("/").serve_dir(path.to_str().unwrap())?;
|
||||
} else { warn!("Static Directory dosen't exists!") }
|
||||
|
||||
let path = path.join("index.html");
|
||||
if path.exists() {
|
||||
app.at("/").serve_file(path.to_str().unwrap())?;
|
||||
}
|
||||
}
|
||||
|
||||
app.at("/poweroff").get(poweroff);
|
||||
app.at("/reboot").get(reboot);
|
||||
|
||||
app.at("/sysinfo").get(system_info);
|
||||
|
||||
app.at("/ip_addr").get(command::ip_addr);
|
||||
app.at("/ps").get(command::ps);
|
||||
app.at("/lsblk").get(command::lsblk);
|
||||
app.at("/arp").get(command::arp);
|
||||
|
||||
app.listen(format!("{}:{}", conf.addr, conf.port)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn poweroff(_: Request<()>) -> tide::Result {
|
||||
async_std::task::spawn(async {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(3)).await;
|
||||
Command::new("poweroff").spawn().expect("Failed to poweroff!");
|
||||
});
|
||||
Ok("Reqesting to poweroff. Please see green led for for activity".into())
|
||||
}
|
||||
|
||||
async fn reboot(_: Request<()>) -> tide::Result {
|
||||
async_std::task::spawn(async {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(3)).await;
|
||||
Command::new("reboot").spawn().expect("Failed to reboot!");
|
||||
});
|
||||
Ok("Reqesting to Rebooting.".into())
|
||||
}
|
||||
|
||||
async fn system_info(_: Request<()>) -> tide::Result {
|
||||
let os = sys_info::linux_os_release().unwrap();
|
||||
|
||||
let mut cpu_load_avg = 0.0;
|
||||
if let Ok(ld) = sys_info::loadavg() {
|
||||
cpu_load_avg = ld.one;
|
||||
}
|
||||
|
||||
let mut mem_total = 0.0;
|
||||
let mut mem_used = 0.0;
|
||||
let mut swap_total = 0.0;
|
||||
let mut swap_used = 0.0;
|
||||
if let Ok(info) = sys_info::mem_info() {
|
||||
mem_total = info.total as f64 / 1024.0;
|
||||
mem_used = (info.total - info.free) as f64 / 1024.0;
|
||||
swap_total = info.swap_total as f64 / 1024.0;
|
||||
swap_used = (info.swap_total - info.swap_free) as f64 / 1024.0;
|
||||
}
|
||||
|
||||
let mut disk = Vec::new();
|
||||
for d in disks::get_disks_info() {
|
||||
disk.push(Disk {
|
||||
mount: d.mount,
|
||||
total: d.total as f64 / 1048576.0, // bytes to mb
|
||||
available: d.available as f64 / 1048576.0 // bytes to mb
|
||||
});
|
||||
}
|
||||
|
||||
let mut temperature: Vec<Temprature> = Vec::new();
|
||||
let hwmons = parse_hwmons().unwrap();
|
||||
for (_, _, hwmon) in &hwmons {
|
||||
for (_, temp_sensor) in hwmon.temps() {
|
||||
let tmp = temp_sensor.read_input().unwrap();
|
||||
temperature.push(Temprature {
|
||||
label: temp_sensor.name(),
|
||||
temp: tmp.as_degrees_celsius()
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let boottime = std::time::Duration::from_secs(match sys_info::boottime() {
|
||||
Ok(s) => s.tv_sec as u64,
|
||||
Err(_) => 0
|
||||
});
|
||||
|
||||
let sys_info = SystemInfo {
|
||||
hardware: "".to_owned(),
|
||||
system_name: os.pretty_name.unwrap_or_default(),
|
||||
os_version: os.version,
|
||||
kernel_ver: sys_info::os_release().unwrap_or_default(),
|
||||
last_uadate: last_update(),
|
||||
hostname: sys_info::hostname().unwrap_or_default(),
|
||||
boot_time: humantime::format_duration(boottime).to_string(),
|
||||
cpu_cores_count: sys_info::cpu_num().unwrap_or_default(),
|
||||
cpu_load_avg,
|
||||
mem_total,
|
||||
mem_used,
|
||||
swap_total,
|
||||
swap_used,
|
||||
disk,
|
||||
temperature
|
||||
};
|
||||
|
||||
Ok(json!(sys_info).to_string().into())
|
||||
}
|
||||
|
||||
fn last_update() -> Option<String> {
|
||||
let mut cmd = std::process::Command::new("bash");
|
||||
cmd.args(&["-c", "grep 'pacman -Syu' /var/log/pacman.log | tail -n 1"]);
|
||||
|
||||
let stdout = match cmd.output() {
|
||||
Ok(out) => out.stdout,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
match String::from_utf8(stdout) {
|
||||
Ok(val) => {
|
||||
let s = val.split(" ").next()?;
|
||||
return Some(s[1..s.len()-1].to_owned());
|
||||
}, Err(_) => return None
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="#" />
|
||||
<title>Rasp Manager</title>
|
||||
<link rel="stylesheet" href="awsm.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Rasp Manager</h1>
|
||||
<p>Simple manager to manage Raspberry Pi 4</p>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/commands.html">Commands</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h5>General</h5>
|
||||
<ul>
|
||||
<li><a href="/exec.html?cmd=ps">ps -aux</a></li>
|
||||
<li><a href="/exec.html?cmd=ip_addr">ip addr</a></li>
|
||||
<li><a href="/exec.html?cmd=lsblk">lsblk</a></li>
|
||||
<li><a href="/exec.html?cmd=arp">arp</a></li>
|
||||
</ul>
|
||||
<h5>Power Related</h5>
|
||||
<ul>
|
||||
<li><a href="/poweroff">Power off</a></li>
|
||||
<li><a href="/reboot">Reboot</a></li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer>
|
||||
Made with ❤️ by <a href="https://github.com/PiyushXCoder">Piyush Mishra</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="#" />
|
||||
<title>Rasp Manager</title>
|
||||
<link rel="stylesheet" href="awsm.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Rasp Manager</h1>
|
||||
<p>Simple manager to manage Raspberry Pi 4</p>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/commands.html">Commands</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div id="output">
|
||||
<img src="loading.gif" alt="">
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Made with ❤️ by <a href="https://github.com/PiyushXCoder">Piyush Mishra</a>
|
||||
</footer>
|
||||
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
var command = params.get('cmd');
|
||||
if(command != '' || command != undefined) {
|
||||
$.get("/"+command, function(data) {
|
||||
$('#output').empty().append($('<pre>').append(data));
|
||||
}).fail(function() {
|
||||
$('#output').empty().append($('<span>', {style: 'color: red'})
|
||||
.append('Error in executing command!'));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="#" />
|
||||
<title>Rasp Manager</title>
|
||||
<link rel="stylesheet" href="awsm.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Rasp Manager</h1>
|
||||
<p>Simple manager to manage Raspberry Pi 4</p>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/commands.html">Commands</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<div id="sysinfo">
|
||||
<img src="loading.gif" alt="">
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Made with ❤️ by <a href="https://github.com/PiyushXCoder">Piyush Mishra</a>
|
||||
</footer>
|
||||
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script>
|
||||
$.get("/sysinfo", function(data) {
|
||||
var data = JSON.parse(data);
|
||||
var area = $('#sysinfo');
|
||||
var pushin = function(parent, label, dat) {
|
||||
parent.append($('<b>').append(label))
|
||||
.append(dat)
|
||||
.append('<br>');
|
||||
}
|
||||
area.empty();
|
||||
|
||||
var fset = $('<fieldset>');
|
||||
fset.append($('<legend>').append('System'));
|
||||
pushin(fset, 'System Name: ', data.system_name);
|
||||
if(data.os_version != undefined)
|
||||
pushin(fset, 'Operating System Version: ', data.os_version);
|
||||
pushin(fset, 'Kernel Version: ', data.kernel_ver);
|
||||
if(data.last_uadate != undefined)
|
||||
pushin(fset, 'Last Update: ', data.last_uadate);
|
||||
pushin(fset, 'Hostname: ', data.hostname);
|
||||
pushin(fset, 'Boot Time: ', data.boot_time);
|
||||
area.append(fset);
|
||||
|
||||
var fset = $('<fieldset>');
|
||||
fset.append($('<legend>').append('Cpu'));
|
||||
pushin(fset, 'Core Count: ', data.cpu_cores_count);
|
||||
pushin(fset, 'Load Average(One minute): ', data.cpu_load_avg);
|
||||
area.append(fset);
|
||||
|
||||
var fset = $('<fieldset>');
|
||||
fset.append($('<legend>').append('Memory'));
|
||||
pushin(fset, 'RAM Total: ', data.mem_total.toFixed(2) + ' MB');
|
||||
pushin(fset, 'RAM Used: ', data.mem_used.toFixed(2) + ' MB');
|
||||
pushin(fset, 'SWAP Total: ', data.swap_total.toFixed(2) + ' MB');
|
||||
pushin(fset, 'SWAP Used: ', data.swap_used.toFixed(2) + ' MB');
|
||||
area.append(fset);
|
||||
|
||||
var fset = $('<fieldset>');
|
||||
fset.append($('<legend>').append('Disk'));
|
||||
data.disk.forEach(e => {
|
||||
pushin(fset, '→ '+e.mount, '');
|
||||
var available = e.available;
|
||||
var total = e.total;
|
||||
var unit_available = 'MB';
|
||||
var unit_total = 'MB';
|
||||
|
||||
if(available > 1024) {
|
||||
available /= 1024;
|
||||
unit_available = 'GB';
|
||||
}
|
||||
if(total > 1024) {
|
||||
total /= 1024;
|
||||
unit_total = 'GB';
|
||||
}
|
||||
|
||||
pushin(fset, '', +available.toFixed(2)+ ' '+unit_available+'/' +total.toFixed(2)+ ' '+unit_total);
|
||||
});
|
||||
area.append(fset);
|
||||
|
||||
var fset = $('<fieldset>');
|
||||
fset.append($('<legend>').append('Temperature'));
|
||||
data.temperature.forEach(e => {
|
||||
pushin(fset, '→ '+e.label+': ', e.temp +'°C');
|
||||
});
|
||||
area.append(fset);
|
||||
|
||||
}).fail(function() {
|
||||
$('#sysinfo').empty().append($('<span>', {style: 'color: red'})
|
||||
.append('Error in getting system information!'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Loading…
Reference in New Issue