feat: initial TrustOS platform scaffold
- FastAPI backend: auth, findings, dashboard, attack paths, footprint, AI translator, risk calculator, PDF report generator - Next.js frontend: Vault dashboard, login, findings table, finding detail with AI coach, digital footprint, reports - PostgreSQL data model: tenants, users, assets, findings, risk scores, audit reports, attack paths - Docker Compose + Dockerfiles for all services - Demo seed data: Acme Corp with 6 findings and 90-day risk score history - AI Risk Translator (OpenAI/Anthropic) with plain-English business impact - Role-based access: executive / it_admin / trustos_admin - Scope-lock engine: authorization required before any assessment Stage 1-8 complete: Phase 1 Vault Audit product ready
This commit is contained in:
104
frontend/src/app/reports/page.tsx
Normal file
104
frontend/src/app/reports/page.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { api } from "@/lib/api";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import { FileText, Download, CheckCircle } from "lucide-react";
|
||||
|
||||
interface Report {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
title: string;
|
||||
report_date: string;
|
||||
baseline_score: number | null;
|
||||
executive_summary: string | null;
|
||||
pdf_path: string | null;
|
||||
is_baseline: boolean;
|
||||
generated_by: string | null;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export default function ReportsPage() {
|
||||
const { tenantId, role, ready } = useAuth();
|
||||
const [reports, setReports] = useState<Report[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready || !tenantId || role !== "trustos_admin") {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
// Only admins see this — public endpoint for tenants to view their own would come later
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/api/v1/audit-reports?tenant_id=${tenantId}`, {
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem("trustos_token")}` }
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(setReports)
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, [ready, tenantId, role]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen bg-vault-black">
|
||||
<Sidebar />
|
||||
<main className="ml-64 flex-1 p-8">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl font-bold text-vault-text flex items-center gap-2">
|
||||
<FileText className="w-6 h-6 text-vault-sapphire" />
|
||||
Vault Audit Reports
|
||||
</h1>
|
||||
<p className="text-vault-muted text-sm mt-1">Point-in-time baseline reports and audit deliverables</p>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center h-40">
|
||||
<div className="animate-spin w-6 h-6 border-2 border-vault-sapphire border-t-transparent rounded-full" />
|
||||
</div>
|
||||
) : role !== "trustos_admin" ? (
|
||||
<div className="vault-card">
|
||||
<p className="text-vault-muted text-sm">Audit reports are managed by your TrustOS administrator.</p>
|
||||
</div>
|
||||
) : reports.length === 0 ? (
|
||||
<div className="vault-card text-center py-12">
|
||||
<FileText className="w-8 h-8 text-vault-muted mx-auto mb-3" />
|
||||
<p className="text-vault-text font-semibold">No audit reports yet</p>
|
||||
<p className="text-vault-muted text-sm mt-1">Generate the first Vault Audit from the admin panel.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{reports.map(r => (
|
||||
<div key={r.id} className="vault-card">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{r.is_baseline && (
|
||||
<span className="vault-badge-info text-xs">Baseline</span>
|
||||
)}
|
||||
<p className="text-vault-text font-semibold">{r.title}</p>
|
||||
</div>
|
||||
<p className="text-vault-muted text-xs">
|
||||
{new Date(r.report_date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })}
|
||||
{r.baseline_score && ` · Score at audit: ${Math.round(r.baseline_score)}`}
|
||||
</p>
|
||||
{r.executive_summary && (
|
||||
<p className="text-vault-subtle text-sm mt-2 leading-relaxed line-clamp-2">{r.executive_summary}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{r.pdf_path ? (
|
||||
<span className="flex items-center gap-1.5 text-emerald-400 text-xs">
|
||||
<CheckCircle className="w-3.5 h-3.5" /> PDF ready
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-vault-muted text-xs">PDF generating…</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user