fix(monitor/frontend): dshield panel (topports record has date/limit siblings of port entries)

This commit is contained in:
CarterPerez-dev 2026-05-03 18:54:34 -04:00
parent 038de64605
commit 0705f37330
2 changed files with 82 additions and 31 deletions

View File

@ -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
}

View File

@ -1,7 +1,8 @@
// ©AngelaMos | 2026
// 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 { Panel } from './Panel'
@ -10,39 +11,11 @@ const SOURCE_ROW_LIMIT = 8
const THOUSAND = 1_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 {
const { data } = useSnapshot()
const ds = (data?.scan_firehose as DShieldData | undefined) ?? {}
const ds = useDShieldData()
const ports = toArray(ds.topports)
.filter(isPort)
.slice()
.sort((a, b) => a.rank - b.rank)
.slice(0, PORT_ROW_LIMIT)
@ -119,6 +92,17 @@ function toArray<T>(v: Record<string, T> | T[] | undefined): T[] {
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(
list: DShieldDailySummary[] | undefined
): DShieldDailySummary | undefined {