mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-27 02:22:23 +02:00
refactor: ui cleanup
This commit is contained in:
@@ -268,7 +268,9 @@ impl GroupManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Create result including all groups (even those with 0 count)
|
||||
// Create result including all groups (even those with 0 count).
|
||||
// The "Default" pseudo-group is intentionally not returned: profiles
|
||||
// without a group_id are surfaced through the "All" filter instead.
|
||||
let mut result = Vec::new();
|
||||
for group in groups {
|
||||
let count = group_counts.get(&group.id).copied().unwrap_or(0);
|
||||
@@ -281,18 +283,6 @@ impl GroupManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Add default group count (profiles without group_id), always include even if 0
|
||||
let default_count = profiles.iter().filter(|p| p.group_id.is_none()).count();
|
||||
let default_group = GroupWithCount {
|
||||
id: "default".to_string(),
|
||||
name: "Default".to_string(),
|
||||
count: default_count,
|
||||
sync_enabled: false,
|
||||
last_sync: None,
|
||||
};
|
||||
// Insert at the beginning for consistent ordering with UI expectations
|
||||
result.insert(0, default_group);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ use profile::manager::{
|
||||
|
||||
use profile::password::{
|
||||
change_profile_password, is_profile_locked, lock_profile, remove_profile_password,
|
||||
set_profile_password, unlock_profile,
|
||||
set_profile_password, unlock_profile, verify_profile_password,
|
||||
};
|
||||
|
||||
use browser_version_manager::{
|
||||
@@ -103,6 +103,7 @@ use sync::{
|
||||
is_vpn_in_use_by_synced_profile, request_profile_sync, rollover_encryption_for_all_entities,
|
||||
set_e2e_password, set_extension_group_sync_enabled, set_extension_sync_enabled,
|
||||
set_group_sync_enabled, set_profile_sync_mode, set_proxy_sync_enabled, set_vpn_sync_enabled,
|
||||
verify_e2e_password,
|
||||
};
|
||||
|
||||
use tag_manager::get_all_tags;
|
||||
@@ -2112,6 +2113,7 @@ pub fn run() {
|
||||
enable_sync_for_all_entities,
|
||||
set_e2e_password,
|
||||
check_has_e2e_password,
|
||||
verify_e2e_password,
|
||||
delete_e2e_password,
|
||||
rollover_encryption_for_all_entities,
|
||||
read_profile_cookies,
|
||||
@@ -2177,6 +2179,7 @@ pub fn run() {
|
||||
set_profile_password,
|
||||
change_profile_password,
|
||||
remove_profile_password,
|
||||
verify_profile_password,
|
||||
unlock_profile,
|
||||
lock_profile,
|
||||
is_profile_locked,
|
||||
|
||||
@@ -473,6 +473,8 @@ impl ProfileManager {
|
||||
// Save profile with new name
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Keep tag suggestions up to date after name change (rebuild from all profiles)
|
||||
let _ = crate::tag_manager::TAG_MANAGER.lock().map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
@@ -678,6 +680,8 @@ impl ProfileManager {
|
||||
profile.group_id = group_id.clone();
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Auto-enable sync for new group if profile has sync enabled
|
||||
if profile.is_sync_enabled() {
|
||||
if let Some(ref new_group_id) = group_id {
|
||||
@@ -732,6 +736,8 @@ impl ProfileManager {
|
||||
// Save profile
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Update global tag suggestions from all profiles
|
||||
let _ = crate::tag_manager::TAG_MANAGER.lock().map(|tm| {
|
||||
let _ = tm.rebuild_from_profiles(&self.list_profiles().unwrap_or_default());
|
||||
@@ -766,6 +772,8 @@ impl ProfileManager {
|
||||
// Save profile
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Emit profile note update event
|
||||
if let Err(e) = events::emit_empty("profiles-changed") {
|
||||
log::warn!("Warning: Failed to emit profiles-changed event: {e}");
|
||||
@@ -792,6 +800,8 @@ impl ProfileManager {
|
||||
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
if let Err(e) = events::emit("profile-updated", &profile) {
|
||||
log::warn!("Warning: Failed to emit profile update event: {e}");
|
||||
}
|
||||
@@ -821,6 +831,8 @@ impl ProfileManager {
|
||||
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
if let Err(e) = events::emit_empty("profiles-changed") {
|
||||
log::warn!("Warning: Failed to emit profiles-changed event: {e}");
|
||||
}
|
||||
@@ -845,6 +857,8 @@ impl ProfileManager {
|
||||
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
if let Err(e) = events::emit_empty("profiles-changed") {
|
||||
log::warn!("Warning: Failed to emit profiles-changed event: {e}");
|
||||
}
|
||||
@@ -1060,6 +1074,8 @@ impl ProfileManager {
|
||||
format!("Failed to save profile: {e}").into()
|
||||
})?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
log::info!(
|
||||
"Camoufox configuration updated for profile '{}' (ID: {}).",
|
||||
profile.name,
|
||||
@@ -1120,6 +1136,8 @@ impl ProfileManager {
|
||||
format!("Failed to save profile: {e}").into()
|
||||
})?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
log::info!(
|
||||
"Wayfern configuration updated for profile '{}' (ID: {}).",
|
||||
profile.name,
|
||||
@@ -1174,6 +1192,8 @@ impl ProfileManager {
|
||||
format!("Failed to save profile: {e}").into()
|
||||
})?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Auto-enable sync for new proxy if profile has sync enabled
|
||||
if profile.is_sync_enabled() {
|
||||
if let Some(ref new_proxy_id) = proxy_id {
|
||||
@@ -1263,6 +1283,8 @@ impl ProfileManager {
|
||||
format!("Failed to save profile: {e}").into()
|
||||
})?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Auto-enable sync for the new VPN if profile has sync enabled.
|
||||
if profile.is_sync_enabled() {
|
||||
if let Some(ref new_vpn_id) = vpn_id {
|
||||
@@ -1300,6 +1322,8 @@ impl ProfileManager {
|
||||
profile.extension_group_id = extension_group_id.clone();
|
||||
self.save_profile(&profile)?;
|
||||
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
|
||||
// Auto-enable sync for the new extension group if profile has sync
|
||||
// enabled. The helper is sync internally; we fire-and-forget through
|
||||
// the async runtime so any I/O doesn't block this caller.
|
||||
|
||||
@@ -292,10 +292,45 @@ pub async fn set_profile_password(profile_id: String, password: String) -> Resul
|
||||
.map_err(err_internal)?;
|
||||
|
||||
cache_key(id, key);
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
emit_profiles_changed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify a profile password without unlocking. Used by the Settings UI's
|
||||
/// "Validate" button so users can confirm they remember the password without
|
||||
/// performing a destructive change. Honors the same lockout schedule as
|
||||
/// `unlock_profile` so a brute-force attacker can't bypass rate-limiting by
|
||||
/// hammering this command.
|
||||
#[tauri::command]
|
||||
pub async fn verify_profile_password(profile_id: String, password: String) -> Result<(), String> {
|
||||
let id = parse_uuid(&profile_id)?;
|
||||
let profile = load_profile(&id)?;
|
||||
if !profile.password_protected {
|
||||
return Err(err_code("PROFILE_NOT_PROTECTED"));
|
||||
}
|
||||
if let Err(secs) = check_lockout(&id) {
|
||||
return Err(err_with("LOCKED_OUT", &[("seconds", secs.to_string())]));
|
||||
}
|
||||
let salt = profile
|
||||
.encryption_salt
|
||||
.as_deref()
|
||||
.ok_or_else(|| err_code("PROFILE_MISSING_SALT"))?;
|
||||
let key = derive_profile_key(&password, salt).map_err(err_internal)?;
|
||||
let dir = profile_data_dir(&profile);
|
||||
match verify_key_against_dir(&key, &dir) {
|
||||
Ok(()) => {
|
||||
clear_failed_attempts(&id);
|
||||
Ok(())
|
||||
}
|
||||
Err(crate::profile::encryption::PasswordError::WrongPassword) => {
|
||||
record_failed_attempt(id);
|
||||
Err(err_code("INCORRECT_PASSWORD"))
|
||||
}
|
||||
Err(other) => Err(err_internal(other)),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unlock_profile(profile_id: String, password: String) -> Result<(), String> {
|
||||
let id = parse_uuid(&profile_id)?;
|
||||
@@ -396,6 +431,7 @@ pub async fn change_profile_password(
|
||||
|
||||
drop_cached_key(&id);
|
||||
cache_key(id, new_key);
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
emit_profiles_changed();
|
||||
Ok(())
|
||||
}
|
||||
@@ -464,6 +500,7 @@ pub async fn remove_profile_password(profile_id: String, password: String) -> Re
|
||||
.map_err(err_internal)?;
|
||||
|
||||
drop_cached_key(&id);
|
||||
crate::sync::queue_profile_sync_if_eligible(&profile);
|
||||
emit_profiles_changed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -346,6 +346,14 @@ pub fn check_has_e2e_password() -> bool {
|
||||
has_e2e_password()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn verify_e2e_password(password: String) -> Result<bool, String> {
|
||||
match load_e2e_password()? {
|
||||
Some(stored) => Ok(stored == password),
|
||||
None => Err(serde_json::json!({ "code": "NO_E2E_PASSWORD_SET" }).to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_e2e_password() -> Result<(), String> {
|
||||
enforce_team_owner_for_encryption_change().await?;
|
||||
|
||||
@@ -7,7 +7,9 @@ pub mod subscription;
|
||||
pub mod types;
|
||||
|
||||
pub use client::SyncClient;
|
||||
pub use encryption::{check_has_e2e_password, delete_e2e_password, set_e2e_password};
|
||||
pub use encryption::{
|
||||
check_has_e2e_password, delete_e2e_password, set_e2e_password, verify_e2e_password,
|
||||
};
|
||||
pub use engine::{
|
||||
enable_extension_group_sync_if_needed, enable_group_sync_if_needed, enable_proxy_sync_if_needed,
|
||||
enable_sync_for_all_entities, enable_vpn_sync_if_needed, get_unsynced_entity_counts,
|
||||
@@ -22,3 +24,21 @@ pub use manifest::{compute_diff, generate_manifest, HashCache, ManifestDiff, Syn
|
||||
pub use scheduler::{get_global_scheduler, set_global_scheduler, SyncScheduler};
|
||||
pub use subscription::{SubscriptionManager, SyncWorkItem};
|
||||
pub use types::{SyncError, SyncResult};
|
||||
|
||||
/// Queue a profile sync if the profile has sync enabled. No-op otherwise.
|
||||
///
|
||||
/// Called from profile metadata update paths so a rename / tag edit / proxy
|
||||
/// reassignment shows up on other devices without waiting for the next
|
||||
/// scheduled tick. Spawns the async queue call so this helper is callable
|
||||
/// from both sync and async contexts.
|
||||
pub fn queue_profile_sync_if_eligible(profile: &crate::profile::BrowserProfile) {
|
||||
if !profile.is_sync_enabled() {
|
||||
return;
|
||||
}
|
||||
let profile_id = profile.id.to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
if let Some(scheduler) = get_global_scheduler() {
|
||||
scheduler.queue_profile_sync(profile_id).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user