Voice (Phase 10): - voice.rs: OpenAI Whisper (STT) + TTS Backend - ChatPanel: Mikrofon-Button, VAD (Pause 1.5s), Live-Pegel - SettingsPanel: OpenAI-Key Konfiguration Phase 9 Nacharbeiten: - Auto-Extract vor Compacting (Entscheidungen/TODOs/Insights) - get_tool_hints() - relevante KB-Eintraege bei Tool-Start - activeKnowledgeHints Store, Anzeige im KnowledgePanel Tech-Schulden: - Dead-Code in memory.rs entfernt (MemorySystem struct) - cargo-check Warnings behoben Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
5.3 KiB
Rust
183 lines
5.3 KiB
Rust
// 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<serde_json::Value>,
|
|
pub new_value: Option<serde_json::Value>,
|
|
pub reason: Option<String>,
|
|
pub auto_corrected: bool,
|
|
pub session_id: Option<String>,
|
|
}
|
|
|
|
/// Audit-Log Manager (für zukünftige In-Memory-Nutzung)
|
|
#[allow(dead_code)]
|
|
#[derive(Debug, Default)]
|
|
pub struct AuditLog {
|
|
entries: Vec<AuditEntry>,
|
|
}
|
|
|
|
#[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<usize>) -> Result<Vec<AuditEntry>, String> {
|
|
let limit = limit.unwrap_or(50);
|
|
let state = app.state::<Arc<Mutex<db::Database>>>();
|
|
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<serde_json::Value>,
|
|
new_value: Option<serde_json::Value>,
|
|
reason: Option<String>,
|
|
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::<Arc<Mutex<db::Database>>>();
|
|
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<AuditStats, String> {
|
|
let state = app.state::<Arc<Mutex<db::Database>>>();
|
|
let db_lock = state.lock().unwrap();
|
|
db_lock.audit_stats().map_err(|e| e.to_string())
|
|
}
|