use eframe::egui; use rust_i18n::t; use crate::installer::{GlobalState, InstallerStep}; /// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı. pub struct UsersStep { show_password: bool, show_password_confirm: bool, show_root_password: bool, show_root_password_confirm: bool, use_same_for_admin: bool, } impl Default for UsersStep { fn default() -> Self { Self { show_password: false, show_password_confirm: false, show_root_password: false, show_root_password_confirm: false, use_same_for_admin: true, } } } /// Gerçek addan geçerli bir Linux kullanıcı adı türetir. /// - Küçük harfe çevirir /// - Türkçe karakterleri ASCII'ye dönüştürür (ş→s, ğ→g, ı→i, ö→o, ü→u, ç→c) /// - Boşlukları `-`'ye çevirir /// - Geçersiz karakterleri temizler fn derive_username(real_name: &str) -> String { let mut name = real_name.to_lowercase(); name = name .replace('ş', "s") .replace('ğ', "g") .replace('ı', "i") .replace('ö', "o") .replace('ü', "u") .replace('ç', "c") .replace(' ', "-") .replace('_', "-"); let valid: String = name.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_').collect(); let trimmed = valid.trim_matches('-').to_string(); if trimmed.is_empty() { "user".to_string() } else if trimmed.len() > 32 { trimmed[..32].trim_end_matches('-').to_string() } else { trimmed } } /// Kullanıcı adının geçerliliğini kontrol eder. /// Linux kuralları: küçük harf veya alt çizgi ile başlamalı, /// en fazla 32 karakter, yalnızca [a-z0-9_-] içermeli. fn is_valid_username(s: &str) -> bool { if s.is_empty() || s.len() > 32 { return false; } let mut chars = s.chars(); // İlk karakter küçük harf veya alt çizgi olmalı match chars.next() { Some(c) if c.is_ascii_lowercase() || c == '_' => {} _ => return false, } chars.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-') } /// Hostname kuralları: 1-63 karakter, yalnızca [a-zA-Z0-9-], tire ile başlayamaz/bitemez. fn is_valid_hostname(s: &str) -> bool { if s.is_empty() || s.len() > 63 { return false; } if s.starts_with('-') || s.ends_with('-') { return false; } s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') } fn password_strength(p: &str) -> (String, egui::Color32) { let len = p.len(); let has_upper = p.chars().any(|c| c.is_uppercase()); let has_digit = p.chars().any(|c| c.is_ascii_digit()); let has_special = p.chars().any(|c| "!@#$%^&*()_+-=[]{}|;':\",./<>?".contains(c)); let score = (len >= 8) as u8 + (len >= 12) as u8 + has_upper as u8 + has_digit as u8 + has_special as u8; match score { 0..=1 => (t!("pwd_very_weak").to_string(), egui::Color32::from_rgb(200, 60, 40)), 2 => (t!("pwd_weak").to_string(), egui::Color32::from_rgb(220, 140, 40)), 3 => (t!("pwd_medium").to_string(), egui::Color32::from_rgb(200, 180, 40)), 4 => (t!("pwd_strong").to_string(), egui::Color32::from_rgb(80, 170, 80)), _ => (t!("pwd_very_strong").to_string(), egui::Color32::from_rgb(40, 140, 60)), } } impl InstallerStep for UsersStep { fn name(&self) -> String { t!("users").to_string() } fn on_enter(&mut self, state: &mut GlobalState) { // Gerçek ad varsa ama kullanıcı adı yoksa türet if !state.real_name.is_empty() && state.username.is_empty() { state.username = derive_username(&state.real_name); } // Hostname boşsa önce /etc/hostname dene if state.hostname.is_empty() { let sys_hostname = std::fs::read_to_string("/etc/hostname") .unwrap_or_default() .trim() .to_string(); if !sys_hostname.is_empty() && sys_hostname != "localhost" { state.hostname = sys_hostname; } } // Hâlâ boşsa ve username varsa username-pc öner if state.hostname.is_empty() && !state.username.is_empty() { state.hostname = format!("{}-pc", state.username); } } fn show(&mut self, ui: &mut egui::Ui, state: &mut GlobalState) { use crate::ui::theme; theme::section_heading(ui, &t!("users_title")); ui.label( egui::RichText::new(t!("users_description")) .color(theme::c_text_dim()) .size(13.0), ); ui.add_space(12.0); // ─── Kullanıcı Bilgileri Grubu ────────────────────────────────── egui::Frame::group(ui.style()) .fill(theme::c_bg_widget()) .rounding(6.0) .inner_margin(egui::Margin::same(12.0)) .show(ui, |ui| { ui.set_width(ui.available_width()); egui::Grid::new("users_grid") .num_columns(2) .spacing([12.0, 10.0]) .show(ui, |ui| { // Gerçek ad ui.label(egui::RichText::new(t!("real_name_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { let prev = state.real_name.clone(); ui.text_edit_singleline(&mut state.real_name); if state.real_name != prev && !state.real_name.is_empty() { state.username = derive_username(&state.real_name); } if !state.real_name.is_empty() { ui.colored_label(theme::c_success(), "✓"); } }); ui.end_row(); // Kullanıcı adı (otomatik türetilir, düzenlenebilir) ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { ui.text_edit_singleline(&mut state.username); if !state.username.is_empty() { if is_valid_username(&state.username) { ui.colored_label(theme::c_success(), "✓"); } else { ui.colored_label(theme::c_error(), "✗"); } } }); ui.end_row(); // Şifre ui.label(egui::RichText::new(t!("password_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { if self.show_password { ui.text_edit_singleline(&mut state.password); } else { ui.add(egui::TextEdit::singleline(&mut state.password).password(true)); } if ui.small_button(if self.show_password { "🙈" } else { "👁" }).clicked() { self.show_password = !self.show_password; } if !state.password.is_empty() { let (label, color) = password_strength(&state.password); ui.colored_label(color, label); } }); ui.end_row(); // Şifre doğrulama ui.label(egui::RichText::new(t!("password_confirm_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { if self.show_password_confirm { ui.text_edit_singleline(&mut state.password_confirm); } else { ui.add(egui::TextEdit::singleline(&mut state.password_confirm).password(true)); } if ui.small_button(if self.show_password_confirm { "🙈" } else { "👁" }).clicked() { self.show_password_confirm = !self.show_password_confirm; } if !state.password_confirm.is_empty() { if state.password == state.password_confirm { ui.colored_label(theme::c_success(), "✓"); } else { ui.colored_label(theme::c_error(), t!("passwords_no_match")); } } }); ui.end_row(); // Hostname ui.label(egui::RichText::new(t!("hostname_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { ui.text_edit_singleline(&mut state.hostname); if !state.hostname.is_empty() { if is_valid_hostname(&state.hostname) { ui.colored_label(theme::c_success(), "✓"); } else { ui.colored_label(theme::c_error(), "✗"); } } }); ui.end_row(); }); }); ui.add_space(14.0); // ─── Yönetici (root) Ayarları ───────────────────────────────────── ui.checkbox(&mut self.use_same_for_admin, egui::RichText::new(t!("use_same_for_admin")).strong().color(theme::c_text())); if self.use_same_for_admin { state.root_password = state.password.clone(); state.root_password_confirm = state.password_confirm.clone(); } else { ui.add_space(8.0); egui::Frame::group(ui.style()) .fill(theme::c_bg_widget()) .rounding(6.0) .inner_margin(egui::Margin::same(12.0)) .show(ui, |ui| { ui.set_width(ui.available_width()); ui.label(egui::RichText::new(t!("admin_description")).size(12.0).color(theme::c_text_dim())); ui.add_space(8.0); egui::Grid::new("admin_grid") .num_columns(2) .spacing([12.0, 10.0]) .show(ui, |ui| { // Root şifresi ui.label(egui::RichText::new(t!("admin_password_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { if self.show_root_password { ui.text_edit_singleline(&mut state.root_password); } else { ui.add(egui::TextEdit::singleline(&mut state.root_password).password(true)); } if ui.small_button(if self.show_root_password { "🙈" } else { "👁" }).clicked() { self.show_root_password = !self.show_root_password; } if !state.root_password.is_empty() && state.root_password.len() < 4 { ui.colored_label(theme::c_error(), t!("admin_password_short")); } }); ui.end_row(); // Şifre doğrulama ui.label(egui::RichText::new(t!("admin_password_confirm_label")).strong().color(theme::c_text())); ui.horizontal(|ui| { if self.show_root_password_confirm { ui.text_edit_singleline(&mut state.root_password_confirm); } else { ui.add(egui::TextEdit::singleline(&mut state.root_password_confirm).password(true)); } if ui.small_button(if self.show_root_password_confirm { "🙈" } else { "👁" }).clicked() { self.show_root_password_confirm = !self.show_root_password_confirm; } if !state.root_password_confirm.is_empty() { if state.root_password == state.root_password_confirm { ui.colored_label(theme::c_success(), "✓"); } else { ui.colored_label(theme::c_error(), t!("passwords_no_match")); } } }); ui.end_row(); }); }); } // Hata mesajları gösterimi ui.add_space(8.0); if !state.username.is_empty() && !is_valid_username(&state.username) { ui.colored_label(theme::c_error(), t!("username_invalid")); } if !state.hostname.is_empty() && !is_valid_hostname(&state.hostname) { ui.colored_label(theme::c_error(), t!("hostname_invalid")); } } fn is_complete(&self, state: &GlobalState) -> bool { let user_ok = is_valid_username(&state.username) && is_valid_hostname(&state.hostname) && state.password.len() >= 4 && state.password == state.password_confirm; if self.use_same_for_admin { user_ok } else { user_ok && ( state.root_password.is_empty() || (state.root_password.len() >= 4 && state.root_password == state.root_password_confirm) ) } } }