This commit is contained in:
Piyush मिश्रः 2021-06-27 22:15:58 +05:30
commit 964f93b2d0
13 changed files with 2322 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1796
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
Cargo.toml Normal file
View File

@ -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"

33
src/command.rs Normal file
View File

@ -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())
}

45
src/config.rs Normal file
View File

@ -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()
}
}
}

53
src/disks.rs Normal file
View File

@ -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
}

175
src/main.rs Normal file
View File

@ -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
}
}

7
static/awsm.min.css vendored Normal file

File diff suppressed because one or more lines are too long

40
static/commands.html Normal file
View File

@ -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>

45
static/exec.html Normal file
View File

@ -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>

104
static/index.html Normal file
View File

@ -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>

2
static/jquery-3.6.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
static/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB