1use crate::config::{AlertsConfig, Config, HostConfig, OverviewSection};
5use crate::demo::DemoHost;
6use crate::remote::{DEFAULT_REMOTE_COMMAND, RemoteTarget};
7
8pub const LOCAL_NAME: &str = "local";
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum HostKind {
14 Local,
16 Remote(RemoteTarget),
18 Demo(DemoHost),
20}
21
22#[derive(Debug, Clone, PartialEq)]
24pub struct HostSpec {
25 pub name: String,
27 pub kind: HostKind,
28 pub sections: Vec<OverviewSection>,
30 pub alerts: AlertsConfig,
32}
33
34impl HostSpec {
35 pub fn is_local(&self) -> bool {
37 self.kind == HostKind::Local
38 }
39}
40
41pub 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 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 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 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 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}