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