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 #[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 #[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 #[serde(skip_serializing_if = "Option::is_none")]
55 pub firmware: Option<Firmware>,
56 #[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 #[serde(skip_serializing_if = "Option::is_none")]
83 pub longitude: Option<f64>,
84 #[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}