Files
yali-rs/src/steps/users.rs
T
Erkan IŞIK 8bb7116194 feat: form input styling, root password toggle, partition UI fixes
- form_input_text: Frame wrapper with stroke/fill/rounding, .frame(false)
- form_input_password: SVG eye icons instead of emoji, Frame wrapper
- Partition disk grid: hardcoded colors, left-aligned headers
- Users: root password show/hide toggle, colored form frames
- Add c_root_bg() theme function
2026-06-19 02:56:42 +03:00

317 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use eframe::egui;
use rust_i18n::t;
use crate::installer::{GlobalState, InstallerStep};
use crate::funct::{ form_input_text, form_input_password, form_label, form_checkbox };
/// Kullanıcı adı, şifre, hostname ve yönetici (root) şifresi ayarları adımı.
pub struct UsersStep {
use_same_for_admin: bool,
show_password: bool,
show_password_confirm: bool,
show_root_password: bool, // <-- Eklendi
show_root_password_confirm: bool, // <-- Eklendi
}
impl Default for UsersStep {
fn default() -> Self {
Self {
use_same_for_admin: true,
show_password: false,
show_password_confirm: false,
show_root_password: false,
show_root_password_confirm: false,
}
}
}
/// 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 == '-')
}
/// Otomatik hostname üretirken nokta, boşluk ve geçersiz karakterleri kaldırır.
fn sanitize_hostname(s: &str) -> String {
let mut hostname: String = s
.chars()
.filter(|c| c.is_ascii_alphanumeric())
.collect();
if hostname.len() > 63 {
hostname.truncate(63);
}
if hostname.is_empty() {
"host".to_string()
} else {
hostname
}
}
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();
let sys_hostname = sanitize_hostname(&sys_hostname);
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 = sanitize_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 Formu ──────────────────────────────────
egui::Frame::group(ui.style())
.fill(egui::Color32::from_rgb(240, 243, 198))
.rounding(6.0)
.inner_margin(egui::Margin::symmetric(10.0, 10.0))
.show(ui, |ui| {
ui.set_width(ui.available_width());
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.add_space(10.0);
// Gerçek ad
form_label(ui, &t!("real_name_label"));
{
let prev = state.real_name.clone();
form_input_text(ui, &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(), "");
} else {
ui.colored_label(theme::c_error(), "");
}
ui.add_space(6.0);
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
form_label(ui, &t!("username_label"));
form_input_text(ui, &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_warning(), t!("username_invalid"));
}
}
ui.add_space(6.0);
// Şifre
form_label(ui, &t!("password_label"));
// OEM modunda varsayılan şifre uyarısı göster
if state.oem_mode && !state.oem_password_changed {
ui.colored_label(
theme::c_error(),
t!("oem_change_password_warning"),
);
}
{
let show_pwd = &mut self.show_password;
let before = state.password.clone();
form_input_password(ui, &mut state.password, show_pwd);
// Kullanıcı şifreyi değiştirdiyse OEM bayrağını kaldır
if state.oem_mode && state.password != before {
state.oem_password_changed = true;
}
}
if !state.password.is_empty() {
let (label, color) = password_strength(&state.password);
ui.colored_label(color, label);
}
ui.add_space(6.0);
// Şifre doğrulama
form_label(ui, &t!("password_confirm_label"));
{
let show_pwd = &mut self.show_password_confirm;
form_input_password(ui, &mut state.password_confirm, show_pwd);
}
if !state.password_confirm.is_empty() {
if state.password == state.password_confirm {
ui.colored_label(theme::c_success(), "");
} else {
ui.colored_label(theme::c_warning(), t!("passwords_no_match"));
}
}
ui.add_space(6.0);
// Hostname
form_label(ui, &t!("hostname_label"));
form_input_text(ui, &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_warning(), t!("hostname_invalid"));
}
}
});
ui.add_space(10.0);
});
});
ui.add_space(14.0);
// ─── Yönetici (root) Ayarları ─────────────────────────────────────
form_checkbox(ui, &mut self.use_same_for_admin, &t!("use_same_for_admin"));
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_root_bg())
.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);
// Root şifresi
ui.label(egui::RichText::new(t!("admin_password_label")).strong().color(theme::c_text()));
// DÜZELTME: 3. parametre olarak show_root_password eklendi
let show_root_pwd = &mut self.show_root_password;
form_input_password(ui, &mut state.root_password, show_root_pwd);
ui.add_space(8.0);
// Şifre doğrulama
ui.label(egui::RichText::new(t!("admin_password_confirm_label")).strong().color(theme::c_text()));
// DÜZELTME: 3. parametre olarak show_root_password_confirm eklendi
let show_root_pwd_conf = &mut self.show_root_password_confirm;
form_input_password(ui, &mut state.root_password_confirm, show_root_pwd_conf);
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"));
}
});
}
}
fn is_complete(&self, state: &GlobalState) -> bool {
// OEM modunda varsayılan şifre değiştirilmeden ilerlenmez.
if state.oem_mode && !state.oem_password_changed {
return false;
}
let user_ok = is_valid_username(&state.username)
&& is_valid_hostname(&state.hostname)
&& state.password.len() >= 8
&& 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() >= 8 && state.root_password == state.root_password_confirm)
)
}
}
}