fix: cast compliance_pct to float8 and fix PDF generation crashes
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 56s
CI Pipeline / Rust Unit Tests (push) Successful in 1m16s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 5s
CI Pipeline / Clippy Lints (push) Successful in 56s
CI Pipeline / Rust Unit Tests (push) Successful in 1m16s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
This commit is contained in:
@ -29,9 +29,9 @@ SELECT
|
|||||||
h.last_patch_at,
|
h.last_patch_at,
|
||||||
COALESCE(jsonb_array_length(pd.installed_packages), 0) AS total_packages,
|
COALESCE(jsonb_array_length(pd.installed_packages), 0) AS total_packages,
|
||||||
COALESCE(pd.patch_count, 0) AS pending_patches,
|
COALESCE(pd.patch_count, 0) AS pending_patches,
|
||||||
CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages), 0) = 0 THEN 100.0
|
(CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages), 0) = 0 THEN 100.0
|
||||||
ELSE ROUND(CAST((1.0 - pd.patch_count::float / NULLIF(jsonb_array_length(pd.installed_packages), 0)) * 100 AS numeric), 1)
|
ELSE ROUND(CAST((1.0 - pd.patch_count::float / NULLIF(jsonb_array_length(pd.installed_packages), 0)) * 100 AS numeric), 1)
|
||||||
END AS compliance_pct,
|
END)::float8 AS compliance_pct,
|
||||||
COALESCE(string_agg(DISTINCT g.name, ', '), '') AS group_names
|
COALESCE(string_agg(DISTINCT g.name, ', '), '') AS group_names
|
||||||
FROM hosts h
|
FROM hosts h
|
||||||
LEFT JOIN host_patch_data pd ON pd.host_id = h.id
|
LEFT JOIN host_patch_data pd ON pd.host_id = h.id
|
||||||
@ -59,9 +59,9 @@ SELECT
|
|||||||
h.last_patch_at,
|
h.last_patch_at,
|
||||||
COALESCE(jsonb_array_length(pd.installed_packages), 0) AS total_packages,
|
COALESCE(jsonb_array_length(pd.installed_packages), 0) AS total_packages,
|
||||||
COALESCE(pd.patch_count, 0) AS pending_patches,
|
COALESCE(pd.patch_count, 0) AS pending_patches,
|
||||||
CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages), 0) = 0 THEN 100.0
|
(CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages), 0) = 0 THEN 100.0
|
||||||
ELSE ROUND(CAST((1.0 - pd.patch_count::float / NULLIF(jsonb_array_length(pd.installed_packages), 0)) * 100 AS numeric), 1)
|
ELSE ROUND(CAST((1.0 - pd.patch_count::float / NULLIF(jsonb_array_length(pd.installed_packages), 0)) * 100 AS numeric), 1)
|
||||||
END AS compliance_pct,
|
END)::float8 AS compliance_pct,
|
||||||
COALESCE(string_agg(DISTINCT g.name, ', '), '') AS group_names
|
COALESCE(string_agg(DISTINCT g.name, ', '), '') AS group_names
|
||||||
FROM hosts h
|
FROM hosts h
|
||||||
LEFT JOIN host_patch_data pd ON pd.host_id = h.id
|
LEFT JOIN host_patch_data pd ON pd.host_id = h.id
|
||||||
|
|||||||
@ -46,6 +46,16 @@ fn render_bar_chart(
|
|||||||
|
|
||||||
let mut pixel_buf = vec![0u8; (W * H * 3) as usize];
|
let mut pixel_buf = vec![0u8; (W * H * 3) as usize];
|
||||||
|
|
||||||
|
// Guard: skip rendering for empty or mismatched data
|
||||||
|
if labels.is_empty() || values.is_empty() || labels.len() != values.len() {
|
||||||
|
anyhow::bail!("cannot render bar chart with empty or mismatched data");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guard: reject NaN / infinity which would panic in plotters
|
||||||
|
if values.iter().any(|v| !v.is_finite()) {
|
||||||
|
anyhow::bail!("bar chart values contain non-finite numbers");
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let root = BitMapBackend::with_buffer(&mut pixel_buf, (W, H)).into_drawing_area();
|
let root = BitMapBackend::with_buffer(&mut pixel_buf, (W, H)).into_drawing_area();
|
||||||
root.fill(&WHITE)?;
|
root.fill(&WHITE)?;
|
||||||
@ -169,6 +179,24 @@ impl PdfBuilder {
|
|||||||
scale_x: f32,
|
scale_x: f32,
|
||||||
scale_y: f32,
|
scale_y: f32,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
// Validate dimensions and buffer size to prevent panics
|
||||||
|
let expected_len = (img_w as usize) * (img_h as usize) * 3;
|
||||||
|
if raw_rgb.len() != expected_len || img_w == 0 || img_h == 0 {
|
||||||
|
anyhow::bail!(
|
||||||
|
"image buffer size mismatch: expected {} bytes for {}x{} RGB, got {}",
|
||||||
|
expected_len,
|
||||||
|
img_w,
|
||||||
|
img_h,
|
||||||
|
raw_rgb.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !scale_x.is_finite() || !scale_y.is_finite() || scale_x <= 0.0 || scale_y <= 0.0 {
|
||||||
|
anyhow::bail!(
|
||||||
|
"invalid image scale factors: scale_x={}, scale_y={}",
|
||||||
|
scale_x,
|
||||||
|
scale_y
|
||||||
|
);
|
||||||
|
}
|
||||||
let xobj = ImageXObject {
|
let xobj = ImageXObject {
|
||||||
width: Px(img_w as usize),
|
width: Px(img_w as usize),
|
||||||
height: Px(img_h as usize),
|
height: Px(img_h as usize),
|
||||||
@ -252,9 +280,9 @@ async fn compliance_pdf(pool: &sqlx::PgPool, params: &ReportParams) -> anyhow::R
|
|||||||
SELECT h.display_name, h.fqdn,
|
SELECT h.display_name, h.fqdn,
|
||||||
COALESCE(jsonb_array_length(pd.installed_packages),0) AS total_packages,
|
COALESCE(jsonb_array_length(pd.installed_packages),0) AS total_packages,
|
||||||
COALESCE(pd.patch_count,0) AS pending_patches,
|
COALESCE(pd.patch_count,0) AS pending_patches,
|
||||||
CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages),0)=0 THEN 100.0
|
(CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages),0)=0 THEN 100.0
|
||||||
ELSE ROUND(CAST((1.0-pd.patch_count::float/NULLIF(jsonb_array_length(pd.installed_packages),0))*100 AS numeric),1)
|
ELSE ROUND(CAST((1.0-pd.patch_count::float/NULLIF(jsonb_array_length(pd.installed_packages),0))*100 AS numeric),1)
|
||||||
END AS compliance_pct,
|
END)::float8 AS compliance_pct,
|
||||||
h.health_status::text AS health_status
|
h.health_status::text AS health_status
|
||||||
FROM hosts h LEFT JOIN host_patch_data pd ON pd.host_id=h.id
|
FROM hosts h LEFT JOIN host_patch_data pd ON pd.host_id=h.id
|
||||||
WHERE h.id IN (SELECT host_id FROM host_groups WHERE group_id=$1)
|
WHERE h.id IN (SELECT host_id FROM host_groups WHERE group_id=$1)
|
||||||
@ -271,9 +299,9 @@ ORDER BY compliance_pct ASC",
|
|||||||
SELECT h.display_name, h.fqdn,
|
SELECT h.display_name, h.fqdn,
|
||||||
COALESCE(jsonb_array_length(pd.installed_packages),0) AS total_packages,
|
COALESCE(jsonb_array_length(pd.installed_packages),0) AS total_packages,
|
||||||
COALESCE(pd.patch_count,0) AS pending_patches,
|
COALESCE(pd.patch_count,0) AS pending_patches,
|
||||||
CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages),0)=0 THEN 100.0
|
(CASE WHEN COALESCE(jsonb_array_length(pd.installed_packages),0)=0 THEN 100.0
|
||||||
ELSE ROUND(CAST((1.0-pd.patch_count::float/NULLIF(jsonb_array_length(pd.installed_packages),0))*100 AS numeric),1)
|
ELSE ROUND(CAST((1.0-pd.patch_count::float/NULLIF(jsonb_array_length(pd.installed_packages),0))*100 AS numeric),1)
|
||||||
END AS compliance_pct,
|
END)::float8 AS compliance_pct,
|
||||||
h.health_status::text AS health_status
|
h.health_status::text AS health_status
|
||||||
FROM hosts h LEFT JOIN host_patch_data pd ON pd.host_id=h.id
|
FROM hosts h LEFT JOIN host_patch_data pd ON pd.host_id=h.id
|
||||||
GROUP BY h.id, h.health_status, pd.installed_packages, pd.patch_count
|
GROUP BY h.id, h.health_status, pd.installed_packages, pd.patch_count
|
||||||
|
|||||||
Reference in New Issue
Block a user