Files
yali-rs/src/steps/users.rs
T

320 lines
14 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};
/// 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)
)
}
}
}