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:
127
frontend/src/lib/api.ts
Normal file
127
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
const BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
|
||||
|
||||
let authToken: string | null = null;
|
||||
|
||||
export function setAuthToken(token: string | null) {
|
||||
authToken = token;
|
||||
}
|
||||
|
||||
async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
...(options.headers as Record<string, string>),
|
||||
};
|
||||
if (authToken) {
|
||||
headers["Authorization"] = `Bearer ${authToken}`;
|
||||
}
|
||||
const res = await fetch(`${BASE}${path}`, { ...options, headers });
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
||||
throw new Error(err.detail || `HTTP ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
login: (email: string, password: string) =>
|
||||
request<{ access_token: string; role: string; tenant_id: string; full_name: string }>(
|
||||
"/api/v1/auth/login",
|
||||
{ method: "POST", body: JSON.stringify({ email, password }) }
|
||||
),
|
||||
|
||||
me: () => request<{ id: string; email: string; full_name: string; role: string; tenant_id: string }>(
|
||||
"/api/v1/auth/me"
|
||||
),
|
||||
|
||||
dashboard: (tenantId: string) =>
|
||||
request<DashboardData>(`/api/v1/dashboard/${tenantId}`),
|
||||
|
||||
findings: (tenantId: string, params?: string) =>
|
||||
request<Finding[]>(`/api/v1/findings?tenant_id=${tenantId}${params ? "&" + params : ""}`),
|
||||
|
||||
finding: (id: string) =>
|
||||
request<Finding>(`/api/v1/findings/${id}`),
|
||||
|
||||
updateFindingStatus: (id: string, body: { status: string; resolution_note?: string }) =>
|
||||
request<Finding>(`/api/v1/findings/${id}/status`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
attackPaths: (findingId: string) =>
|
||||
request<AttackPath[]>(`/api/v1/attack-paths/${findingId}`),
|
||||
|
||||
footprint: (tenantId: string) =>
|
||||
request<FootprintData>(`/api/v1/footprint/${tenantId}`),
|
||||
|
||||
aiExplain: (findingId: string, question: string) =>
|
||||
request<{ question: string; answer: string }>(`/api/v1/ai/explain/${findingId}?question=${encodeURIComponent(question)}`),
|
||||
};
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DashboardData {
|
||||
tenant_name: string;
|
||||
current_score: number;
|
||||
previous_score: number | null;
|
||||
score_delta: number | null;
|
||||
score_trend: { date: string; score: number }[];
|
||||
top_risks: RiskCard[];
|
||||
open_critical: number;
|
||||
open_high: number;
|
||||
open_medium: number;
|
||||
total_open: number;
|
||||
baseline_score: number | null;
|
||||
baseline_date: string | null;
|
||||
}
|
||||
|
||||
export interface RiskCard {
|
||||
id: string;
|
||||
title: string;
|
||||
ai_summary: string | null;
|
||||
ai_business_impact: string | null;
|
||||
ai_impact_level: string | null;
|
||||
ai_fix_priority: string | null;
|
||||
severity: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
export interface Finding {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
title: string;
|
||||
severity: string;
|
||||
status: string;
|
||||
category: string;
|
||||
technical_description: string | null;
|
||||
cve_id: string | null;
|
||||
cvss_score: number | null;
|
||||
affected_component: string | null;
|
||||
ai_summary: string | null;
|
||||
ai_business_impact: string | null;
|
||||
ai_impact_level: string | null;
|
||||
ai_remediation_steps: string | null;
|
||||
ai_fix_priority: string | null;
|
||||
assignee_email: string | null;
|
||||
due_date: string | null;
|
||||
is_top_risk: boolean;
|
||||
source: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface AttackPath {
|
||||
id: string;
|
||||
finding_id: string;
|
||||
title: string;
|
||||
ai_narrative: string | null;
|
||||
nodes_json: string | null;
|
||||
edges_json: string | null;
|
||||
}
|
||||
|
||||
export interface FootprintData {
|
||||
tenant_id: string;
|
||||
executives: { id: string; name: string; title: string; email: string }[];
|
||||
footprint_findings: any[];
|
||||
total_exposures: number;
|
||||
}
|
||||
Reference in New Issue
Block a user