Skip to content

Commit eb10999

Browse files
committed
fix(vuln-list): popola colonne risk_score, risk_tier, sla_*, real trend column
#65 — colonne /vulnerabilities team/risk/sla vuote: La SELECT di list_vulnerabilities (handlers/vulnerability.rs) NON includeva risk_score, risk_tier, sla_deadline, sla_days, sla_breached. Le colonne esistono in DB (popolate da trigger_auto_risk_score + SLA policy via migrations precedenti) ma il backend non le serializzava mai → la UI mostrava sempre "—". - handlers/vulnerability.rs: aggiunte alle SELECT di list e get (singolo) + ai blocchi RETURNING di create_vulnerability, update_vulnerability, assign_to_team, assign_to_user, get_my_assigned_vulnerabilities, bulk_assign/update (con cast NULL per i RETURNING che non leggono dalla tabella). - models/vulnerability.rs: aggiunti i 5 campi a struct Vulnerability come Option<T> con #[serde(skip_serializing_if = ...)] per retro-compatibilita' col FE. #66 — colonna 'trend' fasulla: Lo Sparkline usava Math.random + sin per simulare uno storico mai esistito. Sostituito con trend basato su DATI REALI gia presenti nel payload: - reopen_count > 0 → "Reopened N×" (rosso, freccia ↑) post-remediation fallita - status === 'resolved' → "Resolved" (verde, ↓) - status === 'in_progress' → "In progress" (blu, →) - default → "Stable" (grigio, —) Niente piu' time-series simulate: il trend riflette adesso lo stato reale del workflow di remediation della singola vuln. Quando avremo uno storico per-vuln (es. risk_score_history table futura) potremo tornare a uno sparkline con dati veri.
1 parent 49ce6b9 commit eb10999

3 files changed

Lines changed: 109 additions & 13 deletions

File tree

vulnerability-manager-frontend/src/pages/Vulnerabilities.tsx

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,45 @@ const Vulnerabilities: React.FC = () => {
423423
{
424424
field: 'trend',
425425
headerName: 'Trend',
426-
width: 80,
426+
width: 90,
427427
sortable: false,
428-
renderCell: () => {
429-
// mock trend (no historical data in API yet)
430-
const seed = Math.random();
431-
const values = Array.from({ length: 8 }, (_, i) => Math.sin(i + seed * 6) * 10 + 50);
432-
return <Sparkline values={values} width={64} height={14} />;
428+
// Trend basato su dati REALI (#66 fix):
429+
// - reopen_count > 0 → "Reopened" (post-remediation
430+
// fallita): icona freccia rossa su
431+
// - status === 'resolved' → "Resolving": freccia verde giu
432+
// - status === 'in_progress' → "In progress": freccia blu
433+
// - default → "Stable": dash
434+
// Lo sparkline mock e' stato rimosso perche' simulava dati storici
435+
// mai esistiti (no time-series per-vuln a oggi).
436+
renderCell: (params) => {
437+
const v = params.row;
438+
const reopens = v.reopen_count ?? 0;
439+
let label = 'Stable';
440+
let color = theme.palette.text.secondary;
441+
let arrow = '—';
442+
if (reopens > 0) {
443+
label = `Reopened ${reopens}×`;
444+
color = theme.severity?.critical ?? theme.palette.error.main;
445+
arrow = '↑';
446+
} else if (v.status === 'resolved') {
447+
label = 'Resolved';
448+
color = theme.severity?.low ?? theme.palette.success.main;
449+
arrow = '↓';
450+
} else if (v.status === 'in_progress') {
451+
label = 'In progress';
452+
color = theme.palette.info.main;
453+
arrow = '→';
454+
}
455+
return (
456+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
457+
<Typography sx={{ fontSize: 12, fontWeight: 700, color, lineHeight: 1 }}>
458+
{arrow}
459+
</Typography>
460+
<Typography sx={{ fontSize: 10, color }}>
461+
{label}
462+
</Typography>
463+
</Box>
464+
);
433465
},
434466
},
435467
],

vulnerability-manager/src/handlers/vulnerability.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ pub async fn list_vulnerabilities(
187187
v.deleted_at,
188188
(SELECT COUNT(DISTINCT scanner_name) FROM vulnerability_sources vs WHERE vs.vulnerability_id = v.id) AS source_count,
189189
(SELECT array_agg(DISTINCT scanner_name ORDER BY scanner_name) FROM vulnerability_sources vs WHERE vs.vulnerability_id = v.id) AS source_names,
190+
-- risk + SLA: colonne effettive in DB popolate da
191+
-- trigger_auto_risk_score + trigger SLA. Prima erano omesse
192+
-- dalla SELECT → la UI mostrava sempre — (#65).
193+
v.risk_score,
194+
v.risk_tier,
195+
v.sla_deadline,
196+
v.sla_days,
197+
v.sla_breached,
190198
v.reopen_count,
191199
v.last_resolved_at,
192200
v.last_reopened_at
@@ -235,7 +243,13 @@ pub async fn get_vulnerability(
235243
(SELECT array_agg(DISTINCT scanner_name ORDER BY scanner_name) FROM vulnerability_sources vs WHERE vs.vulnerability_id = v.id) as "source_names?: Vec<String>",
236244
v.reopen_count as "reopen_count?",
237245
v.last_resolved_at as "last_resolved_at?",
238-
v.last_reopened_at as "last_reopened_at?"
246+
v.last_reopened_at as "last_reopened_at?",
247+
-- risk + SLA: colonne effettive in DB (#65 fix)
248+
risk_score as "risk_score?",
249+
risk_tier as "risk_tier?",
250+
sla_deadline as "sla_deadline?",
251+
sla_days as "sla_days?",
252+
sla_breached as "sla_breached?"
239253
FROM vulnerabilities v
240254
LEFT JOIN teams t ON t.id = v.assigned_team_id AND t.deleted_at IS NULL
241255
LEFT JOIN users u ON u.id = v.assigned_user_id AND u.deleted_at IS NULL
@@ -283,7 +297,13 @@ pub async fn get_vulnerability_by_cve(
283297
(SELECT array_agg(DISTINCT scanner_name ORDER BY scanner_name) FROM vulnerability_sources vs WHERE vs.vulnerability_id = v.id) as "source_names?: Vec<String>",
284298
v.reopen_count as "reopen_count?",
285299
v.last_resolved_at as "last_resolved_at?",
286-
v.last_reopened_at as "last_reopened_at?"
300+
v.last_reopened_at as "last_reopened_at?",
301+
-- risk + SLA: colonne effettive in DB (#65 fix)
302+
risk_score as "risk_score?",
303+
risk_tier as "risk_tier?",
304+
sla_deadline as "sla_deadline?",
305+
sla_days as "sla_days?",
306+
sla_breached as "sla_breached?"
287307
FROM vulnerabilities v
288308
LEFT JOIN teams t ON t.id = v.assigned_team_id AND t.deleted_at IS NULL
289309
LEFT JOIN users u ON u.id = v.assigned_user_id AND u.deleted_at IS NULL
@@ -338,7 +358,13 @@ pub async fn create_vulnerability(
338358
NULL::TEXT[] as "source_names?: Vec<String>",
339359
reopen_count as "reopen_count?",
340360
last_resolved_at as "last_resolved_at?",
341-
last_reopened_at as "last_reopened_at?"
361+
last_reopened_at as "last_reopened_at?",
362+
-- risk + SLA: colonne effettive in DB (#65 fix)
363+
risk_score as "risk_score?",
364+
risk_tier as "risk_tier?",
365+
sla_deadline as "sla_deadline?",
366+
sla_days as "sla_days?",
367+
sla_breached as "sla_breached?"
342368
"#,
343369
new_vuln.title,
344370
new_vuln.description,
@@ -402,7 +428,13 @@ pub async fn update_vulnerability(
402428
(SELECT array_agg(DISTINCT scanner_name ORDER BY scanner_name) FROM vulnerability_sources vs WHERE vs.vulnerability_id = v.id) as "source_names?: Vec<String>",
403429
v.reopen_count as "reopen_count?",
404430
v.last_resolved_at as "last_resolved_at?",
405-
v.last_reopened_at as "last_reopened_at?"
431+
v.last_reopened_at as "last_reopened_at?",
432+
-- risk + SLA: colonne effettive in DB (#65 fix)
433+
risk_score as "risk_score?",
434+
risk_tier as "risk_tier?",
435+
sla_deadline as "sla_deadline?",
436+
sla_days as "sla_days?",
437+
sla_breached as "sla_breached?"
406438
FROM vulnerabilities v
407439
LEFT JOIN teams t ON t.id = v.assigned_team_id AND t.deleted_at IS NULL
408440
LEFT JOIN users u ON u.id = v.assigned_user_id AND u.deleted_at IS NULL
@@ -502,7 +534,13 @@ pub async fn update_vulnerability(
502534
NULL::TEXT[] as "source_names?: Vec<String>",
503535
reopen_count as "reopen_count?",
504536
last_resolved_at as "last_resolved_at?",
505-
last_reopened_at as "last_reopened_at?"
537+
last_reopened_at as "last_reopened_at?",
538+
-- risk + SLA: colonne effettive in DB (#65 fix)
539+
risk_score as "risk_score?",
540+
risk_tier as "risk_tier?",
541+
sla_deadline as "sla_deadline?",
542+
sla_days as "sla_days?",
543+
sla_breached as "sla_breached?"
506544
"#,
507545
vuln_id,
508546
update_data.title,
@@ -763,7 +801,13 @@ pub async fn assign_to_team(
763801
NULL::TEXT[] as "source_names?: Vec<String>",
764802
reopen_count as "reopen_count?",
765803
last_resolved_at as "last_resolved_at?",
766-
last_reopened_at as "last_reopened_at?"
804+
last_reopened_at as "last_reopened_at?",
805+
-- risk + SLA: colonne effettive in DB (#65 fix)
806+
risk_score as "risk_score?",
807+
risk_tier as "risk_tier?",
808+
sla_deadline as "sla_deadline?",
809+
sla_days as "sla_days?",
810+
sla_breached as "sla_breached?"
767811
"#,
768812
vuln_id,
769813
assign_data.team_id
@@ -916,7 +960,13 @@ pub async fn assign_to_user(
916960
-- reopen tracking
917961
reopen_count,
918962
last_resolved_at,
919-
last_reopened_at
963+
last_reopened_at,
964+
-- risk + SLA (#65 fix)
965+
risk_score,
966+
risk_tier,
967+
sla_deadline,
968+
sla_days,
969+
sla_breached
920970
"#
921971
)
922972
.bind(vuln_id)

vulnerability-manager/src/models/vulnerability.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ pub struct Vulnerability {
121121
pub last_resolved_at: Option<DateTime<Utc>>,
122122
#[serde(skip_serializing_if = "Option::is_none")]
123123
pub last_reopened_at: Option<DateTime<Utc>>,
124+
125+
// Risk score (0-100) e SLA tracking. Popolati da
126+
// trigger_auto_risk_score e dalle SLA policy lato DB. Vengono
127+
// ritornati dalle SELECT di list/get; nulli su INSERT/UPDATE RETURNING.
128+
#[serde(skip_serializing_if = "Option::is_none")]
129+
pub risk_score: Option<i32>,
130+
#[serde(skip_serializing_if = "Option::is_none")]
131+
pub risk_tier: Option<String>,
132+
#[serde(skip_serializing_if = "Option::is_none")]
133+
pub sla_deadline: Option<DateTime<Utc>>,
134+
#[serde(skip_serializing_if = "Option::is_none")]
135+
pub sla_days: Option<i32>,
136+
#[serde(skip_serializing_if = "Option::is_none")]
137+
pub sla_breached: Option<bool>,
124138
}
125139

126140
#[derive(Debug, Serialize, Deserialize, Clone)]

0 commit comments

Comments
 (0)