meshviewer_models/
lib.rs

1use std::{
2    collections::HashMap,
3    cmp::Ordering
4};
5use chrono::{DateTime,Utc};
6use serde::{Deserialize, Serialize};
7
8#[derive(Default,Serialize, Deserialize)]
9pub struct Meshviewer {
10    pub timestamp: Option<DateTime<Utc>>,
11    pub nodes: Vec<Node>,
12    pub links: Vec<Link>
13}
14
15#[derive(Default,Serialize, Deserialize)]
16pub struct Node {
17    pub firstseen: DateTime<Utc>,
18    pub lastseen: DateTime<Utc>,
19    pub is_online: bool,
20    pub is_gateway: bool,
21    pub clients: u32,
22    pub clients_wifi24: u32,
23    pub clients_wifi5: u32,
24    pub clients_other: u32,
25    pub clients_owe: u32,
26    pub clients_owe24: u32,
27    pub clients_owe5: u32,
28    pub rootfs_usage: f64,
29    pub loadavg: f64,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub memory_usage: Option<f64>,
32    // in yanic always set with omitempty
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub uptime: Option<DateTime<Utc>>,
35    #[serde(skip_serializing_if = "String::is_empty")]
36    pub gateway_nexthop: String,
37    #[serde(skip_serializing_if = "String::is_empty")]
38    pub gateway: String,
39    #[serde(skip_serializing_if = "String::is_empty")]
40    pub gateway6: String,
41    // in yanic always set with omitempty
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub gateway_tq: Option<f64>,
44    pub node_id: String,
45    pub mac: String,
46    pub addresses: Vec<String>,
47    pub domain: String,
48    pub hostname: String,
49    #[serde(skip_serializing_if = "String::is_empty")]
50    pub owner: String,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub location: Option<Location>,
53    // in yanic always set with omitempty
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub firmware: Option<Firmware>,
56    // in yanic always set with omitempty
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub autoupdater: Option<Autoupdater>,
59    pub nproc: u8,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub model: Option<String>
62}
63
64#[derive(Default,Serialize, Deserialize)]
65pub struct Firmware {
66    #[serde(skip_serializing_if = "String::is_empty")]
67    pub base: String,
68    #[serde(skip_serializing_if = "String::is_empty")]
69    pub release: String
70}
71
72#[derive(Default,Serialize, Deserialize)]
73pub struct Autoupdater {
74    pub enabled: bool,
75    #[serde(skip_serializing_if = "String::is_empty")]
76    pub branch: String
77}
78
79#[derive(Default,Serialize, Deserialize)]
80pub struct Location {
81    // in yanic always set with omitempty
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub longitude: Option<f64>,
84    // in yanic always set with omitempty
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub latitude: Option<f64>
87}
88
89#[derive(Debug, PartialEq, Serialize, Deserialize)]
90pub struct Link {
91    #[serde(rename="type")]
92    pub link_type: String,
93    pub source: String,
94    pub target: String,
95    pub source_tq: f64,
96    pub target_tq: f64,
97    pub source_addr: String,
98    pub target_addr: String,
99}
100
101pub struct MeshviewerBuilder {
102    nodes: HashMap<String,Node>,
103    links: HashMap<String,Link>
104}
105
106impl MeshviewerBuilder {
107    pub fn new() -> Self {
108        MeshviewerBuilder{
109            nodes: HashMap::new(),
110            links: HashMap::new()
111        }
112    }
113    pub fn build(self) -> Meshviewer {
114        let mut mv = Meshviewer::default();
115        mv.nodes = self.nodes.into_values().collect();
116        mv.links = self.links.into_values().collect();
117        mv
118    }
119    pub fn add_node(mut self, node: Node) -> Self {
120        let id = node.node_id.clone();
121        self.nodes.insert(id, node);
122        self
123    }
124    pub fn add_link(mut self, link: Link) -> Self {
125        let source_addr = link.source_addr.clone();
126        let target_addr = link.target_addr.clone();
127        let ordering = source_addr.cmp(&target_addr.clone());
128        let id = match ordering {
129            Ordering::Less => [source_addr, "-".to_string(), target_addr].concat(),
130            Ordering::Greater => [target_addr, "-".to_string(), source_addr].concat(),
131            Ordering::Equal => panic!("source and target id are equal"),
132        };
133        let current_tq = match self.links.get(&id.clone()) {
134            Some(stored_link) => match ordering {
135                Ordering::Less =>  stored_link.target_tq,
136                Ordering::Greater =>  stored_link.source_tq,
137                Ordering::Equal => panic!("source and target id are equal"),
138            },
139            None => 0.0
140        };
141        let ordered_link = match ordering {
142            Ordering::Less => Link{
143                link_type: link.link_type,
144                source: link.source,
145                target: link.target,
146                source_addr: link.source_addr,
147                target_addr: link.target_addr,
148                source_tq: link.source_tq,
149                target_tq: current_tq
150            },
151            Ordering::Greater => Link{
152                link_type: link.link_type,
153                source: link.target,
154                target: link.source,
155                source_addr: link.target_addr,
156                target_addr: link.source_addr,
157                source_tq: current_tq,
158                target_tq: link.source_tq
159            },
160            Ordering::Equal => panic!("source and target id are equal"),
161        };
162        self.links.insert(id, ordered_link);
163        self
164    }
165}
166
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[test]
173    fn use_builder() {
174        let mv = MeshviewerBuilder::new()
175            .add_node(Node{
176                firstseen: DateTime::from_timestamp(61, 0).unwrap(),
177                lastseen: DateTime::from_timestamp(61, 0).unwrap(),
178                node_id: "test01".to_string(),
179                is_online: true,
180                ..Default::default()
181            })
182            .add_link(Link{
183                link_type: "wifi5".to_string(),
184                source: "test01".to_string(),
185                target: "test02".to_string(),
186                source_tq: 0.1,
187                target_tq: -9.0,
188                source_addr: "test01-if01".to_string(),
189                target_addr: "test02-if01".to_string(),
190            })
191            .add_link(Link{
192                link_type: "vpn".to_string(),
193                source: "test02".to_string(),
194                target: "test01".to_string(),
195                source_tq: 0.3,
196                target_tq: -9.0,
197                source_addr: "test02-if01".to_string(),
198                target_addr: "test01-if01".to_string(),
199            })
200            .build();
201        assert_eq!(mv.nodes.len(), 1);
202        assert_eq!(mv.links.len(), 1);
203        assert_eq!(mv.links[0], Link{
204            link_type: "vpn".to_string(),
205            source: "test01".to_string(),
206            target: "test02".to_string(),
207            source_tq: 0.1,
208            target_tq: 0.3,
209            source_addr: "test01-if01".to_string(),
210            target_addr: "test02-if01".to_string(),
211        })
212
213        
214    }
215}