Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions frontend/src/features/alerts/components/alert-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Flame, X } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/shared/lib/utils'
import { Button } from '@/shared/components/ui/button'
import { SEV_META, ST_META, TS, absTime, riskOf, sevKey, statusKey } from '../lib/alert-meta'
import { SEV_BADGE, ST_META, TS, absTime, riskOf, sevKey, statusKey } from '../lib/alert-meta'
import { combineUserNote, isAiNote, parseAiNote, userNotePart } from '../lib/ai-note'
import { type Alert, type AlertTag } from '../types/alert.types'
import { useRelatedLogs } from '../hooks/use-related-logs'
Expand Down Expand Up @@ -56,9 +56,9 @@ export function AlertDrawer({
const [notes, setNotes] = useState(userNotePart(a.notes))
// The SOC AI agent may write its assessment into notes OR statusObservation.
const aiNote = parseAiNote(a.notes) ?? parseAiNote(a.statusObservation)
const sm = SEV_META[sevKey(a)]
const sk = sevKey(a)
const stm = ST_META[statusKey(a)]
const smLabel = t(`alerts.severity.${sevKey(a)}`)
const smLabel = t(`alerts.severity.${sk}`)
const stmLabel = t(`alerts.status.${statusKey(a)}`)
const tags = a.tags ?? []

Expand All @@ -75,7 +75,7 @@ export function AlertDrawer({
<div className="flex items-start justify-between gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
<span className={cn('inline-flex items-center rounded-md px-1.5 py-0.5 font-medium ring-1 ring-inset', sm.pill)}>
<span className={cn('inline-flex items-center rounded-md px-1.5 py-0.5 font-medium uppercase tracking-wide ring-1 ring-inset', SEV_BADGE[sk])}>
{smLabel}
</span>
<span className={cn('inline-flex items-center rounded-md px-1.5 py-0.5 font-medium ring-1 ring-inset', stm.pill)}>
Expand Down Expand Up @@ -206,6 +206,16 @@ export function AlertDrawer({
)}
<Section title={t('alerts.drawer.section.details')}>
<dl className="grid grid-cols-[140px_1fr] gap-y-2 text-xs">
<Row k={t('alerts.drawer.details.severity')}>
<span
className={cn(
'inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide ring-1 ring-inset',
SEV_BADGE[sk],
)}
>
{smLabel}
</span>
</Row>
<Row k={t('alerts.drawer.details.risk')}>{riskOf(a)}</Row>
<Row k={t('alerts.drawer.details.assignee')}>
{a.assignee ? a.assignee : <span className="text-muted-foreground">{t('alerts.drawer.unassigned')}</span>}
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/features/alerts/components/alert-row.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Sparkles, Tag, UserCheck } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/shared/lib/utils'
import { SEV_META, TS, absTime, relativeTime, riskOf, sevKey, statusKey } from '../lib/alert-meta'
import { SEV_BADGE, SEV_META, TS, absTime, relativeTime, riskOf, sevKey, statusKey } from '../lib/alert-meta'
import { isAiNote } from '../lib/ai-note'
import type { Alert, AlertTag } from '../types/alert.types'
import { AlertIncidentTarget } from './alert-incident-target'
Expand Down Expand Up @@ -130,6 +130,16 @@ export function AlertRow({
<td className={TD}>
<FlowCell source={a.target} adversary={a.adversary} />
</td>
<td className={`${TD} text-center`}>
<span
className={cn(
'inline-flex items-center justify-center rounded-md px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide ring-1 ring-inset',
SEV_BADGE[sk],
)}
>
{t(`alerts.severity.${sk}`)}
</span>
</td>
<td className={`${TD} text-center`}>
<span
className={cn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function AlertsTableHeader({ allChecked, onTogglePage }: { allChecked: bo
<th className={TH}>{t('alerts.table.status')}</th>
<th className={TH}>{t('alerts.table.technique')}</th>
<th className={TH}>{t('alerts.table.sourceAdversary')}</th>
<th className={`${TH} text-center`}>{t('alerts.table.severity')}</th>
<th className={`${TH} text-center`} title={t('alerts.table.riskHint')}>{t('alerts.table.risk')}</th>
<th className={`${TH} text-center`}>{t('alerts.table.echoes')}</th>
<th className={`${TH} text-center`}>{t('alerts.table.time')}</th>
Expand All @@ -26,4 +27,4 @@ export function AlertsTableHeader({ allChecked, onTogglePage }: { allChecked: bo
)
}

export const ALERTS_TABLE_COLUMN_COUNT = 11
export const ALERTS_TABLE_COLUMN_COUNT = 12
8 changes: 8 additions & 0 deletions frontend/src/features/alerts/lib/alert-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ export const SEV_META: Record<SeverityKey, { label: string; bar: string; pill: s
low: { label: 'Low', bar: 'bg-sky-500', pill: 'bg-sky-500/15 text-sky-600 ring-sky-500/30 dark:text-sky-300' },
}

// Traffic-light badge (green/yellow/red) shown wherever the alert severity
// itself is displayed as a chip (table column, drawer details).
export const SEV_BADGE: Record<SeverityKey, string> = {
high: 'bg-red-500/15 text-red-600 ring-red-500/30 dark:text-red-300',
medium: 'bg-yellow-500/15 text-yellow-700 ring-yellow-500/30 dark:text-yellow-300',
low: 'bg-emerald-500/15 text-emerald-600 ring-emerald-500/30 dark:text-emerald-300',
}

export const ST_META: Record<StatusKey, { label: string; pill: string }> = {
open: { label: 'Open', pill: 'bg-red-500/15 text-red-600 ring-red-500/30 dark:text-red-300' },
in_review: { label: 'In review', pill: 'bg-sky-500/15 text-sky-600 ring-sky-500/30 dark:text-sky-300' },
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3883,6 +3883,7 @@
"status": "Status",
"technique": "Technik",
"sourceAdversary": "Quelle → Angreifer",
"severity": "Schweregrad",
"risk": "Risiko",
"echoes": "Echos",
"time": "Zeit",
Expand Down Expand Up @@ -3931,6 +3932,7 @@
"notes": "Notizen"
},
"details": {
"severity": "Schweregrad",
"risk": "Risiko",
"category": "Kategorie",
"technique": "Technik",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4169,6 +4169,7 @@
"status": "Status",
"technique": "Technique",
"sourceAdversary": "Source → Adversary",
"severity": "Severity",
"risk": "Risk",
"echoes": "Echoes",
"time": "Time",
Expand Down Expand Up @@ -4217,6 +4218,7 @@
"notes": "Notes"
},
"details": {
"severity": "Severity",
"risk": "Risk",
"category": "Category",
"technique": "Technique",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -3992,6 +3992,7 @@
"status": "Estado",
"technique": "Técnica",
"sourceAdversary": "Origen → Adversario",
"severity": "Severidad",
"risk": "Riesgo",
"echoes": "Ecos",
"time": "Hora",
Expand Down Expand Up @@ -4040,6 +4041,7 @@
"notes": "Notas"
},
"details": {
"severity": "Severidad",
"risk": "Riesgo",
"category": "Categoría",
"technique": "Técnica",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3883,6 +3883,7 @@
"status": "Statut",
"technique": "Technique",
"sourceAdversary": "Source → Adversaire",
"severity": "Gravité",
"risk": "Risque",
"echoes": "Échos",
"time": "Heure",
Expand Down Expand Up @@ -3931,6 +3932,7 @@
"notes": "Notes"
},
"details": {
"severity": "Gravité",
"risk": "Risque",
"category": "Catégorie",
"technique": "Technique",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -3883,6 +3883,7 @@
"status": "Stato",
"technique": "Tecnica",
"sourceAdversary": "Origine → Avversario",
"severity": "Gravità",
"risk": "Rischio",
"echoes": "Echi",
"time": "Ora",
Expand Down Expand Up @@ -3931,6 +3932,7 @@
"notes": "Note"
},
"details": {
"severity": "Gravità",
"risk": "Rischio",
"category": "Categoria",
"technique": "Tecnica",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -3992,6 +3992,7 @@
"status": "Estado",
"technique": "Técnica",
"sourceAdversary": "Origem → Adversário",
"severity": "Severidade",
"risk": "Risco",
"echoes": "Ecos",
"time": "Hora",
Expand Down Expand Up @@ -4040,6 +4041,7 @@
"notes": "Notas"
},
"details": {
"severity": "Severidade",
"risk": "Risco",
"category": "Categoria",
"technique": "Técnica",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/shared/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -3574,6 +3574,7 @@
"status": "Статус",
"technique": "Техника",
"sourceAdversary": "Источник → Противник",
"severity": "Серьёзность",
"risk": "Риск",
"echoes": "Эхо",
"time": "Время",
Expand Down Expand Up @@ -3622,6 +3623,7 @@
"notes": "Заметки"
},
"details": {
"severity": "Серьёзность",
"risk": "Риск",
"category": "Категория",
"technique": "Техника",
Expand Down
Loading