## 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>
135 lines
4.3 KiB
TypeScript
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>
|
|
);
|
|
}
|