// Claude Desktop — Änderungs-Log (Audit Trail) // Protokolliert alle Änderungen an Einstellungen, Guard-Rails, Hooks, Skills, etc. use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use tauri::{AppHandle, Manager}; use crate::db; /// Kategorie der Änderung #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AuditCategory { GuardRail, // Freigabe-Regeln Pattern, // Vorgehensweisen Hook, // Claude Hooks Skill, // Claude Skills Setting, // App-Einstellungen MCP, // MCP-Server Konfiguration Memory, // Gedächtnis-Einträge } /// Art der Aktion #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AuditAction { Create, Update, Delete, Enable, Disable, } /// Ein Audit-Eintrag #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuditEntry { pub id: String, pub timestamp: String, pub category: AuditCategory, pub action: AuditAction, pub item_id: String, pub item_name: String, pub old_value: Option, pub new_value: Option, pub reason: Option, pub auto_corrected: bool, pub session_id: Option, } /// Audit-Log Manager (für zukünftige In-Memory-Nutzung) #[allow(dead_code)] #[derive(Debug, Default)] pub struct AuditLog { entries: Vec, } #[allow(dead_code)] impl AuditLog { pub fn new() -> Self { Self { entries: vec![] } } /// Fügt einen Eintrag hinzu pub fn log(&mut self, entry: AuditEntry) { println!( "📋 Audit: {:?} {:?} - {} ({})", entry.action, entry.category, entry.item_name, entry.reason.as_deref().unwrap_or("keine Begründung") ); self.entries.push(entry); } /// Holt die letzten N Einträge pub fn recent(&self, limit: usize) -> Vec<&AuditEntry> { self.entries.iter().rev().take(limit).collect() } /// Holt Einträge nach Kategorie pub fn by_category(&self, category: &AuditCategory) -> Vec<&AuditEntry> { self.entries .iter() .filter(|e| std::mem::discriminant(&e.category) == std::mem::discriminant(category)) .collect() } /// Holt auto-korrigierte Einträge pub fn auto_corrected(&self) -> Vec<&AuditEntry> { self.entries.iter().filter(|e| e.auto_corrected).collect() } /// Statistiken pub fn stats(&self) -> AuditStats { AuditStats { total: self.entries.len(), auto_corrected: self.entries.iter().filter(|e| e.auto_corrected).count(), today: self.entries.iter().filter(|e| { // Vereinfachte Prüfung - in echt: Datum vergleichen e.timestamp.starts_with(&chrono::Local::now().format("%Y-%m-%d").to_string()) }).count(), } } } #[derive(Debug, Serialize, Deserialize)] pub struct AuditStats { pub total: usize, pub auto_corrected: usize, pub today: usize, } // Tauri-Commands /// Holt die letzten Audit-Einträge #[tauri::command] pub async fn get_audit_log(app: AppHandle, limit: Option) -> Result, String> { let limit = limit.unwrap_or(50); let state = app.state::>>(); let db_lock = state.lock().unwrap(); db_lock.load_audit_log(limit).map_err(|e| e.to_string()) } /// Fügt einen Audit-Eintrag hinzu #[tauri::command] pub async fn add_audit_entry( app: AppHandle, category: String, action: String, item_id: String, item_name: String, old_value: Option, new_value: Option, reason: Option, auto_corrected: bool, ) -> Result<(), String> { let category = match category.as_str() { "guard_rail" => AuditCategory::GuardRail, "pattern" => AuditCategory::Pattern, "hook" => AuditCategory::Hook, "skill" => AuditCategory::Skill, "setting" => AuditCategory::Setting, "mcp" => AuditCategory::MCP, "memory" => AuditCategory::Memory, _ => return Err(format!("Unbekannte Kategorie: {}", category)), }; let action = match action.as_str() { "create" => AuditAction::Create, "update" => AuditAction::Update, "delete" => AuditAction::Delete, "enable" => AuditAction::Enable, "disable" => AuditAction::Disable, _ => return Err(format!("Unbekannte Aktion: {}", action)), }; let entry = AuditEntry { id: uuid::Uuid::new_v4().to_string(), timestamp: chrono::Local::now().to_rfc3339(), category, action, item_id, item_name, old_value, new_value, reason, auto_corrected, session_id: None, }; // In SQLite speichern let state = app.state::>>(); let db_lock = state.lock().unwrap(); db_lock.save_audit_entry(&entry).map_err(|e| e.to_string())?; println!("📋 Audit: {:?} {:?} - {}", entry.action, entry.category, entry.item_name); Ok(()) } /// Holt Audit-Statistiken #[tauri::command] pub async fn get_audit_stats(app: AppHandle) -> Result { let state = app.state::>>(); let db_lock = state.lock().unwrap(); db_lock.audit_stats().map_err(|e| e.to_string()) }