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::{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, Eq)]
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}
31
32impl HostSpec {
33    /// Whether this is the local machine.
34    pub fn is_local(&self) -> bool {
35        self.kind == HostKind::Local
36    }
37}
38
39/// The local machine first, then `[[hosts]]` from the configuration, then
40/// destinations from the command line that the configuration does not
41/// already list (matched by name or SSH destination).
42pub fn host_specs(
43    config: &Config,
44    extra_destinations: &[String],
45    remote_command: Option<&str>,
46) -> Vec<HostSpec> {
47    let default_sections = config.overview.sections.clone();
48    let mut specs = vec![HostSpec {
49        name: LOCAL_NAME.to_owned(),
50        kind: HostKind::Local,
51        sections: default_sections.clone(),
52    }];
53    let mut hosts: Vec<HostConfig> = config.hosts.clone();
54    for destination in extra_destinations {
55        let known = hosts
56            .iter()
57            .any(|h| h.name == *destination || h.destination() == destination);
58        if !known {
59            let mut host = HostConfig::from_destination(destination);
60            host.command = remote_command.map(str::to_owned);
61            hosts.push(host);
62        }
63    }
64    for host in hosts {
65        let command = host.command.as_deref().unwrap_or(DEFAULT_REMOTE_COMMAND);
66        let mut target = RemoteTarget::ssh(host.destination(), command);
67        target.name = host.name.clone();
68        specs.push(HostSpec {
69            sections: host
70                .sections
71                .clone()
72                .unwrap_or_else(|| default_sections.clone()),
73            name: host.name,
74            kind: HostKind::Remote(target),
75        });
76    }
77    specs
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn local_first_then_config_then_new_command_line_hosts() {
86        let mut config = Config::default();
87        config.hosts.push(HostConfig {
88            name: "nas".to_owned(),
89            ssh: Some("admin@nas.local".to_owned()),
90            command: Some("/opt/rigtop --agent".to_owned()),
91            sections: Some(vec![OverviewSection::Storage]),
92        });
93        let extra = vec!["admin@nas.local".to_owned(), "zefat".to_owned()];
94        let specs = host_specs(&config, &extra, Some("~/bin/rigtop --agent"));
95        let names: Vec<&str> = specs.iter().map(|s| s.name.as_str()).collect();
96        // "admin@nas.local" is the configured nas, not a new host.
97        assert_eq!(names, vec!["local", "nas", "zefat"]);
98        assert!(specs[0].is_local());
99        assert_eq!(specs[1].sections, vec![OverviewSection::Storage]);
100        assert_eq!(specs[2].sections, OverviewSection::ALL.to_vec());
101        match (&specs[1].kind, &specs[2].kind) {
102            (HostKind::Remote(nas), HostKind::Remote(zefat)) => {
103                assert_eq!(nas.name, "nas");
104                assert!(nas.args.ends_with(&[
105                    "admin@nas.local".to_owned(),
106                    "/opt/rigtop --agent".to_owned()
107                ]));
108                // Command-line hosts use --remote-command.
109                assert!(
110                    zefat
111                        .args
112                        .ends_with(&["zefat".to_owned(), "~/bin/rigtop --agent".to_owned()])
113                );
114            }
115            other => panic!("unexpected {other:?}"),
116        }
117    }
118}