claude-desktop/src/lib/components/AuditLog.svelte
Eddy f5ca5bca7c Phase 3: SQLite-Persistierung, Guard-Rails Integration + Claude Bridge
- db.rs: Vollständige SQLite-Schicht (Permissions, Audit, Memory, Patterns, Settings)
- guard.rs: Risiko-Klassifikation + Freigabe-Management in lib.rs integriert
- scripts/claude-bridge.js: Node.js Bridge für Claude CLI (stream-json, NDJSON)
- audit.rs + memory.rs: An SQLite angebunden statt In-Memory
- Frontend: MemoryPanel + AuditLog laden echte Daten via Tauri-Commands
- shell.nix: Rust-Toolchain aus nixpkgs statt rustup
- Build: cargo check + npm run build erfolgreich

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:28:35 +02:00

273 lines
5.7 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts">
import { onMount } from 'svelte';
import { invoke } from '@tauri-apps/api/core';
interface AuditEntry {
id: string;
timestamp: string;
category: string;
action: string;
item_name: string;
old_value?: unknown;
new_value?: unknown;
reason?: string;
auto_corrected: boolean;
}
let entries: AuditEntry[] = [];
let filter = 'all';
let loading = true;
// Kategorie-Icons
const categoryIcons: Record<string, string> = {
guard_rail: '🛡️',
guardrail: '🛡️',
pattern: '📋',
hook: '🪝',
skill: '🎯',
setting: '⚙️',
mcp: '🔌',
memory: '🧠'
};
// Aktions-Icons
const actionIcons: Record<string, string> = {
create: '',
update: '✏️',
delete: '🗑️',
enable: '✅',
disable: '❌'
};
async function loadEntries() {
try {
entries = await invoke('get_audit_log', { limit: 50 });
} catch (err) {
console.error('Fehler beim Laden des Audit-Logs:', err);
}
loading = false;
}
onMount(() => {
loadEntries();
});
function formatTime(timestamp: string): string {
const date = new Date(timestamp);
const now = new Date();
const diff = now.getTime() - date.getTime();
if (diff < 60000) return 'gerade eben';
if (diff < 3600000) return `vor ${Math.floor(diff / 60000)} Min`;
if (diff < 86400000) return `vor ${Math.floor(diff / 3600000)} Std`;
return date.toLocaleDateString('de-DE');
}
function filteredEntries(): AuditEntry[] {
if (filter === 'all') return entries;
if (filter === 'auto') return entries.filter((e) => e.auto_corrected);
return entries.filter((e) => e.category === filter);
}
</script>
<div class="audit-log">
<div class="panel-header">
<h2>📝 Änderungshistorie</h2>
<select bind:value={filter}>
<option value="all">Alle</option>
<option value="auto">Auto-korrigiert</option>
<option value="guard_rail">Guard-Rails</option>
<option value="pattern">Vorgehensweisen</option>
<option value="hook">Hooks</option>
<option value="skill">Skills</option>
<option value="setting">Einstellungen</option>
</select>
</div>
{#if loading}
<div class="loading-state">Lade...</div>
{:else if filteredEntries().length === 0}
<div class="empty-state">Keine Einträge</div>
{:else}
<div class="entries-list">
{#each filteredEntries() as entry}
<div class="entry" class:auto-corrected={entry.auto_corrected}>
<div class="entry-header">
<span class="entry-time">{formatTime(entry.timestamp)}</span>
<span class="entry-category">
{categoryIcons[entry.category] || '📦'}
</span>
<span class="entry-action">
{actionIcons[entry.action] || '•'}
</span>
<span class="entry-name">{entry.item_name}</span>
{#if entry.auto_corrected}
<span class="auto-badge">⚡ Auto</span>
{/if}
</div>
{#if entry.old_value && entry.new_value}
<div class="entry-change">
<span class="old-value">{entry.old_value}</span>
<span class="arrow"></span>
<span class="new-value">{entry.new_value}</span>
</div>
{/if}
{#if entry.reason}
<div class="entry-reason">
💬 {entry.reason}
</div>
{/if}
</div>
{/each}
</div>
{/if}
<div class="actions">
<button class="btn-export">📤 Export</button>
<button class="btn-all">Alle anzeigen</button>
</div>
</div>
<style>
.audit-log {
padding: var(--spacing-md);
height: 100%;
display: flex;
flex-direction: column;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.panel-header h2 {
font-size: 1rem;
font-weight: 600;
}
.panel-header select {
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--bg-tertiary);
font-size: 0.75rem;
}
.loading-state,
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
}
.entries-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.entry {
padding: var(--spacing-sm);
background: var(--bg-secondary);
border-radius: var(--radius-md);
border-left: 3px solid var(--bg-tertiary);
}
.entry.auto-corrected {
border-left-color: var(--warning);
}
.entry-header {
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-size: 0.875rem;
}
.entry-time {
color: var(--text-secondary);
font-size: 0.75rem;
min-width: 80px;
}
.entry-category,
.entry-action {
width: 20px;
text-align: center;
}
.entry-name {
flex: 1;
font-weight: 500;
}
.auto-badge {
font-size: 0.625rem;
padding: 2px 6px;
background: var(--warning);
color: var(--bg-primary);
border-radius: var(--radius-sm);
font-weight: 600;
}
.entry-change {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-top: var(--spacing-xs);
padding: var(--spacing-xs);
background: var(--bg-primary);
border-radius: var(--radius-sm);
font-family: var(--font-mono);
font-size: 0.75rem;
}
.old-value {
color: var(--error);
text-decoration: line-through;
}
.arrow {
color: var(--text-secondary);
}
.new-value {
color: var(--success);
}
.entry-reason {
margin-top: var(--spacing-xs);
font-size: 0.75rem;
color: var(--text-secondary);
}
.actions {
display: flex;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.actions button {
flex: 1;
padding: var(--spacing-sm);
border-radius: var(--radius-md);
font-size: 0.75rem;
background: var(--bg-secondary);
border: 1px solid var(--bg-tertiary);
transition: background 0.2s ease;
}
.actions button:hover {
background: var(--bg-tertiary);
}
</style>