Skip to main content

rigtop_core/
hosts.rs

1//! The hosts a front-end monitors: this machine plus remote hosts from the
2//! configuration and the command line.
3
4use crate::config::{AlertsConfig, Config, HostConfig, OverviewSection};
5use crate::demo::DemoHost;
6use crate::remote::{DEFAULT_REMOTE_COMMAND, RemoteTarget};
7
8/// Name of the entry for the machine rigtop runs on.
9pub const LOCAL_NAME: &str = "local";
10
11/// Where a host's data comes from.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum HostKind {
14    /// This machine.
15    Local,
16    /// A remote host reached through SSH.
17    Remote(RemoteTarget),
18    /// A made-up host (`--demo`).
19    Demo(DemoHost),
20}
21
22/// One monitored host.
23#[derive(Debug, Clone, PartialEq)]
24pub struct HostSpec {
25    /// Name for tabs and cards.
26    pub name: String,
27    pub kind: HostKind,
28    /// Overview sections in priority order (host override or the default).
29    pub sections: Vec<OverviewSection>,
30    /// Alert thresholds (the host's own over `[alerts]`).
31    pub alerts: AlertsConfig,
32}
33
34impl HostSpec {
35    /// Whether this is the local machine.
36    pub fn is_local(&self) -> bool {
37        self.kind == HostKind::Local
38    }
39}
40
41/// The local machine first, then `[[hosts]]` from the configuration, then
42/// destinations from the command line that the configuration does not
43/// already list (matched by name or SSH destination).
44pub fn host_specs(
45    config: &Config,
46    extra_destinations: &[String],
47    remote_command: Option<&str>,
48) -> Vec<HostSpec> {
49    let default_sections = config.overview.sections.clone();
50    let mut specs = vec![HostSpec {
51        name: LOCAL_NAME.to_owned(),
52        kind: HostKind::Local,
53        sections: default_sections.clone(),
54        alerts: config.alerts.clone(),
55    }];
56    let mut hosts: Vec<HostConfig> = config.hosts.clone();
57    for destination in extra_destinations {
58        let known = hosts
59            .iter()
60            .any(|h| h.name == *destination || h.destination() == destination);
61        if !known {
62            let mut host = HostConfig::from_destination(destination);
63            host.command = remote_command.map(str::to_owned);
64            hosts.push(host);
65        }
66    }
67    for host in hosts {
68        let command = host.command.as_deref().unwrap_or(DEFAULT_REMOTE_COMMAND);
69        let mut target = RemoteTarget::ssh(host.destination(), command);
70        target.name = host.name.clone();
71        specs.push(HostSpec {
72            // Checked when the configuration was loaded.
73            alerts: config
74                .alerts_for(&host)
75                .unwrap_or_else(|_| config.alerts.clone()),
76            sections: host
77                .sections
78                .clone()
79                .unwrap_or_else(|| default_sections.clone()),
80            name: host.name,
81            kind: HostKind::Remote(target),
82        });
83    }
84    specs
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn local_first_then_config_then_new_command_line_hosts() {
93        let mut config = Config::default();
94        config.hosts.push(HostConfig {
95            name: "nas".to_owned(),
96            ssh: Some("admin@nas.local".to_owned()),
97            command: Some("/opt/rigtop --agent".to_owned()),
98            sections: Some(vec![OverviewSection::Storage]),
99            alerts: Some(toml::from_str("cpu_warning_celsius = 100").unwrap()),
100        });
101        let extra = vec!["admin@nas.local".to_owned(), "zefat".to_owned()];
102        let specs = host_specs(&config, &extra, Some("~/bin/rigtop --agent"));
103        let names: Vec<&str> = specs.iter().map(|s| s.name.as_str()).collect();
104        // "admin@nas.local" is the configured nas, not a new host.
105        assert_eq!(names, vec!["local", "nas", "zefat"]);
106        assert!(specs[0].is_local());
107        assert_eq!(specs[1].sections, vec![OverviewSection::Storage]);
108        assert_eq!(specs[2].sections, OverviewSection::ALL.to_vec());
109        // Thresholds: the host's own over [alerts].
110        assert_eq!(specs[1].alerts.cpu_warning_celsius, 100.0);
111        assert_eq!(specs[2].alerts, config.alerts);
112        match (&specs[1].kind, &specs[2].kind) {
113            (HostKind::Remote(nas), HostKind::Remote(zefat)) => {
114                assert_eq!(nas.name, "nas");
115                assert!(nas.args.ends_with(&[
116                    "admin@nas.local".to_owned(),
117                    "/opt/rigtop --agent".to_owned()
118                ]));
119                // Command-line hosts use --remote-command.
120                assert!(
121                    zefat
122                        .args
123                        .ends_with(&["zefat".to_owned(), "~/bin/rigtop --agent".to_owned()])
124                );
125            }
126            other => panic!("unexpected {other:?}"),
127        }
128    }
129}