Compare commits

..

4 Commits

Author SHA1 Message Date
c1d51d31cc fix test
display mountpoints in gui
2022-12-10 13:36:59 +01:00
6991e730d8 api return also disk mountpoints 2022-12-10 13:08:34 +01:00
c7b748f34a set manually bind address 2022-12-10 11:33:55 +01:00
f60033e02c use 0.0.0.0 bind address for web server 2022-12-09 18:40:14 +01:00
12 changed files with 218 additions and 73 deletions

View File

@ -18,18 +18,23 @@ rust-latest:
stage: build_backend
image: rust:latest
script:
- mkdir ./lib/webroot
- cp -r ./app/build/web/* ./lib/webroot
- cargo build -r --manifest-path=lib/Cargo.toml --features static
- mv ./lib/target/release/raid_manager ./lib/target/release/raid_manager_static
- cargo build -r --manifest-path=lib/Cargo.toml
- cargo test -r --manifest-path=lib/Cargo.toml
- cp ./lib/target/release/raid_manager* .
- cd lib
- mkdir ./webroot
- cp -r ../app/build/web/* ./webroot
- cargo build -r --features static
- mv ./target/release/raid_manager ./target/release/raid_manager_static
- cargo build -r
- cargo test -r
- cp ./target/release/raid_manager* ..
artifacts:
expire_in: 2 days
paths:
- "./raid_manager_static"
- "./raid_manager"
cache:
key: shared-rust-latest-cache
paths:
- lib/target/
rust-nightly:
stage: build_backend
@ -41,3 +46,7 @@ rust-nightly:
- cargo build -r --manifest-path=lib/Cargo.toml
- cargo test -r --manifest-path=lib/Cargo.toml
allow_failure: true
cache:
key: shared-rust-nightly-cache
paths:
- lib/target/

View File

@ -59,7 +59,7 @@ class _DiskPageState extends State<DiskPage> {
),
onTap: () {
Provider.of<BreadCrumbController>(context, listen: false)
.pushPage(const DiskInfoPage(), data[idx].name);
.pushPage(DiskInfoPage(disk: data[idx]), data[idx].name);
},
);
},

View File

@ -6,8 +6,9 @@ part 'disk.g.dart';
class Disk {
String name;
int size;
List<String> mountpoints;
Disk(this.name, this.size);
Disk(this.name, this.size, this.mountpoints);
factory Disk.fromJson(Map<String, dynamic> json) => _$DiskFromJson(json);
Map<String, dynamic> toJson() => _$DiskToJson(this);
}

View File

@ -9,9 +9,11 @@ part of 'disk.dart';
Disk _$DiskFromJson(Map<String, dynamic> json) => Disk(
json['name'] as String,
json['size'] as int,
(json['mountpoints'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$DiskToJson(Disk instance) => <String, dynamic>{
'name': instance.name,
'size': instance.size,
'mountpoints': instance.mountpoints,
};

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:raid_manager/utils/file_formatter.dart';
import '../breadcrumb_page/breadcrumb_controller.dart';
import '../types/disk.dart';
class DiskInfoPage extends StatefulWidget {
const DiskInfoPage({Key? key}) : super(key: key);
const DiskInfoPage({Key? key, required this.disk}) : super(key: key);
final Disk disk;
@override
State<DiskInfoPage> createState() => _DiskInfoPageState();
@ -14,8 +17,14 @@ class _DiskInfoPageState extends State<DiskInfoPage> {
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Mysupercool page"),
Text("Name:",style: Theme.of(context).textTheme.labelMedium),
Text(widget.disk.name,style: Theme.of(context).textTheme.labelMedium),
Text("Size:",style: Theme.of(context).textTheme.labelMedium),
Text(widget.disk.size.readableFileSize(),style: Theme.of(context).textTheme.labelMedium),
Text("Mountpoints:",style: Theme.of(context).textTheme.labelMedium),
Text(widget.disk.mountpoints.toString(),style: Theme.of(context).textTheme.labelMedium),
TextButton(
onPressed: () {
Provider.of<BreadCrumbController>(context, listen: false)

36
lib/Cargo.lock generated
View File

@ -573,6 +573,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.4"
@ -646,6 +655,12 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "0.8.5"
@ -678,6 +693,16 @@ dependencies = [
"version_check",
]
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -838,6 +863,7 @@ dependencies = [
"regex",
"rocket",
"rust-embed",
"versions",
]
[[package]]
@ -1456,6 +1482,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "versions"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee97e1d97bd593fb513912a07691b742361b3dd64ad56f2c694ea2dbfe0665d3"
dependencies = [
"itertools",
"nom",
]
[[package]]
name = "walkdir"
version = "2.3.2"

View File

@ -9,6 +9,7 @@ regex = "1"
lazy_static = "1.4.0"
include_dir = "0.7.3"
rust-embed = "6.4.2"
versions = "4.1.0"
[features]
static = []

View File

@ -1,7 +1,7 @@
use std::io::Cursor;
use rust_embed::{EmbeddedFile, RustEmbed};
use rust_embed::RustEmbed;
use rocket::{Data, Request, Response, Route};
use rocket::http::{ContentType, Method, Status};
use rocket::http::{ContentType, Method};
use rocket::http::uri::{Segments};
use rocket::http::uri::fmt::Path;
use rocket::route::{Handler, Outcome};
@ -13,7 +13,8 @@ struct Asset;
#[cfg(feature = "static")]
#[derive(Clone)]
pub struct CustomHandler {}
pub struct CustomHandler {
}
#[cfg(feature = "static")]
impl Into<Vec<Route>> for CustomHandler {
@ -37,12 +38,8 @@ impl Handler for CustomHandler {
path
};
let file_content = <Asset as RustEmbed>::get(path.to_string_lossy().as_ref());
let file_content = match file_content {
None => return Outcome::Failure(Status::NotFound),
Some(c) => c
};
let file_content =
<Asset as RustEmbed>::get(path.to_string_lossy().as_ref()).unwrap();
let content_type: ContentType = path
.extension()
.map(|x| x.to_string_lossy())

View File

@ -7,6 +7,7 @@ mod embed;
use rocket::error::ErrorKind;
use rocket::serde::json::Json;
use rocket::config::{Config};
#[cfg(feature = "static")]
use crate::embed::CustomHandler;
use crate::parser::lsblk_parser::{Disk, parse_lsblk};
@ -26,7 +27,11 @@ fn get_disks() -> Json<Vec<Disk>> {
async fn main() -> Result<(), rocket::Error> {
println!("init server");
println!("access at: http://127.0.0.1:8000/");
let b = rocket::build();
let mut cfg = Config::default();
cfg.address = "0.0.0.0".parse().unwrap();
let b = rocket::custom(cfg);
let b = b.mount("/api", routes![get_raid_devices, get_disks]);
#[cfg(feature = "static")]
let b = b.mount("/", CustomHandler{});

View File

@ -1,12 +1,17 @@
use std::process::Command;
use lazy_static::lazy_static;
use regex::{Regex};
use rocket::serde::json::{serde_json};
use rocket::serde::{Serialize, Deserialize};
use versions::Versioning;
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
pub struct Disk {
name: String,
size: u64,
mountpoints: Vec<String>
}
#[derive(Serialize, Deserialize)]
@ -20,7 +25,10 @@ struct ChildDevice {
ro: bool,
#[serde(rename = "type")]
kind: String,
mountpoints: Vec<Option<String>>,
// new version is with vec of mountpoints
mountpoints: Option<Vec<Option<String>>>,
// old version is with one single mountpoint
mountpoint: Option<String>,
}
#[derive(Serialize, Deserialize)]
@ -34,7 +42,10 @@ struct Blockdevice {
majmin: String,
rm: bool,
ro: bool,
mountpoints: Vec<Option<String>>,
// new version is with vec of mountpoints
mountpoints: Option<Vec<Option<String>>>,
// old version is with one single mountpoint
mountpoint: Option<String>,
children: Option<Vec<ChildDevice>>,
}
@ -44,6 +55,63 @@ struct LsblkT {
blockdevices: Vec<Blockdevice>,
}
fn lsblk_version() -> Versioning {
let output = Command::new("lsblk")
.arg("--version")
.output()
.expect("failed to execute process");
let out = output.stdout;
let out = String::from_utf8(out).unwrap();
lazy_static! {
static ref RE: Regex = Regex::new(r"([0-9]+\.[0-9]+\.[0-9]+)$").unwrap();
}
match RE.captures(&out) {
None => Versioning::default(),
Some(res) => {
if res.len() == 2 {
match Versioning::new(&res[1]){
None => Versioning::default(),
Some(v) => v
}
}else{
Versioning::default()
}
}
}
}
fn parse_lsblk_str(out: String) -> Vec<Disk> {
let v: LsblkT = serde_json::from_str(out.as_str()).unwrap();
let version = lsblk_version();
let mut disks: Vec<Disk> = Vec::new();
for d in v.blockdevices {
if d.kind != "disk" { continue; };
let mut mountpoints: Vec<String> = Vec::new();
if version < Versioning::new("2.37.0").unwrap() {
if d.mountpoint.is_some() {
mountpoints.push(d.mountpoint.unwrap())
}
} else{
if d.mountpoints.is_some() {
let mntpts = d.mountpoints.unwrap();
for p in mntpts {
if p.is_some() {
mountpoints.push(p.unwrap())
}
}
}
}
disks.push(Disk { name: d.name, size: d.size, mountpoints });
}
return disks;
}
pub fn parse_lsblk() -> Vec<Disk> {
let output = Command::new("lsblk")
.arg("-J")
@ -54,13 +122,70 @@ pub fn parse_lsblk() -> Vec<Disk> {
let out = output.stdout;
let out = String::from_utf8(out).unwrap();
let v: LsblkT = serde_json::from_str(out.as_str()).unwrap();
let mut disks: Vec<Disk> = Vec::new();
for d in v.blockdevices {
if d.kind != "disk" { continue; };
disks.push(Disk { name: d.name, size: d.size });
}
return disks;
parse_lsblk_str(out)
}
#[cfg(test)]
mod tests {
use rocket::serde::json::serde_json::json;
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
#[test]
fn test_empty() {
assert_eq!(json!(parse_lsblk_str(r#"
{
"blockdevices": [
{"name":"/dev/loop0", "maj:min":"7:0", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop1", "maj:min":"7:1", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop2", "maj:min":"7:2", "rm":false, "size":107374182400, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop3", "maj:min":"7:3", "rm":false, "size":10737418240, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop4", "maj:min":"7:4", "rm":false, "size":34359738368, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop5", "maj:min":"7:5", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop6", "maj:min":"7:6", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop7", "maj:min":"7:7", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop8", "maj:min":"7:8", "rm":false, "size":32212254720, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop9", "maj:min":"7:9", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop10", "maj:min":"7:10", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop11", "maj:min":"7:11", "rm":false, "size":12884901888, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/loop12", "maj:min":"7:12", "rm":false, "size":8589934592, "ro":false, "type":"loop", "mountpoint":null},
{"name":"/dev/sda", "maj:min":"8:0", "rm":false, "size":120034123776, "ro":false, "type":"disk", "mountpoint":null,
"children": [
{"name":"/dev/sda1", "maj:min":"8:1", "rm":false, "size":1031168, "ro":false, "type":"part", "mountpoint":null},
{"name":"/dev/sda2", "maj:min":"8:2", "rm":false, "size":536870912, "ro":false, "type":"part", "mountpoint":null},
{"name":"/dev/sda3", "maj:min":"8:3", "rm":false, "size":119496187392, "ro":false, "type":"part", "mountpoint":null,
"children": [
{"name":"/dev/mapper/pve-swap", "maj:min":"253:0", "rm":false, "size":4294967296, "ro":false, "type":"lvm", "mountpoint":"[SWAP]"},
{"name":"/dev/mapper/pve-root", "maj:min":"253:1", "rm":false, "size":115196559360, "ro":false, "type":"lvm", "mountpoint":"/"}
]
}
]
},
{"name":"/dev/sdb", "maj:min":"8:16", "rm":false, "size":3000592982016, "ro":false, "type":"disk", "mountpoint":null,
"children": [
{"name":"/dev/md127", "maj:min":"9:127", "rm":false, "size":9001376219136, "ro":false, "type":"raid5", "mountpoint":"/media/3TBRaid"}
]
},
{"name":"/dev/sdc", "maj:min":"8:32", "rm":false, "size":3000592982016, "ro":false, "type":"disk", "mountpoint":null,
"children": [
{"name":"/dev/md127", "maj:min":"9:127", "rm":false, "size":9001376219136, "ro":false, "type":"raid5", "mountpoint":"/media/3TBRaid"}
]
},
{"name":"/dev/sdd", "maj:min":"8:48", "rm":false, "size":4000787030016, "ro":false, "type":"disk", "mountpoint":null,
"children": [
{"name":"/dev/md127", "maj:min":"9:127", "rm":false, "size":9001376219136, "ro":false, "type":"raid5", "mountpoint":"/media/3TBRaid"}
]
},
{"name":"/dev/sde", "maj:min":"8:64", "rm":false, "size":240057409536, "ro":false, "type":"disk", "mountpoint":"/media/hdd1"},
{"name":"/dev/sdf", "maj:min":"8:80", "rm":false, "size":120034123776, "ro":false, "type":"disk", "mountpoint":"/media/hdd2"},
{"name":"/dev/sdg", "maj:min":"8:96", "rm":false, "size":3000592982016, "ro":false, "type":"disk", "mountpoint":null,
"children": [
{"name":"/dev/md127", "maj:min":"9:127", "rm":false, "size":9001376219136, "ro":false, "type":"raid5", "mountpoint":"/media/3TBRaid"}
]
},
{"name":"/dev/nvme0n1", "maj:min":"259:0", "rm":false, "size":1000204886016, "ro":false, "type":"disk", "mountpoint":"/media/hdd3"}
]
}
"#.to_string())).to_string(), r#"[{"mountpoints":[],"name":"/dev/sda","size":120034123776},{"mountpoints":[],"name":"/dev/sdb","size":3000592982016},{"mountpoints":[],"name":"/dev/sdc","size":3000592982016},{"mountpoints":[],"name":"/dev/sdd","size":4000787030016},{"mountpoints":["/media/hdd1"],"name":"/dev/sde","size":240057409536},{"mountpoints":["/media/hdd2"],"name":"/dev/sdf","size":120034123776},{"mountpoints":[],"name":"/dev/sdg","size":3000592982016},{"mountpoints":["/media/hdd3"],"name":"/dev/nvme0n1","size":1000204886016}]"#);
}
}

View File

@ -1,3 +1,2 @@
pub mod mdstat_parser;
pub mod lsblk_parser;
mod smart_parser;

View File

@ -1,39 +0,0 @@
use std::process::Command;
use rocket::serde::json::serde_json;
use rocket::serde::{Deserialize};
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct DiskInfo {
model_family: String,
model_name: String,
serial_number: String,
rotation_rate: u32,
}
fn get_disk_info(diskpath: &str) -> Option<DiskInfo> {
let mut cmd = Command::new("smartctl");
cmd.arg("-i")
.arg("-json")
.arg(diskpath);
let output = match cmd.output() {
Ok(output) => output,
Err(err) => {
println!("error while getting smart info: {}", err);
return None;
}
};
let rawsmart = output.stdout;
let info: DiskInfo = match serde_json::from_slice(&rawsmart) {
Ok(info) => info,
Err(err) => {
println!("{}", err);
return None;
}
};
Some(info)
}