feat(frontend): add Zod schemas for threat detection API
Type-safe schemas matching backend Pydantic models for threats, stats, models status, and WebSocket alerts.
This commit is contained in:
parent
012d849366
commit
cdeb1451ef
|
|
@ -2,3 +2,8 @@
|
||||||
// © AngelaMos | 2026
|
// © AngelaMos | 2026
|
||||||
// index.ts
|
// index.ts
|
||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
|
export * from './threats.types'
|
||||||
|
export * from './stats.types'
|
||||||
|
export * from './models.types'
|
||||||
|
export * from './websocket.types'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// ===================
|
||||||
|
// © AngelaMos | 2026
|
||||||
|
// models.types.ts
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const ActiveModelSchema = z.object({
|
||||||
|
model_type: z.string(),
|
||||||
|
version: z.string(),
|
||||||
|
training_samples: z.number().int(),
|
||||||
|
metrics: z.record(z.string(), z.number()),
|
||||||
|
threshold: z.number().nullable(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ModelStatusSchema = z.object({
|
||||||
|
models_loaded: z.boolean(),
|
||||||
|
detection_mode: z.string(),
|
||||||
|
active_models: z.array(ActiveModelSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const RetrainResponseSchema = z.object({
|
||||||
|
status: z.string(),
|
||||||
|
job_id: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ActiveModel = z.infer<typeof ActiveModelSchema>
|
||||||
|
export type ModelStatus = z.infer<typeof ModelStatusSchema>
|
||||||
|
export type RetrainResponse = z.infer<typeof RetrainResponseSchema>
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// ===================
|
||||||
|
// © AngelaMos | 2026
|
||||||
|
// stats.types.ts
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const SeverityBreakdownSchema = z.object({
|
||||||
|
high: z.number().int(),
|
||||||
|
medium: z.number().int(),
|
||||||
|
low: z.number().int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const IPStatEntrySchema = z.object({
|
||||||
|
source_ip: z.string(),
|
||||||
|
count: z.number().int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const PathStatEntrySchema = z.object({
|
||||||
|
path: z.string(),
|
||||||
|
count: z.number().int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const StatsResponseSchema = z.object({
|
||||||
|
time_range: z.string(),
|
||||||
|
threats_stored: z.number().int(),
|
||||||
|
threats_detected: z.number().int(),
|
||||||
|
severity_breakdown: SeverityBreakdownSchema,
|
||||||
|
top_source_ips: z.array(IPStatEntrySchema),
|
||||||
|
top_attacked_paths: z.array(PathStatEntrySchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type SeverityBreakdown = z.infer<typeof SeverityBreakdownSchema>
|
||||||
|
export type IPStatEntry = z.infer<typeof IPStatEntrySchema>
|
||||||
|
export type PathStatEntry = z.infer<typeof PathStatEntrySchema>
|
||||||
|
export type StatsResponse = z.infer<typeof StatsResponseSchema>
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
// ===================
|
||||||
|
// © AngelaMos | 2026
|
||||||
|
// threats.types.ts
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const GeoInfoSchema = z.object({
|
||||||
|
country: z.string().nullable(),
|
||||||
|
city: z.string().nullable(),
|
||||||
|
lat: z.number().nullable(),
|
||||||
|
lon: z.number().nullable(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ThreatEventSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
created_at: z.string(),
|
||||||
|
source_ip: z.string(),
|
||||||
|
request_method: z.string(),
|
||||||
|
request_path: z.string(),
|
||||||
|
status_code: z.number().int(),
|
||||||
|
response_size: z.number().int(),
|
||||||
|
user_agent: z.string(),
|
||||||
|
threat_score: z.number(),
|
||||||
|
severity: z.literal(['HIGH', 'MEDIUM', 'LOW']),
|
||||||
|
component_scores: z.record(z.string(), z.number()),
|
||||||
|
geo: GeoInfoSchema,
|
||||||
|
matched_rules: z.array(z.string()).nullable(),
|
||||||
|
model_version: z.string().nullable(),
|
||||||
|
reviewed: z.boolean(),
|
||||||
|
review_label: z.string().nullable(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ThreatListSchema = z.object({
|
||||||
|
total: z.number().int(),
|
||||||
|
limit: z.number().int(),
|
||||||
|
offset: z.number().int(),
|
||||||
|
items: z.array(ThreatEventSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type GeoInfo = z.infer<typeof GeoInfoSchema>
|
||||||
|
export type ThreatEvent = z.infer<typeof ThreatEventSchema>
|
||||||
|
export type ThreatList = z.infer<typeof ThreatListSchema>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// ===================
|
||||||
|
// © AngelaMos | 2026
|
||||||
|
// websocket.types.ts
|
||||||
|
// ===================
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const WebSocketAlertSchema = z.object({
|
||||||
|
event: z.literal('threat'),
|
||||||
|
timestamp: z.string(),
|
||||||
|
source_ip: z.string(),
|
||||||
|
request_path: z.string(),
|
||||||
|
threat_score: z.number(),
|
||||||
|
severity: z.string(),
|
||||||
|
component_scores: z.record(z.string(), z.number()),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type WebSocketAlert = z.infer<typeof WebSocketAlertSchema>
|
||||||
Loading…
Reference in New Issue