diff --git a/frontend/src/components/design/Formula.tsx b/frontend/src/components/design/Formula.tsx index 61a962d..da93b2e 100644 --- a/frontend/src/components/design/Formula.tsx +++ b/frontend/src/components/design/Formula.tsx @@ -1,51 +1,69 @@ import React from 'react'; -// PTSZI formula component — W = (Q^th + q + (1 - Q^re)) / 3 · Z -// Typography-first: large readable variables, sup rendered at 0.7em with high vertical align. +// PTSZI formula component with proper typographic fraction layout: +// W = ( Q^threat + q^threat + (1 − Q^reaction) ) / 3 · Z +// Rendered as a real stacked fraction (numerator / line / denominator). -interface VarSpanProps { +interface VarProps { color: string; children: React.ReactNode; sup?: string; bold?: boolean; + baseSize: number; } -const V: React.FC = ({ color, children, sup, bold }) => ( - - {children} +const Var: React.FC = ({ color, children, sup, bold, baseSize }) => ( + + + {children} + {sup && ( - {sup} + alignSelf: 'flex-start', + marginTop: `-${Math.round(baseSize * 0.22)}px`, + marginLeft: 1, + lineHeight: 1, + }}> + {sup} + )} ); -const Op: React.FC<{ children: React.ReactNode; muted?: boolean }> = ({ children, muted }) => ( +const Op: React.FC<{ children: React.ReactNode; size: number; muted?: boolean; gap?: number }> = ({ children, size, muted, gap = 0.35 }) => ( {children} ); -const Num: React.FC<{ children: React.ReactNode }> = ({ children }) => ( - +const Numeric: React.FC<{ children: React.ReactNode; size: number; bold?: boolean }> = ({ children, size, bold }) => ( + {children} ); @@ -58,71 +76,75 @@ export interface PtsziFormulaProps { export const PtsziFormula: React.FC = ({ size = 'lg', - align = 'left', + align = 'center', withBackground = true, }) => { - const fs = size === 'xl' ? 28 : size === 'lg' ? 22 : 17; + const baseSize = size === 'xl' ? 32 : size === 'lg' ? 24 : 19; + const fractionLineThickness = Math.max(1.5, baseSize * 0.06); + return (
- W - = - ( - Q - + - q - + - ( - 1 - - Q - ) - ) - / - 3 - · - Z + {/* W */} + W + + {/* = */} + = + + {/* Fraction */} + + {/* Numerator */} + + Q + + + q + + + ( + 1 + + Q + ) + + + {/* Fraction line */} + + + {/* Denominator */} + + 3 + + + + {/* · Z */} + · + Z
); }; - -// Compact inline version for use inside cards where background is controlled by parent -export const PtsziFormulaInline: React.FC<{ size?: number }> = ({ size = 17 }) => ( -
- W - = - ( - Q - + - q - + - ( - 1 - - Q - ) - ) - / - 3 - · - Z -
-); diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 054eafd..89994d4 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -69,16 +69,9 @@ export const DashboardPage: React.FC = () => { const spark3 = [4, 3, 2, 4, 5, 3, 2, 1, 2, 1, 0, 1, 2, 1, 0]; const spark4 = [60, 58, 62, 61, 65, 67, 66, 70, 69, 72, 74, 73, 76, 78, 81]; - const openCveCount = assets.reduce((s, _a) => s + 0, 0); // TODO: compute from vulns endpoint later - - // Static fake feed (replace with real events endpoint when it exists) - const feed = [ - { id: 'F-2041', at: '15:42', sev: 'critical' as Level, source: 'Honeynet', text: 'Сработала сигнатура БДУ.006 на портале', asset: 'A-003' }, - { id: 'F-2040', at: '15:31', sev: 'high' as Level, source: 'MaxPatrol', text: 'Аномальный брутфорс учётных данных AD-01 (~140 попыток/мин)', asset: 'A-006' }, - { id: 'F-2039', at: '15:18', sev: 'medium' as Level, source: 'NetFlow', text: 'Повышенный исходящий трафик DNS-канала', asset: 'A-010' }, - { id: 'F-2038', at: '14:55', sev: 'high' as Level, source: 'CVE-фид', text: 'Опубликован CVE-2026-0188 (CVSS 9.6)', asset: 'A-001' }, - { id: 'F-2037', at: '14:32', sev: 'low' as Level, source: 'Сканер', text: 'Завершено сканирование сегмента DMZ — 0 новых уязвимостей', asset: null }, - ]; + // Count of assets that have at least one risk point + const assetsAtRisk = new Set(points.map(p => p.asset_id)).size; + const kiiCount = assets.filter(a => a.kii_category && a.kii_category !== 'none').length; return ( { } /> - } /> - f.sev === 'critical' || f.sev === 'high').length} tone="low" mono - sub="MTTR 47 мин · в пределах SLA" + } /> - {/* Main grid */} -
+ {/* Main section */} +
{/* Risk breakdown */} navigate('/risk/map')}>Открыть матрицу →}> @@ -197,30 +190,6 @@ export const DashboardPage: React.FC = () => {
- {/* Threat feed */} - }>LIVE} - pad={0}> -
- {feed.map((f, i) => ( -
-
-
-
- {f.at} - {f.source} - {f.asset && {f.asset}} -
-
{f.text}
-
-
- ))} -
-
{/* PTSZI formula callout */} @@ -229,20 +198,20 @@ export const DashboardPage: React.FC = () => {
-
Qth · потенциал угрозы
-
0 … 1 — из БДУ ФСТЭК
+
Qthreat
+
степень реализации угрозы, 0…1
-
q · вес уязвимости
-
0 … 1 — CVSS/БДУ
+
qthreat
+
степень опасности угрозы, 0…1
-
Qre · реакция СЗИ
-
0 … 1 — доля закрытых VL
+
Qreaction
+
степень предотвращения угрозы, 0…1
-
Z · контур
-
0.5 … 1 — prod/isolated
+
Z
+
критичность для контура, 0.5 или 1