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:
70
frontend/src/components/ScoreTrend.tsx
Normal file
70
frontend/src/components/ScoreTrend.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
import {
|
||||
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip,
|
||||
ResponsiveContainer, ReferenceLine
|
||||
} from "recharts";
|
||||
|
||||
interface ScoreTrendProps {
|
||||
data: { date: string; score: number }[];
|
||||
}
|
||||
|
||||
const CustomTooltip = ({ active, payload, label }: any) => {
|
||||
if (active && payload?.length) {
|
||||
return (
|
||||
<div className="bg-vault-dark border border-vault-border rounded-lg px-3 py-2 shadow-xl">
|
||||
<p className="text-vault-muted text-xs mb-1">{label}</p>
|
||||
<p className="text-vault-sapphireLight font-bold text-sm">Score: {payload[0].value}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default function ScoreTrend({ data }: ScoreTrendProps) {
|
||||
// Thin the data to ~30 points for readability
|
||||
const step = Math.ceil(data.length / 30);
|
||||
const thinned = data.filter((_, i) => i % step === 0 || i === data.length - 1);
|
||||
|
||||
// Format dates to short labels
|
||||
const formatted = thinned.map(d => ({
|
||||
...d,
|
||||
shortDate: d.date.slice(5), // MM-DD
|
||||
}));
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height={180}>
|
||||
<LineChart data={formatted} margin={{ top: 5, right: 10, left: -20, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="scoreGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="#3b82d4" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="#3b82d4" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#2d3447" />
|
||||
<XAxis
|
||||
dataKey="shortDate"
|
||||
tick={{ fill: "#64748b", fontSize: 10 }}
|
||||
axisLine={{ stroke: "#2d3447" }}
|
||||
tickLine={false}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tick={{ fill: "#64748b", fontSize: 10 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<ReferenceLine y={75} stroke="#3b82d430" strokeDasharray="4 4" />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="score"
|
||||
stroke="#3b82d4"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
activeDot={{ r: 5, fill: "#3b82d4", stroke: "#0a0d14", strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user