- 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>
273 lines
5.7 KiB
Svelte
273 lines
5.7 KiB
Svelte
<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>
|