- Neues SettingsPanel mit Modell-Auswahl (Haiku/Sonnet/Opus) - Modell wird in SQLite persistiert (claude_model Setting) - Bridge unterstützt set-model und get-models Commands - Modell kann zur Laufzeit gewechselt werden - Preisanzeige pro Modell im Settings-Panel - Aktuelles Modell wird beim App-Start geladen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
639 lines
22 KiB
Rust
639 lines
22 KiB
Rust
// Claude Desktop — SQLite Datenbankschicht
|
|
// Persistiert Guard-Rails, Audit-Log, Memory und Einstellungen
|
|
|
|
use rusqlite::{params, Connection, Result as SqlResult};
|
|
use std::path::Path;
|
|
use std::sync::{Arc, Mutex};
|
|
use tauri::{AppHandle, Manager};
|
|
|
|
use crate::audit::{AuditAction, AuditCategory, AuditEntry, AuditStats};
|
|
use crate::guard::{Permission, PermissionAction, PermissionType};
|
|
use crate::memory::{ContextCategory, MemoryEntry, Pattern};
|
|
|
|
/// Eine Claude-Session
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
pub struct Session {
|
|
pub id: String,
|
|
pub claude_session_id: Option<String>,
|
|
pub title: String,
|
|
pub working_dir: Option<String>,
|
|
pub message_count: i64,
|
|
pub token_input: i64,
|
|
pub token_output: i64,
|
|
pub cost_usd: f64,
|
|
pub status: String,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
pub last_message: Option<String>,
|
|
}
|
|
|
|
/// Datenbank-Wrapper
|
|
pub struct Database {
|
|
conn: Connection,
|
|
}
|
|
|
|
/// Datenbank-Statistiken
|
|
#[derive(Debug, serde::Serialize)]
|
|
pub struct DbStats {
|
|
pub permissions: usize,
|
|
pub audit_entries: usize,
|
|
pub memory_entries: usize,
|
|
pub patterns: usize,
|
|
pub db_size_kb: u64,
|
|
}
|
|
|
|
impl Database {
|
|
/// Öffnet oder erstellt die Datenbank
|
|
pub fn open(path: &Path) -> SqlResult<Self> {
|
|
let conn = Connection::open(path)?;
|
|
|
|
// WAL-Modus für bessere Performance
|
|
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
|
|
|
|
let db = Self { conn };
|
|
db.create_tables()?;
|
|
Ok(db)
|
|
}
|
|
|
|
/// Schema erstellen
|
|
fn create_tables(&self) -> SqlResult<()> {
|
|
self.conn.execute_batch(
|
|
"
|
|
-- Guard-Rails Permissions
|
|
CREATE TABLE IF NOT EXISTS permissions (
|
|
id TEXT PRIMARY KEY,
|
|
pattern TEXT NOT NULL,
|
|
tool TEXT,
|
|
path_pattern TEXT,
|
|
action TEXT NOT NULL DEFAULT 'allow',
|
|
created_at TEXT NOT NULL,
|
|
use_count INTEGER DEFAULT 0,
|
|
last_used TEXT
|
|
);
|
|
|
|
-- Audit-Log
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
id TEXT PRIMARY KEY,
|
|
timestamp TEXT NOT NULL,
|
|
category TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
item_id TEXT NOT NULL,
|
|
item_name TEXT NOT NULL,
|
|
old_value TEXT,
|
|
new_value TEXT,
|
|
reason TEXT,
|
|
auto_corrected INTEGER DEFAULT 0,
|
|
session_id TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_category ON audit_log(category);
|
|
|
|
-- Memory-Einträge
|
|
CREATE TABLE IF NOT EXISTS memory (
|
|
id TEXT PRIMARY KEY,
|
|
category TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value TEXT NOT NULL,
|
|
sticky INTEGER DEFAULT 0,
|
|
auto_load INTEGER DEFAULT 0,
|
|
last_used TEXT,
|
|
use_count INTEGER DEFAULT 0
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_memory_category ON memory(category);
|
|
CREATE INDEX IF NOT EXISTS idx_memory_sticky ON memory(sticky) WHERE sticky = 1;
|
|
|
|
-- Patterns (Vorgehensweisen)
|
|
CREATE TABLE IF NOT EXISTS patterns (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
trigger_text TEXT,
|
|
old_approach TEXT,
|
|
new_approach TEXT,
|
|
reason TEXT,
|
|
occurrence_count INTEGER DEFAULT 1,
|
|
auto_corrected INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
-- Sessions (Claude-Konversationen)
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
claude_session_id TEXT,
|
|
title TEXT NOT NULL,
|
|
working_dir TEXT,
|
|
message_count INTEGER DEFAULT 0,
|
|
token_input INTEGER DEFAULT 0,
|
|
token_output INTEGER DEFAULT 0,
|
|
cost_usd REAL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
last_message TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(updated_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
|
|
|
|
-- Einstellungen (Key-Value)
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
",
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
// ============ Permissions ============
|
|
|
|
/// Speichert eine Permission
|
|
pub fn save_permission(&self, perm: &Permission) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT OR REPLACE INTO permissions (id, pattern, tool, path_pattern, action, created_at, use_count, last_used)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
|
params![
|
|
perm.id,
|
|
perm.pattern,
|
|
perm.tool,
|
|
perm.path_pattern,
|
|
format!("{:?}", perm.action).to_lowercase(),
|
|
perm.created_at,
|
|
perm.use_count,
|
|
perm.last_used,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Lädt alle permanenten Permissions
|
|
pub fn load_permissions(&self) -> SqlResult<Vec<Permission>> {
|
|
let mut stmt = self.conn.prepare(
|
|
"SELECT id, pattern, tool, path_pattern, action, created_at, use_count, last_used FROM permissions"
|
|
)?;
|
|
|
|
let perms = stmt.query_map([], |row| {
|
|
let action_str: String = row.get(4)?;
|
|
let action = match action_str.as_str() {
|
|
"deny" => PermissionAction::Deny,
|
|
_ => PermissionAction::Allow,
|
|
};
|
|
|
|
Ok(Permission {
|
|
id: row.get(0)?,
|
|
pattern: row.get(1)?,
|
|
tool: row.get(2)?,
|
|
path_pattern: row.get(3)?,
|
|
permission_type: PermissionType::Permanent,
|
|
action,
|
|
created_at: row.get(5)?,
|
|
use_count: row.get(6)?,
|
|
last_used: row.get(7)?,
|
|
})
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
|
|
Ok(perms)
|
|
}
|
|
|
|
/// Löscht eine Permission
|
|
pub fn delete_permission(&self, id: &str) -> SqlResult<()> {
|
|
self.conn.execute("DELETE FROM permissions WHERE id = ?1", params![id])?;
|
|
Ok(())
|
|
}
|
|
|
|
// ============ Audit-Log ============
|
|
|
|
/// Speichert einen Audit-Eintrag
|
|
pub fn save_audit_entry(&self, entry: &AuditEntry) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT INTO audit_log (id, timestamp, category, action, item_id, item_name, old_value, new_value, reason, auto_corrected, session_id)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
|
params![
|
|
entry.id,
|
|
entry.timestamp,
|
|
format!("{:?}", entry.category).to_lowercase(),
|
|
format!("{:?}", entry.action).to_lowercase(),
|
|
entry.item_id,
|
|
entry.item_name,
|
|
entry.old_value.as_ref().map(|v| v.to_string()),
|
|
entry.new_value.as_ref().map(|v| v.to_string()),
|
|
entry.reason,
|
|
entry.auto_corrected as i32,
|
|
entry.session_id,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Lädt die letzten N Audit-Einträge
|
|
pub fn load_audit_log(&self, limit: usize) -> SqlResult<Vec<AuditEntry>> {
|
|
let mut stmt = self.conn.prepare(
|
|
"SELECT id, timestamp, category, action, item_id, item_name, old_value, new_value, reason, auto_corrected, session_id
|
|
FROM audit_log ORDER BY timestamp DESC LIMIT ?1"
|
|
)?;
|
|
|
|
let entries = stmt.query_map(params![limit as i64], |row| {
|
|
let cat_str: String = row.get(2)?;
|
|
let act_str: String = row.get(3)?;
|
|
let old_val: Option<String> = row.get(6)?;
|
|
let new_val: Option<String> = row.get(7)?;
|
|
let auto_corr: i32 = row.get(9)?;
|
|
|
|
Ok(AuditEntry {
|
|
id: row.get(0)?,
|
|
timestamp: row.get(1)?,
|
|
category: parse_audit_category(&cat_str),
|
|
action: parse_audit_action(&act_str),
|
|
item_id: row.get(4)?,
|
|
item_name: row.get(5)?,
|
|
old_value: old_val.and_then(|s| serde_json::from_str(&s).ok()),
|
|
new_value: new_val.and_then(|s| serde_json::from_str(&s).ok()),
|
|
reason: row.get(8)?,
|
|
auto_corrected: auto_corr != 0,
|
|
session_id: row.get(10)?,
|
|
})
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
/// Audit-Statistiken
|
|
pub fn audit_stats(&self) -> SqlResult<AuditStats> {
|
|
let total: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM audit_log", [], |row| row.get(0)
|
|
)?;
|
|
let auto_corrected: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM audit_log WHERE auto_corrected = 1", [], |row| row.get(0)
|
|
)?;
|
|
let today: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM audit_log WHERE timestamp LIKE ?1 || '%'",
|
|
params![chrono::Local::now().format("%Y-%m-%d").to_string()],
|
|
|row| row.get(0),
|
|
)?;
|
|
|
|
Ok(AuditStats { total, auto_corrected, today })
|
|
}
|
|
|
|
// ============ Memory ============
|
|
|
|
/// Speichert einen Memory-Eintrag
|
|
pub fn save_memory_entry(&self, entry: &MemoryEntry) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT OR REPLACE INTO memory (id, category, key, value, sticky, auto_load, last_used, use_count)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
|
params![
|
|
entry.id,
|
|
format!("{:?}", entry.category),
|
|
entry.key,
|
|
entry.value.to_string(),
|
|
entry.sticky as i32,
|
|
entry.auto_load as i32,
|
|
entry.last_used,
|
|
entry.use_count,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Lädt alle Memory-Einträge
|
|
pub fn load_memory_entries(&self) -> SqlResult<Vec<MemoryEntry>> {
|
|
let mut stmt = self.conn.prepare(
|
|
"SELECT id, category, key, value, sticky, auto_load, last_used, use_count FROM memory"
|
|
)?;
|
|
|
|
let entries = stmt.query_map([], |row| {
|
|
let cat_str: String = row.get(1)?;
|
|
let val_str: String = row.get(3)?;
|
|
let sticky: i32 = row.get(4)?;
|
|
let auto_load: i32 = row.get(5)?;
|
|
|
|
Ok(MemoryEntry {
|
|
id: row.get(0)?,
|
|
category: parse_context_category(&cat_str),
|
|
key: row.get(2)?,
|
|
value: serde_json::from_str(&val_str).unwrap_or(serde_json::Value::String(val_str)),
|
|
sticky: sticky != 0,
|
|
auto_load: auto_load != 0,
|
|
last_used: row.get(6)?,
|
|
use_count: row.get(7)?,
|
|
})
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
|
|
Ok(entries)
|
|
}
|
|
|
|
/// Löscht einen Memory-Eintrag
|
|
pub fn delete_memory_entry(&self, id: &str) -> SqlResult<()> {
|
|
self.conn.execute("DELETE FROM memory WHERE id = ?1", params![id])?;
|
|
Ok(())
|
|
}
|
|
|
|
// ============ Patterns ============
|
|
|
|
/// Speichert ein Pattern
|
|
pub fn save_pattern(&self, pattern: &Pattern) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT OR REPLACE INTO patterns (id, name, description, trigger_text, old_approach, new_approach, reason, occurrence_count, auto_corrected, created_at, updated_at)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
|
params![
|
|
pattern.id,
|
|
pattern.name,
|
|
pattern.description,
|
|
pattern.trigger,
|
|
pattern.old_approach,
|
|
pattern.new_approach,
|
|
pattern.reason,
|
|
pattern.occurrence_count,
|
|
pattern.auto_corrected as i32,
|
|
pattern.created_at,
|
|
pattern.updated_at,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Lädt alle Patterns
|
|
pub fn load_patterns(&self) -> SqlResult<Vec<Pattern>> {
|
|
let mut stmt = self.conn.prepare(
|
|
"SELECT id, name, description, trigger_text, old_approach, new_approach, reason, occurrence_count, auto_corrected, created_at, updated_at FROM patterns"
|
|
)?;
|
|
|
|
let patterns = stmt.query_map([], |row| {
|
|
let auto_corr: i32 = row.get(8)?;
|
|
Ok(Pattern {
|
|
id: row.get(0)?,
|
|
name: row.get(1)?,
|
|
description: row.get(2)?,
|
|
trigger: row.get(3)?,
|
|
old_approach: row.get(4)?,
|
|
new_approach: row.get(5)?,
|
|
reason: row.get(6)?,
|
|
occurrence_count: row.get(7)?,
|
|
auto_corrected: auto_corr != 0,
|
|
created_at: row.get(9)?,
|
|
updated_at: row.get(10)?,
|
|
})
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
|
|
Ok(patterns)
|
|
}
|
|
|
|
// ============ Sessions ============
|
|
|
|
/// Erstellt eine neue Session
|
|
pub fn create_session(&self, session: &Session) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT INTO sessions (id, claude_session_id, title, working_dir, message_count, token_input, token_output, cost_usd, status, created_at, updated_at, last_message)
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
|
params![
|
|
session.id,
|
|
session.claude_session_id,
|
|
session.title,
|
|
session.working_dir,
|
|
session.message_count,
|
|
session.token_input,
|
|
session.token_output,
|
|
session.cost_usd,
|
|
session.status,
|
|
session.created_at,
|
|
session.updated_at,
|
|
session.last_message,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Aktualisiert eine Session
|
|
pub fn update_session(&self, session: &Session) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"UPDATE sessions SET claude_session_id = ?2, title = ?3, message_count = ?4,
|
|
token_input = ?5, token_output = ?6, cost_usd = ?7, status = ?8,
|
|
updated_at = ?9, last_message = ?10
|
|
WHERE id = ?1",
|
|
params![
|
|
session.id,
|
|
session.claude_session_id,
|
|
session.title,
|
|
session.message_count,
|
|
session.token_input,
|
|
session.token_output,
|
|
session.cost_usd,
|
|
session.status,
|
|
chrono::Local::now().to_rfc3339(),
|
|
session.last_message,
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Lädt alle Sessions (neueste zuerst)
|
|
pub fn load_sessions(&self, limit: usize) -> SqlResult<Vec<Session>> {
|
|
let mut stmt = self.conn.prepare(
|
|
"SELECT id, claude_session_id, title, working_dir, message_count,
|
|
token_input, token_output, cost_usd, status, created_at, updated_at, last_message
|
|
FROM sessions ORDER BY updated_at DESC LIMIT ?1"
|
|
)?;
|
|
|
|
let sessions = stmt.query_map(params![limit as i64], |row| {
|
|
Ok(Session {
|
|
id: row.get(0)?,
|
|
claude_session_id: row.get(1)?,
|
|
title: row.get(2)?,
|
|
working_dir: row.get(3)?,
|
|
message_count: row.get(4)?,
|
|
token_input: row.get(5)?,
|
|
token_output: row.get(6)?,
|
|
cost_usd: row.get(7)?,
|
|
status: row.get(8)?,
|
|
created_at: row.get(9)?,
|
|
updated_at: row.get(10)?,
|
|
last_message: row.get(11)?,
|
|
})
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
|
|
Ok(sessions)
|
|
}
|
|
|
|
/// Holt eine Session nach ID
|
|
pub fn get_session(&self, id: &str) -> SqlResult<Option<Session>> {
|
|
let result = self.conn.query_row(
|
|
"SELECT id, claude_session_id, title, working_dir, message_count,
|
|
token_input, token_output, cost_usd, status, created_at, updated_at, last_message
|
|
FROM sessions WHERE id = ?1",
|
|
params![id],
|
|
|row| {
|
|
Ok(Session {
|
|
id: row.get(0)?,
|
|
claude_session_id: row.get(1)?,
|
|
title: row.get(2)?,
|
|
working_dir: row.get(3)?,
|
|
message_count: row.get(4)?,
|
|
token_input: row.get(5)?,
|
|
token_output: row.get(6)?,
|
|
cost_usd: row.get(7)?,
|
|
status: row.get(8)?,
|
|
created_at: row.get(9)?,
|
|
updated_at: row.get(10)?,
|
|
last_message: row.get(11)?,
|
|
})
|
|
},
|
|
);
|
|
match result {
|
|
Ok(s) => Ok(Some(s)),
|
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
/// Löscht eine Session
|
|
pub fn delete_session(&self, id: &str) -> SqlResult<()> {
|
|
self.conn.execute("DELETE FROM sessions WHERE id = ?1", params![id])?;
|
|
Ok(())
|
|
}
|
|
|
|
// ============ Settings ============
|
|
|
|
/// Speichert eine Einstellung
|
|
pub fn set_setting(&self, key: &str, value: &str) -> SqlResult<()> {
|
|
self.conn.execute(
|
|
"INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?1, ?2, ?3)",
|
|
params![key, value, chrono::Local::now().to_rfc3339()],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Liest eine Einstellung
|
|
pub fn get_setting(&self, key: &str) -> SqlResult<Option<String>> {
|
|
let result = self.conn.query_row(
|
|
"SELECT value FROM settings WHERE key = ?1",
|
|
params![key],
|
|
|row| row.get(0),
|
|
);
|
|
match result {
|
|
Ok(val) => Ok(Some(val)),
|
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
/// Lädt alle Einstellungen
|
|
pub fn get_all_settings(&self) -> SqlResult<Vec<(String, String)>> {
|
|
let mut stmt = self.conn.prepare("SELECT key, value FROM settings")?;
|
|
let settings = stmt.query_map([], |row| {
|
|
Ok((row.get(0)?, row.get(1)?))
|
|
})?.collect::<SqlResult<Vec<_>>>()?;
|
|
Ok(settings)
|
|
}
|
|
|
|
// ============ Statistiken ============
|
|
|
|
/// DB-Statistiken
|
|
pub fn stats(&self) -> SqlResult<DbStats> {
|
|
let permissions: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM permissions", [], |row| row.get(0)
|
|
)?;
|
|
let audit_entries: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM audit_log", [], |row| row.get(0)
|
|
)?;
|
|
let memory_entries: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM memory", [], |row| row.get(0)
|
|
)?;
|
|
let patterns: usize = self.conn.query_row(
|
|
"SELECT COUNT(*) FROM patterns", [], |row| row.get(0)
|
|
)?;
|
|
|
|
// DB-Größe ermitteln
|
|
let page_count: u64 = self.conn.query_row(
|
|
"PRAGMA page_count", [], |row| row.get(0)
|
|
)?;
|
|
let page_size: u64 = self.conn.query_row(
|
|
"PRAGMA page_size", [], |row| row.get(0)
|
|
)?;
|
|
let db_size_kb = (page_count * page_size) / 1024;
|
|
|
|
Ok(DbStats { permissions, audit_entries, memory_entries, patterns, db_size_kb })
|
|
}
|
|
}
|
|
|
|
// ============ Hilfsfunktionen ============
|
|
|
|
fn parse_audit_category(s: &str) -> AuditCategory {
|
|
match s {
|
|
"guardrail" | "guard_rail" => AuditCategory::GuardRail,
|
|
"pattern" => AuditCategory::Pattern,
|
|
"hook" => AuditCategory::Hook,
|
|
"skill" => AuditCategory::Skill,
|
|
"setting" => AuditCategory::Setting,
|
|
"mcp" => AuditCategory::MCP,
|
|
"memory" => AuditCategory::Memory,
|
|
_ => AuditCategory::Setting,
|
|
}
|
|
}
|
|
|
|
fn parse_audit_action(s: &str) -> AuditAction {
|
|
match s {
|
|
"create" => AuditAction::Create,
|
|
"update" => AuditAction::Update,
|
|
"delete" => AuditAction::Delete,
|
|
"enable" => AuditAction::Enable,
|
|
"disable" => AuditAction::Disable,
|
|
_ => AuditAction::Update,
|
|
}
|
|
}
|
|
|
|
fn parse_context_category(s: &str) -> ContextCategory {
|
|
match s {
|
|
"Critical" => ContextCategory::Critical,
|
|
"Pattern" => ContextCategory::Pattern,
|
|
"Preference" => ContextCategory::Preference,
|
|
"GuardRail" => ContextCategory::GuardRail,
|
|
"Hook" => ContextCategory::Hook,
|
|
"Skill" => ContextCategory::Skill,
|
|
_ => ContextCategory::Pattern,
|
|
}
|
|
}
|
|
|
|
// ============ Tauri Commands ============
|
|
|
|
pub type DbState = Arc<Mutex<Database>>;
|
|
|
|
/// DB initialisieren (falls Frontend es auslösen will)
|
|
#[tauri::command]
|
|
pub async fn init_database(app: AppHandle) -> Result<DbStats, String> {
|
|
let state = app.state::<DbState>();
|
|
let db = state.lock().unwrap();
|
|
db.stats().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// DB-Statistiken abrufen
|
|
#[tauri::command]
|
|
pub async fn get_db_stats(app: AppHandle) -> Result<DbStats, String> {
|
|
let state = app.state::<DbState>();
|
|
let db = state.lock().unwrap();
|
|
db.stats().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Einstellung lesen
|
|
#[tauri::command]
|
|
pub async fn get_setting(app: AppHandle, key: String) -> Result<Option<String>, String> {
|
|
let state = app.state::<DbState>();
|
|
let db = state.lock().unwrap();
|
|
db.get_setting(&key).map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Einstellung speichern
|
|
#[tauri::command]
|
|
pub async fn set_setting(app: AppHandle, key: String, value: String) -> Result<(), String> {
|
|
let state = app.state::<DbState>();
|
|
let db = state.lock().unwrap();
|
|
db.set_setting(&key, &value).map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// Alle Einstellungen laden
|
|
#[tauri::command]
|
|
pub async fn get_all_settings(app: AppHandle) -> Result<Vec<(String, String)>, String> {
|
|
let state = app.state::<DbState>();
|
|
let db = state.lock().unwrap();
|
|
db.get_all_settings().map_err(|e| e.to_string())
|
|
}
|