mirror of
https://gitlab.com/erkanisik/yali-rs.git
synced 2026-07-31 03:09:00 +00:00
320 lines
14 KiB
Rust
320 lines
14 KiB
Rust
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)
|
||
)
|
||
}
|
||
}
|
||
}
|