feat: phase 1C JWT UI, events/hosts API, Vue frontend

This commit is contained in:
2026-05-26 21:46:34 +10:00
parent d8a8329397
commit bdfc7016e6
27 changed files with 2498 additions and 23 deletions
+86
View File
@@ -0,0 +1,86 @@
const TOKEN_KEY = "sac_token";
export function getToken(): string | null {
return localStorage.getItem(TOKEN_KEY);
}
export function setToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token);
}
export function clearToken(): void {
localStorage.removeItem(TOKEN_KEY);
}
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
const headers = new Headers(init.headers);
if (!headers.has("Content-Type") && init.body) {
headers.set("Content-Type", "application/json");
}
const token = getToken();
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
const res = await fetch(path, { ...init, headers });
if (res.status === 401) {
clearToken();
window.location.href = "/login";
throw new Error("Unauthorized");
}
if (!res.ok) {
const text = await res.text();
throw new Error(text || res.statusText);
}
return res.json() as Promise<T>;
}
export interface TokenResponse {
access_token: string;
token_type: string;
}
export interface EventSummary {
id: number;
event_id: string;
host_id: number;
hostname: string;
occurred_at: string;
received_at: string;
category: string;
type: string;
severity: string;
title: string;
summary: string;
}
export interface EventListResponse {
items: EventSummary[];
total: number;
page: number;
page_size: number;
}
export interface EventDetail extends EventSummary {
details: Record<string, unknown> | null;
raw: Record<string, unknown> | null;
payload: Record<string, unknown>;
}
export interface HostSummary {
id: number;
hostname: string;
display_name: string | null;
os_family: string;
product: string;
product_version: string | null;
ipv4: string | null;
last_seen_at: string;
event_count: number | null;
}
export interface HostListResponse {
items: HostSummary[];
total: number;
page: number;
page_size: number;
}