[WIP] A simple wake-on-lan service
at main 236 lines 6.4 kB view raw
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}