stemedb/applications/stemedb-dashboard/src/components/audit/audit-panel.tsx
jordan 157dbbb9eb feat: Complete Aphoria Phase 8-9 + UAT suite (90/90 tests passing)
## Phase 8: Enterprise Extractor Improvements 
- 14 security extractors (TLS, JWT, SQL injection, XSS, etc.)
- 10 framework-specific extractors (Spring, Django, Rails, etc.)
- Config file security detection (YAML, TOML)

## Phase 9: Autonomous Extractor Generation 
- Shadow mode executor with TP/FP tracking
- Graduation pipeline with confidence thresholds
- Auto-rollback on regression detection
- Cross-project pattern syncing

## UAT Suite Complete (14 scripts, 90 tests)
- test-core-detection.sh (6 tests)
- test-declarative-extractors.sh (5 tests)
- test-domain-frameworks.sh (5 tests)
- test-domain-unreal.sh (3 tests)
- test-llm-extraction.sh (6 tests)
- test-eval-harness.sh (5 tests)
- test-cross-language.sh (3 tests)
- test-precommit-performance.sh (4 tests)
- test-output-formats.sh (8 tests)
- test-drift-detection.sh (6 tests)
- test-exit-codes.sh (12 tests)
+ 3 more scripts

## Other Changes
- Updated roadmap to mark Phase 8-9 complete
- Added .gitignore entries for build artifacts
- Updated pre-commit: 800 line limit, exclude tests/data/cmd

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 22:50:55 -07:00

135 lines
4.3 KiB
TypeScript

"use client";
import { useState, useCallback, useEffect } from "react";
import { StemeDBClient, type AuditResponse, ApiError } from "@/lib/api";
import type { PanelState } from "@/lib/types";
import { AUDIT_FETCH_LIMIT, TIME_RANGES_MS, type TimeRangeKey } from "@/lib/constants";
import { ErrorState } from "@/components/shared/error-state";
import { AuditList } from "./audit-list";
import { AuditLoadingSkeleton } from "./audit-loading-skeleton";
import { AuditEmptyState } from "./audit-empty-state";
import type { AuditFilterValues } from "./audit-filters";
interface AuditPanelProps {
initialFilters?: Partial<AuditFilterValues>;
}
export function AuditPanel({ initialFilters }: AuditPanelProps) {
const [state, setState] = useState<PanelState<AuditResponse>>({ status: "idle" });
const [filters, setFilters] = useState<AuditFilterValues>({
subject: initialFilters?.subject ?? "",
predicate: initialFilters?.predicate ?? "",
agentId: initialFilters?.agentId ?? "",
action: initialFilters?.action ?? "",
timeRange: initialFilters?.timeRange ?? "24h",
});
const fetchData = useCallback(async (currentFilters: AuditFilterValues) => {
setState({ status: "loading" });
try {
const client = new StemeDBClient();
// Convert time range to from/to timestamps
let fromTs: number | undefined;
let toTs: number | undefined;
if (currentFilters.timeRange !== "all") {
const now = Date.now();
const rangeMs = TIME_RANGES_MS[currentFilters.timeRange as TimeRangeKey] ?? TIME_RANGES_MS["24h"];
fromTs = now - rangeMs;
toTs = now;
}
const data = await client.auditQueries({
limit: AUDIT_FETCH_LIMIT,
agentId: currentFilters.agentId || undefined,
subject: currentFilters.subject || undefined,
predicate: currentFilters.predicate || undefined,
from: fromTs,
to: toTs,
});
setState({ status: "success", data });
} catch (err) {
// 404 means no audit entries - treat as empty success
if (err instanceof ApiError && err.status === 404) {
setState({
status: "success",
data: { audits: [], total_count: 0 },
});
return;
}
const message =
err instanceof ApiError
? err.userMessage
: err instanceof Error
? err.message
: "Unknown error";
setState({ status: "error", error: message });
}
}, []);
// Load data on mount
useEffect(() => {
fetchData(filters);
}, [fetchData, filters]);
const handleFilterChange = useCallback((newFilters: AuditFilterValues) => {
setFilters(newFilters);
// fetchData will be called by the useEffect above
}, []);
const handleRetry = useCallback(() => {
fetchData(filters);
}, [fetchData, filters]);
const hasFilters =
filters.subject !== "" ||
filters.predicate !== "" ||
filters.agentId !== "" ||
filters.action !== "" ||
filters.timeRange !== "24h";
return (
<div className="space-y-6">
{/* Header */}
<div className="rounded-lg border border-border bg-card p-6">
<h2 className="text-lg font-medium text-card-foreground mb-2">
Query & Action History
</h2>
<p className="text-sm text-muted-foreground">
Browse the complete audit trail of queries and administrative actions.
Filter by subject, predicate, agent, or time range. Export data as JSON or CSV.
</p>
</div>
{/* Content */}
<div className="rounded-lg border border-border bg-card p-6">
{state.status === "idle" && <AuditLoadingSkeleton />}
{state.status === "loading" && <AuditLoadingSkeleton />}
{state.status === "error" && (
<ErrorState
title="Failed to Load Audit Trail"
error={state.error}
onRetry={handleRetry}
/>
)}
{state.status === "success" && (
<>
{state.data.audits.length === 0 && !hasFilters ? (
<AuditEmptyState hasFilters={false} />
) : (
<AuditList
entries={state.data.audits}
totalCount={state.data.total_count}
filters={filters}
onFilterChange={handleFilterChange}
/>
)}
</>
)}
</div>
</div>
);
}