rustop_rs/
lib.rs

1mod utils;
2use colored::*;
3use glob::glob;
4use regex::Regex;
5use serde::{Deserialize, Serialize};
6use std::default::Default;
7use std::fmt;
8use std::fmt::{Debug, Display};
9use std::fs;
10use std::net::{Ipv4Addr, Ipv6Addr};
11use std::path::Path;
12use std::process::Command;
13use std::str;
14use std::str::FromStr;
15use std::string::String;
16
17fn handle<T: Default, E: Display + Debug>(result: Result<T, E>) -> T {
18    match result {
19        Ok(val) => val,
20        Err(e) => {
21            eprintln!("{:?}", e);
22            Default::default()
23        }
24    }
25}
26
27pub enum SysProperty {
28    CpuInfo,
29    Hostname,
30    OsRelease,
31    Uptime,
32    Mem,
33    NetDev,
34    StorDev,
35    StorMounts,
36    SysBlockDev,
37    Temperature,
38}
39pub enum Memory {
40    SwapTotal,
41    SwapFree,
42    MemTotal,
43    MemFree,
44}
45
46#[derive(Serialize, Deserialize, Debug)]
47pub struct NetworkDevice {
48    name: String,
49    received_bytes: u64,
50    transfered_bytes: u64,
51    ipv4_addr: Ipv4Addr,
52    ipv6_addr: Ipv6Addr,
53}
54impl NetworkDevice {
55    #[allow(dead_code)]
56    fn new() -> NetworkDevice {
57        NetworkDevice {
58            name: "".to_string(),
59            received_bytes: 0,
60            transfered_bytes: 0,
61            ipv4_addr: Ipv4Addr::UNSPECIFIED,
62            ipv6_addr: Ipv6Addr::UNSPECIFIED,
63        }
64    }
65}
66
67#[derive(Serialize, Deserialize, Debug)]
68pub struct Storage {
69    name: String,
70    major: u16,
71    minor: u16,
72    size: u64,
73    partitions: Vec<Partition>,
74}
75impl Storage {
76    #[allow(dead_code)]
77    fn new() -> Storage {
78        Storage {
79            name: "".to_string(),
80            major: 0,
81            minor: 0,
82            size: 0,
83            partitions: vec![],
84        }
85    }
86}
87
88#[derive(Serialize, Deserialize, Debug)]
89pub struct Partition {
90    name: String,
91    major: u16,
92    minor: u16,
93    size: u64,
94    filesystem: String,
95    mountpoint: String,
96}
97
98impl Partition {
99    fn new() -> Partition {
100        Partition {
101            name: "".to_string(),
102            major: 0,
103            minor: 0,
104            size: 0,
105            filesystem: "".to_string(),
106            mountpoint: "".to_string(),
107        }
108    }
109}
110
111#[derive(Serialize, Deserialize, Debug)]
112pub struct VolGroup {
113    name: String,
114    format: String,
115    status: String,
116    size: u64,
117    lvms: Vec<LogVolume>,
118}
119
120impl VolGroup {
121    #[allow(dead_code)]
122    fn new() -> VolGroup {
123        VolGroup {
124            name: "".to_string(),
125            format: "".to_string(),
126            status: "".to_string(),
127            size: 0,
128            lvms: vec![],
129        }
130    }
131}
132
133#[derive(Serialize, Deserialize, Debug)]
134pub struct LogVolume {
135    name: String,
136    vg: String,
137    path: String,
138    status: String,
139    major: u16,
140    minor: u16,
141    size: u64,
142    mountpoint: String,
143}
144
145impl LogVolume {
146    #[allow(dead_code)]
147    fn new() -> LogVolume {
148        LogVolume {
149            name: "".to_string(),
150            vg: "".to_string(),
151            path: "".to_string(),
152            status: "".to_string(),
153            major: 0,
154            minor: 0,
155            size: 0,
156            mountpoint: "".to_string(),
157        }
158    }
159}
160
161#[derive(Serialize, Deserialize, Debug)]
162pub struct Sensor {
163    name: String,
164    temp: f32,
165}
166impl Sensor {
167    #[allow(dead_code)]
168    fn new() -> Sensor {
169        Sensor {
170            name: "".to_string(),
171            temp: 0.,
172        }
173    }
174}
175
176type Sensors = Vec<Sensor>;
177#[derive(Serialize, Deserialize, Debug, Default)]
178pub struct DeviceSensors {
179    name: String,
180    sensors: Sensors,
181}
182impl DeviceSensors {
183    #[allow(dead_code)]
184    fn new() -> DeviceSensors {
185        DeviceSensors {
186            name: "".to_string(),
187            sensors: vec![],
188        }
189    }
190}
191#[derive(Serialize, Deserialize, Debug, Default)]
192pub struct Temperatures {
193    pub devices: Vec<DeviceSensors>,
194}
195
196type Partitions = Vec<Partition>;
197#[derive(Serialize, Deserialize, Debug, Default)]
198pub struct NetworkDevices {
199    pub devices: Vec<NetworkDevice>,
200}
201#[derive(Serialize, Deserialize, Debug, Default)]
202pub struct Storages {
203    pub devices: Vec<Storage>,
204}
205#[derive(Serialize, Deserialize, Debug, Default)]
206pub struct VolGroups {
207    pub vgs: Vec<VolGroup>,
208}
209
210#[derive(Serialize, Deserialize, Debug)]
211pub struct PcInfo {
212    hostname: String,
213    kernel_version: String,
214    uptime: f64,
215    cpu: String,
216    cpu_clock: f32,
217    memory: u64,
218    free_memory: u64,
219    swap: u64,
220    free_swap: u64,
221    pub network_dev: NetworkDevices,
222    pub storage_dev: Storages,
223    pub vgs: VolGroups,
224    graphics_card: String,
225    pub temps: Temperatures,
226}
227impl PcInfo {
228    pub fn new() -> PcInfo {
229        PcInfo {
230            hostname: handle(Get::sysproperty(SysProperty::Hostname)),
231            kernel_version: handle(Get::sysproperty(SysProperty::OsRelease)),
232            uptime: handle(Get::uptime()),
233            cpu: handle(Get::cpu_info()),
234            cpu_clock: handle(Get::cpu_clock()),
235            memory: handle(Get::mem(Memory::MemTotal)),
236            free_memory: handle(Get::mem(Memory::MemFree)),
237            swap: handle(Get::mem(Memory::SwapTotal)),
238            free_swap: handle(Get::mem(Memory::SwapFree)),
239            network_dev: handle(Get::network_dev()),
240            storage_dev: handle(Get::storage_devices()),
241            vgs: handle(Get::vgs()),
242            graphics_card: handle(Get::graphics_card()),
243            temps: handle(Get::temperatures()),
244        }
245    }
246}
247impl Default for PcInfo {
248    fn default() -> PcInfo {
249        PcInfo {
250            hostname: "".to_string(),
251            kernel_version: "".to_string(),
252            uptime: 0.,
253            cpu: "".to_string(),
254            cpu_clock: 0.,
255            memory: 0,
256            free_memory: 0,
257            swap: 0,
258            free_swap: 0,
259            network_dev: NetworkDevices { devices: vec![] },
260            storage_dev: Storages { devices: vec![] },
261            vgs: VolGroups { vgs: vec![] },
262            graphics_card: "".to_string(),
263            temps: Temperatures { devices: vec![] },
264        }
265    }
266}
267
268#[derive(Debug)]
269pub struct Get;
270impl Get {
271    fn path(prop: SysProperty) -> &'static Path {
272        match prop {
273            SysProperty::Hostname => &Path::new("/proc/sys/kernel/hostname"),
274            SysProperty::OsRelease => &Path::new("/proc/sys/kernel/osrelease"),
275            SysProperty::Uptime => &Path::new("/proc/uptime"),
276            SysProperty::Mem => &Path::new("/proc/meminfo"),
277            SysProperty::NetDev => &Path::new("/proc/net/dev"),
278            SysProperty::StorDev => &Path::new("/proc/partitions"),
279            SysProperty::StorMounts => &Path::new("/proc/mounts"),
280            SysProperty::SysBlockDev => &Path::new("/sys/block/*"),
281            SysProperty::CpuInfo => &Path::new("/proc/cpuinfo"),
282            SysProperty::Temperature => &Path::new("/sys/class/hwmon"),
283        }
284    }
285
286    pub fn sysproperty(property: SysProperty) -> Result<String, std::io::Error> {
287        let path = match property {
288            SysProperty::OsRelease => Get::path(SysProperty::OsRelease),
289            SysProperty::Hostname => Get::path(SysProperty::Hostname),
290            _ => &Path::new(""),
291        };
292        Ok(String::from(fs::read_to_string(path)?.trim_end()))
293    }
294
295    pub fn uptime() -> Result<f64, std::io::Error> {
296        let output = fs::read_to_string(Get::path(SysProperty::Uptime))?;
297        Ok(handle(
298            output.split(' ').collect::<Vec<&str>>()[0].parse::<f64>(),
299        ))
300    }
301
302    pub fn cpu_info() -> Result<String, Box<dyn std::error::Error>> {
303        let output = fs::read_to_string(Get::path(SysProperty::CpuInfo))?;
304        let re = Regex::new(r"model name\s*: (.*)")?;
305        Ok(re
306            .captures(&output)
307            .map_or("".to_string(), |x| x[1].to_string()))
308    }
309
310    pub fn mem(target: Memory) -> Result<u64, Box<dyn std::error::Error>> {
311        let output = fs::read_to_string(Get::path(SysProperty::Mem))?;
312        let re = match target {
313            Memory::SwapFree => Regex::new(r"SwapFree:\s*(\d*)")?,
314            Memory::SwapTotal => Regex::new(r"SwapTotal:\s*(\d*)")?,
315            Memory::MemTotal => Regex::new(r"MemTotal:\s*(\d*)")?,
316            Memory::MemFree => Regex::new(r"MemFree:\s*(\d*)")?,
317        };
318        match re.captures(&output).map(|m| handle(m[1].parse::<u64>())) {
319            Some(n) => Ok(n * 1024),
320            _ => Ok(0),
321        }
322    }
323
324    pub fn total_clock_speed() -> Result<f32, Box<dyn std::error::Error>> {
325        let output = fs::read_to_string(Get::path(SysProperty::CpuInfo))?;
326        let re = Regex::new(r"cpu MHz\s*: (.*)")?;
327        Ok(re
328            .captures_iter(&output)
329            .map(|x| handle(x[1].parse::<f32>()))
330            .sum::<f32>())
331    }
332
333    pub fn total_cpu_cores() -> Result<usize, std::io::Error> {
334        Ok(fs::read_to_string(Get::path(SysProperty::CpuInfo))?
335            .rmatches("cpu MHz")
336            .count())
337    }
338
339    pub fn cpu_clock() -> Result<f32, Box<dyn std::error::Error>> {
340        Ok(Get::total_clock_speed()? / Get::total_cpu_cores()? as f32)
341    }
342
343    pub fn network_dev() -> Result<NetworkDevices, Box<dyn std::error::Error>> {
344        let mut devices = vec![];
345        let output = fs::read_to_string(Get::path(SysProperty::NetDev))?;
346        let re =
347            Regex::new(r"([\d\w]*):\s*(\d*)\s*\d*\s*\d*\s*\d*\s*\d*\s*\d*\s*\d*\s*\d*\s*(\d*)")?;
348        for network_dev in re.captures_iter(&output) {
349            devices.push(NetworkDevice {
350                name: network_dev[1].to_string(),
351                received_bytes: handle(network_dev[2].parse::<u64>()),
352                transfered_bytes: handle(network_dev[3].parse::<u64>()),
353                ipv4_addr: Get::ipv4_addr(&network_dev[1])?,
354                ipv6_addr: Get::ipv6_addr(&network_dev[1])?,
355            });
356        }
357        Ok(NetworkDevices { devices })
358    }
359
360    pub fn storage_devices() -> Result<Storages, Box<dyn std::error::Error>> {
361        let mut devices = vec![];
362        let mut sys_block_devs = vec![];
363        for entry in glob(Get::path(SysProperty::SysBlockDev).to_str().unwrap())? {
364            if let Ok(path) = entry {
365                let name = path.strip_prefix("/sys/block/").unwrap();
366                if let Some(str_name) = name.to_str() {
367                    sys_block_devs.push(str_name.to_string())
368                }
369            }
370        }
371
372        let output = fs::read_to_string(Get::path(SysProperty::StorDev))?;
373        let re = Regex::new(r"(?m)^\s*(\d*)\s*(\d*)\s*(\d*)\s([\w\d]*)$")?;
374        for storage_dev in re
375            .captures_iter(&output)
376            .filter(|storage_dev| {
377                !(storage_dev[4].starts_with("loop") || storage_dev[4].starts_with("ram"))
378            })
379            .filter(|storage_dev| &storage_dev[2] == "0")
380        {
381            devices.push(Storage {
382                major: handle(storage_dev[1].parse::<u16>()),
383                minor: handle(storage_dev[2].parse::<u16>()),
384                size: handle(storage_dev[3].parse::<u64>()) * 1024,
385                name: storage_dev[4].to_string(),
386                partitions: handle(Get::storage_partitions(&storage_dev[4])),
387            });
388        }
389
390        Ok(Storages { devices })
391    }
392
393    fn storage_partitions(stor_name: &str) -> Result<Partitions, Box<dyn std::error::Error>> {
394        let mut partitions = vec![];
395        let output = fs::read_to_string(Get::path(SysProperty::StorDev))?;
396        let re = Regex::new(r"(?m)^\s*(\d*)\s*(\d*)\s*(\d*)\s(\w*\d+)$")?;
397        let re2 = Regex::new(r"/dev/(\w*)\s(\S*)\s(\S*)")?;
398        let output2 = fs::read_to_string(Get::path(SysProperty::StorMounts))?;
399
400        for storage_dev in re
401            .captures_iter(&output)
402            .filter(|x| x[4].starts_with(stor_name))
403        {
404            let mut partition = Partition::new();
405            let partition_name = &storage_dev[4];
406
407            for found_partition in re2.captures_iter(&output2) {
408                if &found_partition[1] == partition_name {
409                    partition.mountpoint = found_partition[2].to_string();
410                    partition.filesystem = found_partition[3].to_string();
411                    break;
412                } else {
413                    partition.mountpoint = "".to_string();
414                    partition.filesystem = "".to_string();
415                }
416            }
417            partition.major = handle(storage_dev[1].parse::<u16>());
418            partition.minor = handle(storage_dev[2].parse::<u16>());
419            partition.size = handle(storage_dev[3].parse::<u64>()) * 1024;
420            partition.name = partition_name.to_string();
421            partitions.push(partition);
422        }
423        Ok(partitions)
424    }
425
426    pub fn vgs() -> Result<VolGroups, Box<dyn std::error::Error>> {
427        let mut vgs: Vec<VolGroup> = vec![];
428        let output = fs::read_to_string(Get::path(SysProperty::StorDev))?;
429        let re = Regex::new(r"(?m)\d*\s*dm-")?;
430        if re.captures(&output).is_some() {
431            let cmd = Command::new("vgdisplay").arg("--units").arg("b").output()?;
432            let out = str::from_utf8(&cmd.stdout)?;
433            let re = Regex::new(r"(?m)VG Name\s*(.*)\n.*\n\s*Format\s*(.*)$(?:\n.*){3}\s*VG Status\s*(.*)$(?:\n.*){6}$\s*VG Size\s*(\d*)")?;
434            for vg in re.captures_iter(&out) {
435                vgs.push(VolGroup {
436                    name: vg[1].to_string(),
437                    format: vg[2].to_string(),
438                    status: vg[3].to_string(),
439                    size: handle(vg[4].parse::<u64>()),
440                    lvms: handle(Get::lvms(vg[1].to_string())),
441                })
442            }
443        }
444
445        Ok(VolGroups { vgs })
446    }
447
448    fn lvms(vg_name: String) -> Result<Vec<LogVolume>, Box<dyn std::error::Error>> {
449        let mut lvms_vec: Vec<LogVolume> = vec![];
450        let cmd = Command::new("lvdisplay").arg("--units").arg("b").output()?;
451        let out = str::from_utf8(&cmd.stdout)?;
452        let re = Regex::new(r"(?m)LV Path\s*(.*)\n\s*LV Name\s*(.*)$\s*VG Name\s*(.*)$(?:\n.*){3}$\s*LV Status\s*(.*)\n.*$\n\s*LV Size\s*(\d*).*$(?:\n.*){5}\s*Block device\s*(\d*):(\d*)$")?;
453        for lvm in re.captures_iter(&out).filter(|lvm| lvm[3] == vg_name) {
454            lvms_vec.push(LogVolume {
455                name: lvm[2].to_string(),
456                path: lvm[1].to_string(),
457                vg: lvm[3].to_string(),
458                status: lvm[4].to_string(),
459                size: handle(lvm[5].parse::<u64>()),
460                major: handle(lvm[6].parse::<u16>()),
461                minor: handle(lvm[7].parse::<u16>()),
462                mountpoint: "".to_string(), // Not yet implemented
463            })
464        }
465        Ok(lvms_vec)
466    }
467
468    pub fn graphics_card() -> Result<String, Box<dyn std::error::Error>> {
469        let cmd = Command::new("lspci").output()?;
470        let out = str::from_utf8(&cmd.stdout)?;
471        let re = Regex::new(r"(?m)VGA compatible controller:\s*(.*)$")?;
472        Ok(re
473            .captures(&out)
474            .map_or("".to_string(), |vga| vga[1].to_string()))
475    }
476
477    fn ipv4_addr(interface_name: &str) -> Result<Ipv4Addr, Box<dyn std::error::Error>> {
478        let mut iface_dest = "".to_string();
479        let mut ip_addr = Ipv4Addr::UNSPECIFIED;
480        if interface_name == "lo" {
481            Ok(Ipv4Addr::LOCALHOST)
482        } else {
483            let output = fs::read_to_string("/proc/net/route")?;
484            let re = Regex::new(r"(?m)^([\d\w]*)\s*([\d\w]*)")?;
485            for dest in re.captures_iter(&output) {
486                if &dest[1] == interface_name && &dest[2] != "00000000" {
487                    iface_dest = utils::conv_hex_to_ip(&dest[2])?;
488                }
489            }
490
491            let output = fs::read_to_string("/proc/net/fib_trie")?;
492            let file = output.split('\n').collect::<Vec<&str>>();
493            let re = Regex::new(r"\|--\s+(.*)")?;
494            let mut found = false;
495            for (i, line) in (&file).iter().enumerate() {
496                if line.to_string().contains(&iface_dest) {
497                    found = true;
498                } else if found && line.to_string().contains("/32 host LOCAL") {
499                    ip_addr = match re.captures(&file[i - 1]) {
500                        Some(n) => Ipv4Addr::from_str(&n[1])?,
501                        None => Ipv4Addr::UNSPECIFIED,
502                    };
503                    break;
504                }
505            }
506            Ok(ip_addr)
507        }
508    }
509
510    fn ipv6_addr(interface_name: &str) -> Result<Ipv6Addr, Box<dyn std::error::Error>> {
511        let mut ip_addr = Ipv6Addr::UNSPECIFIED;
512        if interface_name == "lo" {
513            Ok(Ipv6Addr::LOCALHOST)
514        } else {
515            let output = fs::read_to_string("/proc/net/if_inet6")?;
516            let re = Regex::new(r"(?m)^([\d\w]*)\s\d*\s\d*\s\d*\s\d*\s*(.*)$")?;
517            for capture in re.captures_iter(&output) {
518                if &capture[2] == interface_name {
519                    ip_addr = Ipv6Addr::from_str(&format!(
520                        "{}:{}:{}:{}:{}:{}:{}:{}",
521                        &capture[1][..4],
522                        &capture[1][4..8],
523                        &capture[1][8..12],
524                        &capture[1][12..16],
525                        &capture[1][16..20],
526                        &capture[1][20..24],
527                        &capture[1][24..28],
528                        &capture[1][28..32]
529                    ))?;
530                    break;
531                }
532            }
533            Ok(ip_addr)
534        }
535    }
536
537    pub fn temperatures() -> Result<Temperatures, Box<dyn std::error::Error>> {
538        // reconsider if this should really return an error if one of the sensors doesn't have a label f.e.
539        let paths = fs::read_dir(Get::path(SysProperty::Temperature))?;
540        let mut devices: Vec<DeviceSensors> = vec![];
541        let re = Regex::new(r"temp[\d]+_input")?;
542        for dir_entry in paths {
543            let mut sensor_count = 0;
544            let path = dir_entry?.path();
545            let mut dev = DeviceSensors::new();
546            let mut dev_temps: Vec<Sensor> = vec![];
547            dev.name = fs::read_to_string(path.join("name"))?.trim().to_string();
548            for temp_file in fs::read_dir(&path)? {
549                if re.is_match(&temp_file?.path().to_str().unwrap()) {
550                    sensor_count += 1;
551                }
552            }
553            for i in 1..=sensor_count {
554                let mut sensor = Sensor::new();
555                sensor.name = fs::read_to_string(path.join(format!("temp{}_label", i)))
556                    .unwrap_or("".to_string())
557                    .trim()
558                    .to_string();
559                sensor.temp = handle(
560                    fs::read_to_string(path.join(format!("temp{}_input", i)))?
561                        .trim()
562                        .parse::<f32>(),
563                ) / 1000.;
564                dev_temps.push(sensor);
565            }
566            dev.sensors = dev_temps;
567            devices.push(dev);
568        }
569        Ok(Temperatures { devices })
570    }
571}
572
573impl fmt::Display for PcInfo {
574    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575        write!(
576            f,
577            "┌──────────────────────────────────
578│ HOSTNAME:             {}
579│ KERNEL VERSION:       {}
580│ UPTIME:               {}
581│ CPU:                  {}
582│ CPU CLOCK:            {:.2} MHz
583│ GRAPHICS CARD:        {}
584│ MEM:                  {}  {}
585│ MEMFREE:              {}  {}  {}%
586│ SWAP:                 {}  {}
587│ SWAPFREE:             {}  {}  {}%",
588            self.hostname.bold().red(),
589            self.kernel_version.bold(),
590            utils::conv_t(self.uptime).bold(),
591            self.cpu.bold(),
592            self.cpu_clock,
593            self.graphics_card.bold(),
594            utils::conv_b(self.memory).bold(),
595            self.memory.to_string().bold(),
596            utils::conv_b(self.free_memory).bold(),
597            self.free_memory.to_string().bold(),
598            utils::conv_p(self.memory, self.free_memory)
599                .to_string()
600                .bold(),
601            utils::conv_b(self.swap).bold(),
602            self.swap.to_string().bold(),
603            utils::conv_b(self.free_swap).bold(),
604            self.free_swap.to_string().bold(),
605            utils::conv_p(self.swap, self.free_swap).to_string().bold(),
606        )
607    }
608}
609impl fmt::Display for NetworkDevices {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        let mut s = String::new();
612        for dev in &self.devices {
613            s.push_str(&dev.to_string());
614        }
615        write!(f, "\n│ NETWORK DEVICE: {}", s)
616    }
617}
618impl fmt::Display for NetworkDevice {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        write!(
621            f,
622            "
623│   ├─{}──────────────────────────────────
624│   │     Ipv4:     {}
625│   │     Ipv6:     {}
626│   │     DOWN:     {}      {}
627│   │     UP:       {}      {}",
628            self.name.cyan().bold(),
629            self.ipv4_addr,
630            self.ipv6_addr,
631            utils::conv_b(self.received_bytes),
632            self.received_bytes,
633            utils::conv_b(self.transfered_bytes),
634            self.transfered_bytes
635        )
636    }
637}
638impl fmt::Display for Storages {
639    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640        let mut s = String::new();
641        for dev in &self.devices {
642            s.push_str(&dev.to_string());
643        }
644        write!(f, "\n│ STORAGE: {}", s)
645    }
646}
647impl fmt::Display for Storage {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        let mut partitions = String::new();
650        for p in &self.partitions {
651            partitions.push_str(&p.to_string());
652        }
653        write!(
654            f,
655            "
656│   ├─{}──────────────────────────────────
657│   │     MAJ:MIN:     {}:{}
658│   │     SIZE:        {}    {}
659│   │     PARTITIONS: {}",
660            self.name.red().bold(),
661            self.major,
662            self.minor,
663            utils::conv_b(self.size),
664            self.size,
665            partitions
666        )
667    }
668}
669
670impl fmt::Display for Partition {
671    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
672        write!(
673            f,
674            "
675│   │         ├─{}──────────────────────────────────
676│   │         │     MAJ:MIN:     {}:{}
677│   │         │     SIZE:        {}    {}
678│   │         │     FILESYSTEM:  {}
679│   │         │     MOUNTPOINT:  {}",
680            self.name.blue().bold(),
681            self.major,
682            self.minor,
683            utils::conv_b(self.size),
684            self.size,
685            self.filesystem,
686            self.mountpoint
687        )
688    }
689}
690impl fmt::Display for VolGroups {
691    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
692        let mut s = String::new();
693        for dev in &self.vgs {
694            s.push_str(&dev.to_string());
695        }
696        write!(f, "\n│ VOLUME GROUPS: {}", s)
697    }
698}
699impl fmt::Display for VolGroup {
700    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
701        let mut lvms = "".to_string();
702        for p in &self.lvms {
703            lvms.push_str(&p.to_string());
704        }
705        write!(
706            f,
707            "
708│   ├─{}──────────────────────────────────
709│   │     FORMAT:        {}
710│   │     STATUS:        {}
711│   │     SIZE:          {}
712│   │     LVMS: {}",
713            self.name.red().bold(),
714            self.format,
715            self.status,
716            self.size,
717            lvms
718        )
719    }
720}
721impl fmt::Display for LogVolume {
722    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723        write!(
724            f,
725            "
726│   │         ├─{}──────────────────────────────────
727│   │         │     MAJ:MIN:     {}:{}
728│   │         │     SIZE:        {}    {}
729│   │         │     PATH:  {}
730│   │         │     STATUS:  {}
731│   │         │     MOUNTPOINT:  {}",
732            self.name.blue().bold(),
733            self.major,
734            self.minor,
735            utils::conv_b(self.size),
736            self.size,
737            self.path,
738            self.status,
739            self.mountpoint
740        )
741    }
742}
743impl fmt::Display for Temperatures {
744    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745        let mut s = String::new();
746        for dev in &self.devices {
747            s.push_str(&dev.to_string());
748        }
749        write!(f, "\n│ TEMPERATURES: {}", s)
750    }
751}
752impl fmt::Display for DeviceSensors {
753    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754        let mut temps = "".to_string();
755        for temp in &self.sensors {
756            temps.push_str(&temp.to_string());
757        }
758        write!(
759            f,
760            "
761│   ├─{}──────────────────────────────────
762│   │     SENSORS: {}",
763            self.name.red().bold(),
764            temps
765        )
766    }
767}
768impl fmt::Display for Sensor {
769    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770        write!(
771            f,
772            "\n│   │         ├─{} {}°C",
773            self.name.green().bold(),
774            self.temp
775        )
776    }
777}