[WIP] A simple wake-on-lan service
1use serde::{Deserialize, Serialize};
2use std::{
3 collections::{HashMap, HashSet},
4 fs::File,
5 io::Read,
6 net::IpAddr,
7 path::PathBuf,
8};
9use thiserror::Error;
10
11#[derive(Deserialize, Serialize, Debug, Clone)]
12pub struct Config {
13 #[serde(default = "Config::default_binding")]
14 pub binding: String,
15 #[serde(default = "Config::default_pinned")]
16 pub pinned: Vec<String>,
17 #[serde(default = "Config::default_targets")]
18 pub targets: HashMap<String, Target>,
19 #[serde(default)]
20 pub info: Info,
21 #[serde(default)]
22 pub theme: Theme,
23}
24
25type Colour = (u8, u8, u8);
26
27/// all colours are in rgb
28#[derive(Deserialize, Serialize, Debug, Clone)]
29pub struct Theme {
30 #[serde(default = "Theme::default_background")]
31 pub background: Colour,
32 #[serde(default = "Theme::default_background_main")]
33 pub background_main: Colour,
34 #[serde(default = "Theme::default_background_server")]
35 pub background_server: Colour,
36 #[serde(default = "Theme::default_background_button")]
37 pub background_button: Colour,
38
39 #[serde(default = "Theme::default_text")]
40 pub text: Colour,
41 #[serde(default = "Theme::default_text_secondary")]
42 pub text_secondary: Colour,
43
44 #[serde(default = "Theme::default_accent_success")]
45 pub accent_success: Colour,
46 #[serde(default = "Theme::default_accent_fail")]
47 pub accent_fail: Colour,
48
49 #[serde(default = "Theme::default_link")]
50 pub link: Colour,
51 #[serde(default = "Theme::default_link_visited")]
52 pub link_visited: Colour,
53
54 #[serde(default = "Theme::default_highlight")]
55 pub highlight: Colour,
56 #[serde(default = "Theme::default_highlight_opacity")]
57 pub highlight_opacity: u8,
58
59 #[serde(default = "Theme::default_fonts")]
60 pub fonts: Vec<String>,
61}
62
63#[derive(Deserialize, Serialize, Debug, Clone)]
64pub struct Target {
65 #[serde(with = "crate::utils::mac")]
66 pub mac: crate::mac::MacAddress,
67 #[serde(default, with = "crate::utils::ip::option")]
68 pub ip: Option<IpAddr>,
69 pub url: Option<String>,
70}
71
72#[derive(Deserialize, Serialize, Debug, Clone)]
73pub struct Info {
74 #[serde(default = "Info::default_title")]
75 pub title: String,
76 #[serde(default = "Info::default_links", with = "crate::utils::links")]
77 pub links: Vec<(String, String)>,
78 #[serde(default = "Info::default_icon")]
79 pub icon: PathBuf,
80}
81
82impl Config {
83 fn default_binding() -> String {
84 String::from("0.0.0.0:3000")
85 }
86 fn default_pinned() -> Vec<String> {
87 Vec::new()
88 }
89 fn default_targets() -> HashMap<String, Target> {
90 HashMap::new()
91 }
92}
93
94impl Default for Config {
95 fn default() -> Self {
96 Self {
97 binding: Config::default_binding(),
98 theme: Theme::default(),
99 info: Info::default(),
100 pinned: Config::default_pinned(),
101 targets: Config::default_targets(),
102 }
103 }
104}
105
106impl Info {
107 fn default_title() -> String {
108 String::from("Wake on Lan")
109 }
110
111 fn default_links() -> Vec<(String, String)> {
112 vec![(
113 String::from("https://tangled.org/vielle.dev/wol/"),
114 String::from("vielle.dev/wol"),
115 )]
116 }
117
118 fn default_icon() -> PathBuf {
119 PathBuf::from("./favicon.ico")
120 }
121}
122
123impl Default for Info {
124 fn default() -> Self {
125 Self {
126 title: Info::default_title(),
127 links: Info::default_links(),
128 icon: Info::default_icon(),
129 }
130 }
131}
132
133impl Theme {
134 fn default_background() -> Colour {
135 (48, 52, 70)
136 }
137 fn default_background_main() -> Colour {
138 (35, 38, 52)
139 }
140 fn default_background_server() -> Colour {
141 (65, 69, 89)
142 }
143 fn default_background_button() -> Colour {
144 (81, 87, 109)
145 }
146
147 fn default_text() -> Colour {
148 (198, 208, 245)
149 }
150 fn default_text_secondary() -> Colour {
151 (165, 173, 206)
152 }
153
154 fn default_accent_success() -> Colour {
155 (166, 209, 137)
156 }
157 fn default_accent_fail() -> Colour {
158 (231, 130, 132)
159 }
160
161 fn default_link() -> Colour {
162 (140, 170, 238)
163 }
164 fn default_link_visited() -> Colour {
165 (202, 158, 230)
166 }
167
168 fn default_highlight() -> Colour {
169 (148, 156, 187)
170 }
171 fn default_highlight_opacity() -> u8 {
172 25
173 }
174
175 fn default_fonts() -> Vec<String> {
176 vec![String::from("system-ui")]
177 }
178}
179
180impl Default for Theme {
181 fn default() -> Self {
182 Self {
183 background: Theme::default_background(),
184 background_main: Theme::default_background_main(),
185 background_server: Theme::default_background_server(),
186 background_button: Theme::default_background_button(),
187 text: Theme::default_text(),
188 text_secondary: Theme::default_text_secondary(),
189 accent_success: Theme::default_accent_success(),
190 accent_fail: Theme::default_accent_fail(),
191 link: Theme::default_link(),
192 link_visited: Theme::default_link_visited(),
193 highlight: Theme::default_highlight(),
194 highlight_opacity: Theme::default_highlight_opacity(),
195 fonts: Theme::default_fonts(),
196 }
197 }
198}
199
200#[derive(Error, Debug)]
201pub enum ConfigError {
202 #[error("Io error: {}", .0)]
203 Io(#[from] std::io::Error),
204 #[error("Deserialize error: {}", .0)]
205 Serde(#[from] toml::de::Error),
206 #[error("Unknown item was pinned: {}", .0)]
207 UnknownPin(String),
208 #[error("Item was pinned more than once: {}", .0)]
209 DupePin(String),
210}
211
212impl Config {
213 pub fn load(path: PathBuf) -> Result<Self, ConfigError> {
214 let mut file = File::open(path)?;
215 let mut contents = String::new();
216 file.read_to_string(&mut contents)?;
217 let config: Self = toml::from_str(&contents)?;
218
219 // all entries in pinned should be keys of targets
220 let targets = config.targets.keys().collect::<Vec<_>>();
221 if let Some(mismatch) = &config.pinned.iter().find(|x| !targets.contains(x)) {
222 return Err(ConfigError::UnknownPin(mismatch.to_string()));
223 };
224
225 let mut uniq = HashSet::<String>::new();
226 if let Some(dupe) = &config
227 .pinned
228 .iter()
229 .find(move |x| !uniq.insert(x.to_string()))
230 {
231 return Err(ConfigError::DupePin(dupe.to_string()));
232 };
233
234 Ok(config)
235 }
236}