login.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. use eframe::egui;
  2. use crate::api::ApiClient;
  3. use crate::config::{KioskFilterSettings, KioskUiSettings};
  4. use crate::models::{QueryRequest, OrderBy, UserInfo};
  5. use serde::Deserialize;
  6. use std::collections::HashMap;
  7. #[derive(Debug, Clone, Deserialize)]
  8. pub struct KioskUser {
  9. pub id: i32,
  10. pub username: String,
  11. pub name: String,
  12. pub role_id: i32,
  13. }
  14. #[derive(Debug, Clone, Deserialize)]
  15. pub struct KioskRole {
  16. pub id: i32,
  17. pub name: String,
  18. }
  19. pub enum LoginResult {
  20. None,
  21. Success(UserInfo, String),
  22. }
  23. pub struct KioskLoginView {
  24. users: Vec<KioskUser>,
  25. roles: HashMap<i32, String>,
  26. selected_user: Option<KioskUser>,
  27. pin_input: String,
  28. rfid_input: String,
  29. error_message: Option<String>,
  30. is_loading: bool,
  31. filter_settings: KioskFilterSettings,
  32. ui_settings: KioskUiSettings,
  33. is_rfid_mode: bool,
  34. }
  35. impl KioskLoginView {
  36. pub fn reset(&mut self) {
  37. self.selected_user = None;
  38. self.pin_input.clear();
  39. self.rfid_input.clear();
  40. self.error_message = None;
  41. self.is_rfid_mode = false;
  42. }
  43. pub fn new(filter_settings: KioskFilterSettings, ui_settings: KioskUiSettings) -> Self {
  44. Self {
  45. users: Vec::new(),
  46. roles: HashMap::new(),
  47. selected_user: None,
  48. pin_input: String::new(),
  49. rfid_input: String::new(),
  50. error_message: None,
  51. is_loading: false,
  52. filter_settings,
  53. ui_settings,
  54. is_rfid_mode: false,
  55. }
  56. }
  57. pub fn refresh_users(&mut self, client: &ApiClient, current_user: Option<&UserInfo>) {
  58. self.is_loading = true;
  59. // Fetch Roles first if needed for filtering
  60. if self.filter_settings.role_whitelist_enabled || self.filter_settings.role_blacklist_enabled {
  61. let role_req = QueryRequest {
  62. action: "select".to_string(),
  63. table: "roles".to_string(),
  64. columns: Some(vec!["id".to_string(), "name".to_string()]),
  65. data: None,
  66. r#where: None,
  67. filter: None,
  68. order_by: None,
  69. limit: Some(100),
  70. offset: None,
  71. joins: None,
  72. };
  73. if let Ok(response) = client.query(&role_req) {
  74. if response.success {
  75. if let Some(data) = response.data {
  76. if let Ok(roles) = serde_json::from_value::<Vec<KioskRole>>(data) {
  77. self.roles = roles.into_iter().map(|r| (r.id, r.name)).collect();
  78. }
  79. }
  80. }
  81. }
  82. }
  83. let request = QueryRequest {
  84. action: "select".to_string(),
  85. table: "users".to_string(),
  86. columns: Some(vec!["id".to_string(), "username".to_string(), "name".to_string(), "role_id".to_string()]),
  87. data: None,
  88. r#where: None,
  89. filter: None,
  90. order_by: Some(vec![OrderBy {
  91. column: "name".to_string(),
  92. direction: "ASC".to_string(),
  93. }]),
  94. limit: Some(100),
  95. offset: None,
  96. joins: None,
  97. };
  98. match client.query(&request) {
  99. Ok(response) => {
  100. if response.success {
  101. if let Some(data) = response.data {
  102. match serde_json::from_value::<Vec<KioskUser>>(data) {
  103. Ok(mut users) => {
  104. // Apply filters
  105. users.retain(|u| {
  106. // Hide self
  107. if self.filter_settings.hide_self {
  108. if let Some(curr) = current_user {
  109. if u.username == curr.username {
  110. return false;
  111. }
  112. }
  113. }
  114. // Username Whitelist
  115. if self.filter_settings.username_whitelist_enabled {
  116. if !self.filter_settings.username_whitelist.contains(&u.username) {
  117. return false;
  118. }
  119. }
  120. // Username Blacklist
  121. if self.filter_settings.username_blacklist_enabled {
  122. if self.filter_settings.username_blacklist.contains(&u.username) {
  123. return false;
  124. }
  125. }
  126. // Role Filtering
  127. if self.filter_settings.role_whitelist_enabled || self.filter_settings.role_blacklist_enabled {
  128. if let Some(role_name) = self.roles.get(&u.role_id) {
  129. if self.filter_settings.role_whitelist_enabled {
  130. if !self.filter_settings.role_whitelist.contains(role_name) {
  131. return false;
  132. }
  133. }
  134. if self.filter_settings.role_blacklist_enabled {
  135. if self.filter_settings.role_blacklist.contains(role_name) {
  136. return false;
  137. }
  138. }
  139. }
  140. }
  141. true
  142. });
  143. self.users = users;
  144. self.error_message = None;
  145. }
  146. Err(e) => {
  147. self.error_message = Some(format!("Failed to parse users: {}", e));
  148. }
  149. }
  150. } else {
  151. self.users.clear();
  152. }
  153. } else {
  154. self.error_message = Some(format!("Failed to fetch users: {}", response.error.unwrap_or_default()));
  155. }
  156. }
  157. Err(e) => {
  158. self.error_message = Some(format!("Network error: {}", e));
  159. }
  160. }
  161. self.is_loading = false;
  162. }
  163. pub fn show(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
  164. let mut result = LoginResult::None;
  165. ui.vertical_centered(|ui| {
  166. ui.add_space(50.0);
  167. ui.heading(egui::RichText::new(&self.ui_settings.title).size(32.0));
  168. ui.add_space(30.0);
  169. if self.is_rfid_mode {
  170. result = self.show_rfid_entry(ui, client);
  171. } else if self.selected_user.is_none() {
  172. self.show_user_selection(ui);
  173. } else {
  174. result = self.show_pin_entry(ui, client);
  175. }
  176. });
  177. result
  178. }
  179. fn show_user_selection(&mut self, ui: &mut egui::Ui) {
  180. ui.label(egui::RichText::new("Select User").size(self.ui_settings.font_size));
  181. ui.add_space(20.0);
  182. if self.is_loading {
  183. ui.spinner();
  184. return;
  185. }
  186. if let Some(error) = &self.error_message {
  187. ui.label(egui::RichText::new(error).color(egui::Color32::RED));
  188. ui.add_space(10.0);
  189. }
  190. egui::ScrollArea::vertical().show(ui, |ui| {
  191. ui.vertical_centered(|ui| {
  192. for user in &self.users {
  193. let btn = egui::Button::new(
  194. egui::RichText::new(&user.name).size(self.ui_settings.font_size * 0.8)
  195. ).min_size(egui::vec2(300.0, self.ui_settings.button_height));
  196. if ui.add(btn).clicked() {
  197. self.selected_user = Some(user.clone());
  198. self.pin_input.clear();
  199. self.error_message = None;
  200. }
  201. ui.add_space(10.0);
  202. }
  203. if self.ui_settings.enable_rfid {
  204. ui.add_space(20.0);
  205. let btn = egui::Button::new(
  206. egui::RichText::new("RFID Sign In").size(self.ui_settings.font_size * 0.8)
  207. ).min_size(egui::vec2(300.0, self.ui_settings.button_height));
  208. if ui.add(btn).clicked() {
  209. self.is_rfid_mode = true;
  210. self.rfid_input.clear();
  211. self.error_message = None;
  212. }
  213. }
  214. });
  215. });
  216. }
  217. fn show_rfid_entry(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
  218. ui.label(egui::RichText::new("Scan RFID Tag").size(self.ui_settings.font_size));
  219. ui.add_space(20.0);
  220. // Invisible input that keeps focus to capture RFID scanner input
  221. let response = ui.add(egui::TextEdit::singleline(&mut self.rfid_input).password(true).desired_width(0.0));
  222. // Always request focus
  223. response.request_focus();
  224. // Check for Enter key (scanner usually sends Enter at the end)
  225. // Also check if input length > 0 to avoid empty submissions
  226. if (response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) ||
  227. (ui.input(|i| i.key_pressed(egui::Key::Enter)) && !self.rfid_input.is_empty()) {
  228. return self.attempt_rfid_login(client);
  229. }
  230. ui.add_space(20.0);
  231. ui.spinner();
  232. ui.add_space(20.0);
  233. if ui.add_sized(egui::vec2(150.0, self.ui_settings.button_height), egui::Button::new(egui::RichText::new("Cancel").size(self.ui_settings.font_size * 0.8))).clicked() {
  234. self.is_rfid_mode = false;
  235. self.rfid_input.clear();
  236. self.error_message = None;
  237. }
  238. if let Some(error) = &self.error_message {
  239. ui.add_space(10.0);
  240. ui.label(egui::RichText::new(error).color(egui::Color32::RED));
  241. }
  242. LoginResult::None
  243. }
  244. fn show_pin_entry(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
  245. if let Some(user) = &self.selected_user {
  246. ui.label(egui::RichText::new(format!("Hello, {}", user.name)).size(self.ui_settings.font_size));
  247. ui.add_space(20.0);
  248. ui.label(egui::RichText::new("Enter PIN:").size(self.ui_settings.font_size * 0.8));
  249. let response = ui.add(egui::TextEdit::singleline(&mut self.pin_input).password(true).font(egui::FontId::proportional(self.ui_settings.font_size)));
  250. // Auto-focus PIN input
  251. if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
  252. return self.attempt_login(client);
  253. }
  254. ui.add_space(20.0);
  255. // OSK
  256. if self.ui_settings.enable_osk {
  257. self.show_osk(ui);
  258. ui.add_space(20.0);
  259. }
  260. let result = ui.horizontal(|ui| {
  261. // Align buttons to bottom/center with larger size
  262. let btn_size = egui::vec2(150.0, self.ui_settings.button_height);
  263. let total_width = btn_size.x * 2.0 + 20.0; // 2 buttons + spacing
  264. let available_width = ui.available_width();
  265. let margin = (available_width - total_width) / 2.0;
  266. if margin > 0.0 {
  267. ui.add_space(margin);
  268. }
  269. if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Back").size(self.ui_settings.font_size * 0.8))).clicked() {
  270. self.selected_user = None;
  271. self.pin_input.clear();
  272. self.error_message = None;
  273. }
  274. ui.add_space(20.0);
  275. if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Login").size(self.ui_settings.font_size * 0.8))).clicked() {
  276. return self.attempt_login(client);
  277. }
  278. LoginResult::None
  279. }).inner;
  280. if let Some(error) = &self.error_message {
  281. ui.add_space(10.0);
  282. ui.label(egui::RichText::new(error).color(egui::Color32::RED));
  283. }
  284. return result;
  285. }
  286. LoginResult::None
  287. }
  288. fn show_osk(&mut self, ui: &mut egui::Ui) {
  289. let keys = [
  290. ["1", "2", "3"],
  291. ["4", "5", "6"],
  292. ["7", "8", "9"],
  293. ["CLR", "0", "DEL"],
  294. ];
  295. let btn_size = egui::vec2(80.0, 80.0); // Large touch targets
  296. ui.vertical_centered(|ui| {
  297. for row in keys {
  298. ui.horizontal(|ui| {
  299. // Center the row content
  300. let row_width = btn_size.x * 3.0 + ui.style().spacing.item_spacing.x * 2.0;
  301. let available_width = ui.available_width();
  302. let margin = (available_width - row_width) / 2.0;
  303. if margin > 0.0 {
  304. ui.add_space(margin);
  305. }
  306. for key in row {
  307. if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new(key).size(32.0))).clicked() {
  308. match key {
  309. "CLR" => self.pin_input.clear(),
  310. "DEL" => { self.pin_input.pop(); },
  311. _ => self.pin_input.push_str(key),
  312. }
  313. }
  314. }
  315. });
  316. ui.add_space(10.0);
  317. }
  318. });
  319. }
  320. fn attempt_login(&mut self, client: &ApiClient) -> LoginResult {
  321. if let Some(user) = &self.selected_user {
  322. match client.login_pin(&user.username, &self.pin_input) {
  323. Ok(response) => {
  324. if response.success {
  325. self.error_message = None;
  326. if let (Some(user), Some(token)) = (response.user, response.token) {
  327. return LoginResult::Success(user, token);
  328. } else {
  329. self.error_message = Some("Login successful but no user data or token returned".to_string());
  330. }
  331. } else {
  332. self.error_message = Some(response.error.unwrap_or_else(|| "Login failed".to_string()));
  333. }
  334. }
  335. Err(e) => {
  336. self.error_message = Some(format!("Login error: {}", e));
  337. }
  338. }
  339. }
  340. LoginResult::None
  341. }
  342. fn attempt_rfid_login(&mut self, client: &ApiClient) -> LoginResult {
  343. match client.login_token(&self.rfid_input) {
  344. Ok(response) => {
  345. if response.success {
  346. self.error_message = None;
  347. self.is_rfid_mode = false;
  348. self.rfid_input.clear();
  349. if let (Some(user), Some(token)) = (response.user, response.token) {
  350. return LoginResult::Success(user, token);
  351. } else {
  352. self.error_message = Some("Login successful but no user data or token returned".to_string());
  353. }
  354. } else {
  355. self.error_message = Some(response.error.unwrap_or_else(|| "Login failed".to_string()));
  356. self.rfid_input.clear();
  357. }
  358. }
  359. Err(e) => {
  360. self.error_message = Some(format!("Login error: {}", e));
  361. self.rfid_input.clear();
  362. }
  363. }
  364. LoginResult::None
  365. }
  366. }