feat(ui): Phase 8 UI/UX optimization and system documentation update
## Sidebar Enhancement - Change tabs to icon + small label layout for better space utilization - Add Teams tab with team collaboration entry point ## Settings Page Improvements - Connect theme toggle to gatewayStore.saveQuickConfig for persistence - Remove OpenFang backend download section, simplify UI - Add time range filter to UsageStats (7d/30d/all) - Add stat cards with icons (sessions, messages, input/output tokens) - Add token usage overview bar chart - Add 8 ZCLAW system skill definitions with categories ## Bug Fixes - Fix ChannelList duplicate content with deduplication logic - Integrate CreateTriggerModal in TriggersPanel - Add independent SecurityStatusPanel with 12 default enabled layers - Change workflow view to use SchedulerPanel as unified entry ## New Components - CreateTriggerModal: Event trigger creation modal - HandApprovalModal: Hand approval workflow dialog - HandParamsForm: Enhanced Hand parameter form - SecurityLayersPanel: 16-layer security status display ## Architecture - Add TOML config parsing support (toml-utils.ts, config-parser.ts) - Add request timeout and retry mechanism (request-helper.ts) - Add secure token storage (secure-storage.ts, secure_storage.rs) ## Tests - Add unit tests for config-parser, toml-utils, request-helper - Add team-client and teamStore tests ## Documentation - Update SYSTEM_ANALYSIS.md with Phase 8 completion - UI completion: 100% (30/30 components) - API coverage: 93% (63/68 endpoints) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
99
desktop/src-tauri/src/secure_storage.rs
Normal file
99
desktop/src-tauri/src/secure_storage.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
// Secure storage module for ZCLAW desktop app
|
||||
// Uses the OS keyring/keychain for secure credential storage
|
||||
// - Windows: DPAPI
|
||||
// - macOS: Keychain
|
||||
// - Linux: Secret Service API (gnome-keyring, kwallet, etc.)
|
||||
|
||||
use keyring::Entry;
|
||||
|
||||
const SERVICE_NAME: &str = "zclaw";
|
||||
|
||||
/// Store a value securely in the OS keyring
|
||||
#[tauri::command]
|
||||
pub fn secure_store_set(key: String, value: String) -> Result<(), String> {
|
||||
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create keyring entry for '{}': {}",
|
||||
key,
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
entry.set_password(&value).map_err(|e| {
|
||||
format!(
|
||||
"Failed to store value for key '{}': {}",
|
||||
key,
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Retrieve a value from the OS keyring
|
||||
#[tauri::command]
|
||||
pub fn secure_store_get(key: String) -> Result<String, String> {
|
||||
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create keyring entry for '{}': {}",
|
||||
key,
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
entry.get_password().map_err(|e| {
|
||||
// Return empty string for "not found" errors to distinguish from actual errors
|
||||
let error_str = e.to_string();
|
||||
if error_str.contains("No matching entry") || error_str.contains("not found") {
|
||||
String::new()
|
||||
} else {
|
||||
format!("Failed to retrieve value for key '{}': {}", key, error_str)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete a value from the OS keyring
|
||||
#[tauri::command]
|
||||
pub fn secure_store_delete(key: String) -> Result<(), String> {
|
||||
let entry = Entry::new(SERVICE_NAME, &key).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create keyring entry for '{}': {}",
|
||||
key,
|
||||
e.to_string()
|
||||
)
|
||||
})?;
|
||||
|
||||
match entry.delete_credential() {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
let error_str = e.to_string();
|
||||
// Don't fail if the entry doesn't exist
|
||||
if error_str.contains("No matching entry") || error_str.contains("not found") {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Failed to delete value for key '{}': {}", key, error_str))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if secure storage is available on this platform
|
||||
#[tauri::command]
|
||||
pub fn secure_store_is_available() -> bool {
|
||||
// Try to create a test entry to verify keyring is working
|
||||
let test_key = "__zclaw_availability_test__";
|
||||
match Entry::new(SERVICE_NAME, test_key) {
|
||||
Ok(entry) => {
|
||||
// Try to set and delete a test value
|
||||
match entry.set_password("test") {
|
||||
Ok(_) => {
|
||||
// Clean up the test entry
|
||||
let _ = entry.delete_credential();
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user