fix(monitor/frontend): dshield panel (topports record has date/limit siblings of port entries)
This commit is contained in:
parent
038de64605
commit
0705f37330
|
|
@ -0,0 +1,67 @@
|
||||||
|
// ©AngelaMos | 2026
|
||||||
|
// threat.types.ts
|
||||||
|
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const ransomwareVictimSchema = z.object({
|
||||||
|
post_title: z.string(),
|
||||||
|
group_name: z.string(),
|
||||||
|
discovered: z.string(),
|
||||||
|
country: z.string().optional(),
|
||||||
|
activity: z.string().optional(),
|
||||||
|
website: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type RansomwareVictim = z.infer<typeof ransomwareVictimSchema>
|
||||||
|
|
||||||
|
export const dshieldPortSchema = z.object({
|
||||||
|
rank: z.number(),
|
||||||
|
targetport: z.number(),
|
||||||
|
records: z.number(),
|
||||||
|
targets: z.number(),
|
||||||
|
sources: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DShieldPort = z.infer<typeof dshieldPortSchema>
|
||||||
|
|
||||||
|
export const dshieldSourceSchema = z.object({
|
||||||
|
rank: z.number(),
|
||||||
|
source: z.string(),
|
||||||
|
reports: z.number(),
|
||||||
|
targets: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DShieldSource = z.infer<typeof dshieldSourceSchema>
|
||||||
|
|
||||||
|
export const dshieldDailySummarySchema = z.object({
|
||||||
|
date: z.string(),
|
||||||
|
records: z.number(),
|
||||||
|
sources: z.number(),
|
||||||
|
targets: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DShieldDailySummary = z.infer<typeof dshieldDailySummarySchema>
|
||||||
|
|
||||||
|
export const dshieldDataSchema = z.object({
|
||||||
|
topports: z
|
||||||
|
.union([z.record(z.string(), z.unknown()), z.array(z.unknown())])
|
||||||
|
.optional(),
|
||||||
|
topips: z.array(dshieldSourceSchema).optional(),
|
||||||
|
dailysummary: z.array(dshieldDailySummarySchema).optional(),
|
||||||
|
ts: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DShieldData = z.infer<typeof dshieldDataSchema>
|
||||||
|
|
||||||
|
export const isValidRansomwareVictim = (
|
||||||
|
data: unknown
|
||||||
|
): data is RansomwareVictim => {
|
||||||
|
if (data === null || data === undefined || typeof data !== 'object') return false
|
||||||
|
return ransomwareVictimSchema.safeParse(data).success
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isValidDShieldData = (data: unknown): data is DShieldData => {
|
||||||
|
if (data === null || data === undefined || typeof data !== 'object') return false
|
||||||
|
return dshieldDataSchema.safeParse(data).success
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// ©AngelaMos | 2026
|
// ©AngelaMos | 2026
|
||||||
// DShieldPanel.tsx
|
// DShieldPanel.tsx
|
||||||
|
|
||||||
import { useSnapshot } from '@/api/snapshot'
|
import { type DShieldDailySummary, type DShieldPort } from '@/api/types'
|
||||||
|
import { useDShieldData } from '@/api/hooks'
|
||||||
import styles from './DShieldPanel.module.scss'
|
import styles from './DShieldPanel.module.scss'
|
||||||
import { Panel } from './Panel'
|
import { Panel } from './Panel'
|
||||||
|
|
||||||
|
|
@ -10,39 +11,11 @@ const SOURCE_ROW_LIMIT = 8
|
||||||
const THOUSAND = 1_000
|
const THOUSAND = 1_000
|
||||||
const MILLION = 1_000_000
|
const MILLION = 1_000_000
|
||||||
|
|
||||||
interface DShieldPort {
|
|
||||||
rank: number
|
|
||||||
targetport: number
|
|
||||||
records: number
|
|
||||||
targets: number
|
|
||||||
sources: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DShieldSource {
|
|
||||||
rank: number
|
|
||||||
source: string
|
|
||||||
reports: number
|
|
||||||
targets: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DShieldDailySummary {
|
|
||||||
date: string
|
|
||||||
records: number
|
|
||||||
sources: number
|
|
||||||
targets: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DShieldData {
|
|
||||||
topports?: Record<string, DShieldPort> | DShieldPort[]
|
|
||||||
topips?: DShieldSource[]
|
|
||||||
dailysummary?: DShieldDailySummary[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DShieldPanel(): React.ReactElement {
|
export function DShieldPanel(): React.ReactElement {
|
||||||
const { data } = useSnapshot()
|
const ds = useDShieldData()
|
||||||
const ds = (data?.scan_firehose as DShieldData | undefined) ?? {}
|
|
||||||
|
|
||||||
const ports = toArray(ds.topports)
|
const ports = toArray(ds.topports)
|
||||||
|
.filter(isPort)
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.rank - b.rank)
|
.sort((a, b) => a.rank - b.rank)
|
||||||
.slice(0, PORT_ROW_LIMIT)
|
.slice(0, PORT_ROW_LIMIT)
|
||||||
|
|
@ -119,6 +92,17 @@ function toArray<T>(v: Record<string, T> | T[] | undefined): T[] {
|
||||||
return Object.values(v)
|
return Object.values(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPort(v: unknown): v is DShieldPort {
|
||||||
|
if (!v || typeof v !== 'object') return false
|
||||||
|
const p = v as Record<string, unknown>
|
||||||
|
return (
|
||||||
|
typeof p.targetport === 'number' &&
|
||||||
|
typeof p.records === 'number' &&
|
||||||
|
typeof p.sources === 'number' &&
|
||||||
|
typeof p.rank === 'number'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function pickLatestSummary(
|
function pickLatestSummary(
|
||||||
list: DShieldDailySummary[] | undefined
|
list: DShieldDailySummary[] | undefined
|
||||||
): DShieldDailySummary | undefined {
|
): DShieldDailySummary | undefined {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue