diff --git a/community_images/common/templates/image_readme.j2 b/community_images/common/templates/image_readme.j2
index e42176ea68..b85771227d 100644
--- a/community_images/common/templates/image_readme.j2
+++ b/community_images/common/templates/image_readme.j2
@@ -1,18 +1,20 @@
-
+
+
[![rf-h][rf-h-badge]][rf-view-report-button]
[![DH Image][dh-rf-badge]][rf-dh-image-link]
[![Slack][slack-badge]][slack-link]
[![FOSSA Status][fossa-badge]][fossa-link]
-Near Zero CVE images available at hub.rapidfort.com/repositories .
+
+
+
+
-
-As of 7/2024 community-images will be gated. Please register for free at www.rapidfort.com to access these images
# RapidFort hardened image for {{ official_name }}
@@ -24,26 +26,33 @@ RapidFort has optimized and hardened this {{ official_name }} container image. T
This optimized image is functionally equivalent to [{{- source_image_provider }} {{ official_name -}}][source-image-repo-link] image but more secure with a significantly smaller software attack surface.
-Every day, RapidFort automatically optimizes and hardens a growing bank of Docker Hub’s most important container images. Check out our [entire library](https://hub.docker.com/u/rapidfort) of secured container images.
-
-[Get the full report here or click on the image below][rf-view-report-link]
+
+
+
+
+
+
+
+
-[![Metrics][metrics-link]][rf-image-metrics-link]
- Vulnerabilities: Original vs. Hardened
-
-
-
-[![CVE Reduction][cve-reduction-link]][rf-image-cve-reduction-link]
+[![Savings][savings-link]][rf-image-savings-link]
-
+
+Every day, RapidFort automatically optimizes and hardens a growing bank of Docker Hub’s most important container images.
+
+Check out our [entire library of secured container images.](https://hub.docker.com/u/rapidfort)
+
+
+[Get the full report here or click on the image below][rf-view-report-link]
+
## What is {{ official_name }}?
> {{ what_is_text }}
@@ -69,7 +78,7 @@ docker run -e RF_ACCESS_TOKEN="your_access_token" image_name
The runtime instructions for this hardened container image are the same as the official release. Follow the instructions provided with the [{{- source_image_provider }} {{ official_name -}}][source-image-repo-link].
-
+
@@ -89,7 +98,7 @@ RapidFort is the pioneering Software Attack Surface Management (SASM) platform i
Vulnerability reports for RapidFort's hardened images are updated daily to include newly discovered vulnerabilities and fixes.
-
+
@@ -123,7 +132,7 @@ If you find this project useful, please star this repo just like many [amazing p
## Have questions?
-[![RapidFort](https://raw.githubusercontent.com/rapidfort/community-images/main/contrib/github_logo_footer.png)][rf-rapidfort-footer-logo-link]
+[![RapidFort](https://raw.githubusercontent.com/rapidfort/community-images/main/contrib/logo_light.svg)][rf-rapidfort-footer-logo-link]
Learn more about RapidFort's pioneering Software Attack Surface Management platform at [RapidFort.com][rf-link].
@@ -142,9 +151,13 @@ Learn more about RapidFort's pioneering Software Attack Surface Management platf
[rf-rapidfort-footer-logo-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=rapidfort_footer_logo
[rf-view-report-button]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=view_report_button
[rf-view-report-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=view_report_link
+
[rf-image-metrics-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=image_metrics_link
[rf-image-cve-reduction-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=image_cve_reduction_link
+[rf-image-savings-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=image_savings_link
+[rf-image-vulns-link]: {{ report_url -}}?utm_source=github&utm_medium=ci_view_report&utm_campaign=sep_01_sprint&utm_term={{- name -}}&utm_content=vulns_link
+
[dh-img-size-badge]: https://img.shields.io/docker/image-size/ {{- rf_docker_link -}} ?logo=docker&logoColor=white&sort=semver
[dh-img-pulls-badge]: https://img.shields.io/docker/pulls/ {{- rf_docker_link -}} ?logo=docker&logoColor=white
@@ -152,8 +165,8 @@ Learn more about RapidFort's pioneering Software Attack Surface Management platf
[slack-link]: https://join.slack.com/t/rapidfortcommunity/shared_invite/zt-1g3wy28lv-DaeGexTQ5IjfpbmYW7Rm_Q
[rf-h-badge]: https://img.shields.io/static/v1?label=RapidFort&labelColor=333F48&message=hardened&color=50B4C4&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACcAAAAkCAYAAAAKNyObAAAACXBIWXMAACE4AAAhOAFFljFgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAHvSURBVHgB7ZjvTcMwEMUvEgNkhNuAjOAR2IAyQbsB2YAyQbsBYoKwQdjA3aAjHA514Xq1Hf9r6QeeFKVJ3tkv+cWOVYCAiKg124b82gZqe0+NNlsHJbLBxthg1o+RASetIEdTJxnBRvtUMCHgM6TIBtMZwY7SiQFfrhUsN+Ao/TJYR3WC5QY88/Nge6oXLBRwO+P/GcnNMZzZteBR0zQfogM0O4Q47Uz9TtSrUIHs71+paugw16Dn+qt5xJ/TD4viEcrE25tepaXPaHxP350GXtD10WwHQWjQxKhl7YUGRg/MuPaY9vxuzPFA+RpEW9rj0yCMbcCsmG9B+Xpk7YRo4RnjQEEttBiBtAefyI23BtoYpBrmRO6ZX0EZWo60c1yfaGBMOKRzdKVocYZO/NpuMss7E9cHitcc0gFS5Qig2LUUtCGkmmJwOsJJvLlokdWtfMFzAvLGctCOooYPtg2USoRQ7HwM2hXzIzuvKQenIxzHm4oWmZ9TKF1AnAR8sI2moB093nKcjoBvtnHFzoXQ8qeMDGcLtUW/i4NYtJ3jJhRcSnRYHMSg1Q5PD5cWHT4/ih0vIpDOf9QrhZtQLsWxlILT8AjXEol/iQRaiVTBX4pO57D6U0WJBFoFtyaLtuqLfwf19G62e7hFWbQKKuoLYovGDo9dW28AAAAASUVORK5CYII=
-[metrics-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/metrics.svg
-[cve-reduction-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/cve_reduction.svg
[source-image-repo-link]: {{ source_image_repo_link }}
[rf-dh-image-link]: https://hub.docker.com/r/{{- rf_docker_link }}
+
+[savings-link]: https://github.com/rapidfort/community-images/raw/main/community_images/ {{- github_location -}} /assets/savings.svg
diff --git a/contrib/full_report.svg b/contrib/full_report.svg
new file mode 100644
index 0000000000..258461aee1
--- /dev/null
+++ b/contrib/full_report.svg
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/logo_light.svg b/contrib/logo_light.svg
new file mode 100644
index 0000000000..7af307a9ec
--- /dev/null
+++ b/contrib/logo_light.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/view_details.svg b/contrib/view_details.svg
new file mode 100644
index 0000000000..8c80b0ee65
--- /dev/null
+++ b/contrib/view_details.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/view_dockerhub.svg b/contrib/view_dockerhub.svg
new file mode 100644
index 0000000000..d8efd93178
--- /dev/null
+++ b/contrib/view_dockerhub.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/view_github.svg b/contrib/view_github.svg
new file mode 100644
index 0000000000..67f9c737c9
--- /dev/null
+++ b/contrib/view_github.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/zero_cve_images_link.svg b/contrib/zero_cve_images_link.svg
new file mode 100644
index 0000000000..655fc26f18
--- /dev/null
+++ b/contrib/zero_cve_images_link.svg
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/report_shots/package.json b/report_shots/package.json
index f71f8a572b..0464bbdf0f 100644
--- a/report_shots/package.json
+++ b/report_shots/package.json
@@ -4,7 +4,8 @@
"js-yaml": "^4.1.0",
"sharp": "^0.33.5",
"svgdom": "^0.1.19",
- "svgson": "^5.3.1"
+ "svgson": "^5.3.1",
+ "xmldom": "^0.6.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
diff --git a/report_shots/shots.js b/report_shots/shots.js
index 7a34a9b033..3812b80d44 100644
--- a/report_shots/shots.js
+++ b/report_shots/shots.js
@@ -2,7 +2,7 @@ const util = require('util');
const fsPromise = require('fs/promises');
const yaml = require('js-yaml')
const fs = require('fs');
-const { parseJSON, parseCSVFormatV2, formatBytes } = require('./utils');
+const { parseJSON, parseCSVFormatV2, formatBytes, capitalizeFirstLetter, formatSizeString } = require('./utils');
const { convertVulnsData, vulnsColorScheme } = require('./vulnsParser');
const sharp = require('sharp');
const svgson = require('svgson');
@@ -66,20 +66,55 @@ const generateCharts = async (imageName, platform, imageSavePath) => {
const {vulnsSeverityCount: vulnsHardenedSummary, hardenedVulnsFlags, } = convertVulnsData(vulnsHardened, true, true);
const {vulnsSeverityCount: vulnsOriginalSummary} = convertVulnsData(vulns, true, false, hardenedVulnsFlags);
+ Object.keys(vulnsOriginalSummary.default).forEach((severity) => {
+ vulnsOriginalSummary.default[severity] = vulnsOriginalSummary.default[severity] * 100000;
+ });
+
// generate SVGs
- const vulnsSavingsChartSVG = await generateSavingsChart('Vulnerabilities', imageInfo.noVulns, imageInfo.noVulnsHardened, false);
- const packagesSavingsChartSVG = await generateSavingsChart('Packages', imageInfo.noPkgs, imageInfo.noPkgsHardened, false);
- const sizeSavingsChartSVG = await generateSavingsChart('Attack surface', imageInfo.origImageSize, imageInfo.hardenedImageSize, true);
- const contextualSeverityChart = await generateContextualSeverityChart(vulnsOriginalSummary)
- const vulnsBySeverityChart = await generateVulnsBySeverityChart(vulnsOriginalSummary.default, vulnsHardenedSummary.default);
+ // const vulnsSavingsChartSVG = await generateSavingsChart('Vulnerabilities', imageInfo.noVulns, imageInfo.noVulnsHardened, false);
+ // const packagesSavingsChartSVG = await generateSavingsChart('Packages', imageInfo.noPkgs, imageInfo.noPkgsHardened, false);
+ // const sizeSavingsChartSVG = await generateSavingsChart('Attack surface', imageInfo.origImageSize, imageInfo.hardenedImageSize, true);
+ // const contextualSeverityChart = await generateContextualSeverityChart(vulnsOriginalSummary)
+ // const vulnsBySeverityChart = await generateVulnsBySeverityChart(vulnsOriginalSummary.default, vulnsHardenedSummary.default);
+ const {width, svg:vulnsCountChartSVG} = await generateVulnsCountChart(vulnsOriginalSummary.default);
+ const vulnsOriginalHardenedChartSVG = await generateVulnsOriginalHardenedChart(width, vulnsOriginalSummary.default, vulnsHardenedSummary.default);
+
+ saveSVGToFile(vulnsCountChartSVG, util.format('%s/vulns_count_chart.svg', imageSavePath));
+ saveSVGToFile(vulnsOriginalHardenedChartSVG, util.format('%s/original_vs_hardened_vulns_chart.svg', imageSavePath));
+ const savingsSVG = await generateSavingsCardsCompound([
+ {
+ type:'vulns',
+ title:'Vulnerabilities',
+ original: imageInfo.noVulns,
+ hardened:imageInfo.noVulnsHardened,
+ isSize:false,
+ },
+ {
+ type:'packages',
+ title:'Packages',
+ original: imageInfo.noPkgs,
+ hardened:imageInfo.noPkgsHardened,
+ isSize:false,
+ },
+ {
+ type:'size',
+ title:'Size',
+ original: imageInfo.origImageSize,
+ hardened:imageInfo.hardenedImageSize,
+ isSize:true,
+ }
+ ]);
+ saveSVGToFile(savingsSVG, util.format('%s/savings.svg', imageSavePath));
+
// save individual charts as svg
// saveSVGToFile(vulnsSavingsChartSVG, util.format('%s/savings_chart_vulns.svg', imageSavePath));
// saveSVGToFile(packagesSavingsChartSVG, util.format('%s/savings_chart_pkgs.svg', imageSavePath));
// saveSVGToFile(sizeSavingsChartSVG, util.format('%s/savings_chart_size.svg', imageSavePath));
// saveSVGToFile(contextualSeverityChart, util.format('%s/contextual_severity_chart.svg', imageSavePath));
// saveSVGToFile(vulnsBySeverityChart, util.format('%s/vulns_by_severity_histogram.svg', imageSavePath));
- generateReportViews(vulnsSavingsChartSVG, packagesSavingsChartSVG, sizeSavingsChartSVG, contextualSeverityChart, vulnsBySeverityChart, imageSavePath);
+ // generateReportViews(vulnsSavingsChartSVG, packagesSavingsChartSVG, sizeSavingsChartSVG, contextualSeverityChart, vulnsBySeverityChart, imageSavePath);
+
} catch (error) {
console.error(error);
}
@@ -213,22 +248,45 @@ const generateReportViews = async (
};
+
+
async function readSVGTemplate (filename) {
// reading svg template
return await fsPromise.readFile(`./${filename}.svg`, { encoding: 'utf8' });
}
// helper function for initiating svg modifying
-async function prepareSVG (filename) {
+async function prepareSVG(filename) {
const { SVG, registerWindow } = await import('@svgdotjs/svg.js');
const { createSVGWindow } = await import('svgdom');
const svgTemplate = await readSVGTemplate(filename);
+
+ // Extract attributes from the tag in the template
+ const svgTagMatch = svgTemplate.match(/]+)>/);
+ const attributes = {};
+ if (svgTagMatch) {
+ const attrString = svgTagMatch[1];
+ attrString.replace(/(\w+)=["']([^"']*)["']/g, (match, name, value) => {
+ attributes[name] = value;
+ });
+ }
+
+ // Remove the outer tags from the template content
+ const innerContent = svgTemplate.replace(/]*>|<\/svg>/g, '');
+
+ // Set up a window and document
const window = createSVGWindow();
const document = window.document;
registerWindow(window, document);
+
+ // Initialize SVG and add only the inner content
const draw = SVG(document.documentElement);
- draw.svg(svgTemplate);
- return draw
+ draw.svg(innerContent); // Add only the inner content to `draw`
+
+ // Apply extracted attributes to the new element
+ Object.keys(attributes).forEach(attr => draw.attr(attr, attributes[attr]));
+
+ return draw;
}
// helper function for updating text in tspan of text element
@@ -465,6 +523,207 @@ async function generateContextualSeverityChart(vulnsOriginalSummary) {
// return svg as string
return draw.svg();
}
+/**
+ * Draws a chart in an SVG based on the provided data.
+ *
+ * @param {Object} draw - SVG.js object.
+ * @param {string} groupId - ID of the group where the chart will be added.
+ * @param {number} maxWidth - Maximum width of the chart.
+ * @param {Object} vulnsCount - Object with vulnerability counts by severity.
+ * @param {number} total - Total value for filling the chart to 100%.
+ */
+function drawVulnsChart(draw, groupId, maxWidth, vulnsCount, total) {
+ const severities = ['critical', 'high', 'medium', 'low', 'unknown'];
+
+ const minWidth = 5;
+ const spacing = 2;
+
+ // Calculate initial widths for each severity level
+ let initialWidths = severities.map(severity => ({
+ severity,
+ color: vulnsColorScheme[severity],
+ value: vulnsCount[severity],
+ width: (vulnsCount[severity] / total) * (maxWidth - spacing * (severities.length - 1))
+ }));
+
+ // Adjust widths to meet minimum width requirements and calculate the excess
+ let totalExcess = 0;
+ initialWidths.forEach(item => {
+ if (item.width < minWidth) {
+ totalExcess += minWidth - item.width;
+ item.width = minWidth;
+ }
+ });
+
+
+ // Proportionally distribute the remaining width
+ let remainingRects = initialWidths.filter(item => item.width > minWidth);
+ let remainingWidth = maxWidth - initialWidths.reduce((acc, item) => acc + item.width, 0) - (spacing * (severities.length - 1));
+
+ if (remainingWidth > 0) {
+ remainingRects.forEach(item => {
+ item.width += (item.width / remainingRects.reduce((acc, r) => acc + r.width, 0)) * remainingWidth;
+ });
+ }
+
+ // Find the group by ID and clear any existing elements
+ const group = draw.findOne(`#${groupId}`);
+ group.clear();
+
+ // Draw rectangles
+ let currentX = 0;
+ initialWidths.forEach(({ color, width }) => {
+ group.rect(width, 8)
+ .attr({ x: currentX, y: 0, fill: color });
+ currentX += width + spacing;
+ });
+}
+
+
+/**
+ *
+ * @param {*} vulnsCount
+ * @returns
+ */
+async function generateVulnsCountChart(vulnsCount) {
+ // Preparing data for chart
+ const severities = ['critical', 'high', 'medium', 'low', 'unknown'];
+ const draw = await prepareSVG('template_vulns_count');
+
+ const margin = 10;
+ const initialSize = 92;
+ const maxLegendWidth = severities.reduce((prev, severity)=> {
+ // Calculate width of reduction_value text for setting it centered
+ const reductionText = draw.text(`${capitalizeFirstLetter(severity)}: ${vulnsCount[severity]}`).font({ size: 12, family: 'InterMedium' });
+ const reductionWidth = reductionText.bbox().width;
+ reductionText.remove(); // remove text after measure
+ return Math.max(reductionWidth + 14 + margin, prev)
+ }, 0)
+
+ const offsetMultiplier = [0, 1, 2, 0, 1]
+ severities.forEach((severity, index) => {
+ updateText(draw, `#text_${severity}`, vulnsCount[severity]);
+ const legendGroup = draw.findOne(`#legend_${severity}`);
+ legendGroup.attr('transform', `translate(${(maxLegendWidth - initialSize) * offsetMultiplier[index]}, 0)`)
+ })
+ const initialContentWidth = 316;
+ // Prepare SVG
+ const contentWidth = Math.max(maxLegendWidth * 3, 316);
+ draw.findOne("#vulns_count_mask").attr('width', contentWidth)
+ draw.findOne("#external-link").attr('transform', `translate(${contentWidth - initialContentWidth}, 0)`)
+ draw.findOne("#vulns_count_mask_rect").attr('width', contentWidth)
+ console.log('contentWidth', contentWidth)
+ drawVulnsChart(draw, 'vulns_count_view', contentWidth, vulnsCount, vulnsCount.total);
+ // Update the total count text
+ updateText(draw, '#vulns_total', vulnsCount.total);
+
+ draw.width(contentWidth + 48);
+ const currentViewBox = draw.attr('viewBox') || '0 0 100 100';
+ const [x, y, , originalHeight] = currentViewBox.split(' ').map(Number);
+ const newWidth = contentWidth + 48;
+ const updatedViewBox = `${x} ${y} ${newWidth} ${originalHeight}`;
+ draw.attr('viewBox', updatedViewBox);
+
+ // return svg as string
+ return {width:newWidth, svg: draw.svg()}
+}
+
+
+/**
+ *
+ * @param {*} vulnsCount
+ * @returns
+ */
+async function generateVulnsOriginalHardenedChart(cardWidth, original, hardened) {
+ // Preparing data for chart
+ const severities = ['critical', 'high', 'medium', 'low', 'unknown'];
+ const data = {
+ original: original,
+ hardened: hardened
+ }
+ const draw = await prepareSVG('template_original_vs_hardened');
+ const originalValueText = draw.text(`${original.total}`).font({ size: 14, family: 'InterMedium' });
+ const originalValueTextWidth = originalValueText.bbox().width;
+ originalValueText.remove(); // remove text after measure
+ const initialContentWidth = 365;
+ const overflowWidth = parseInt(cardWidth) - (337 + originalValueTextWidth);
+ console.log('originalValueTextWidth', originalValueTextWidth)
+ console.log('overflowWidth', overflowWidth)
+ console.log('cardWidth', cardWidth)
+ draw.findOne("#external-link").attr('transform', `translate(${cardWidth - initialContentWidth}, 0)`)
+ draw.findOne(`#original_value_tspan`).attr('x', cardWidth - originalValueTextWidth - 24)
+ draw.findOne(`#original_mask`).attr('width', cardWidth - originalValueTextWidth - 24 - 10 - 106)
+ draw.findOne(`#original_mask_rect`).attr('width', cardWidth - originalValueTextWidth - 24 - 10 - 106)
+
+ Object.entries(data).forEach(([key, vulnsCount]) => {
+ if (original.total === 0) {
+ return
+ }
+ let contentWidth = cardWidth - originalValueTextWidth - 24 - 10 - 106;
+ if (key === 'hardened') {
+ const minWidth = severities.reduce((prev, severity) => {
+ return prev + (vulnsCount[severity] > 0 ? 7 : 0)
+ }, 0)
+ contentWidth = Math.max(vulnsCount.total / original.total * 197, minWidth);
+ draw.findOne(`#hardened_mask`).attr('width', contentWidth)
+ draw.findOne(`#hardened_mask_rect`).attr('width', contentWidth)
+ draw.findOne(`#hardened_value_tspan`).attr('x', 106 + contentWidth + 10)
+ }
+ drawVulnsChart(draw, `${key}_count_view`, contentWidth, vulnsCount, vulnsCount.total);
+ updateText(draw, `#${key}_value`, vulnsCount.total);
+ })
+
+ draw.width(cardWidth);
+ const currentViewBox = draw.attr('viewBox');
+ const [x, y, , originalHeight] = currentViewBox.split(' ').map(Number);
+ const updatedViewBox = `${x} ${y} ${cardWidth} ${originalHeight}`;
+ draw.attr('viewBox', updatedViewBox);
+
+ return draw.svg()
+}
+
+// Generating chart savings chart (vulns, packages, size)
+/**
+ *
+ * @param {String} title chart title
+ * @param {Number} original original value
+ * @param {Number} hardened hardened value
+ * @param {Boolean} isSize if size then need to show values with metrics suffix
+ * @returns
+ */
+async function generateSavingsCardsCompound(savingsData) {
+ const draw = await prepareSVG('template_savings_view')
+ savingsData.forEach(async ({type, title, original, hardened, isSize}) => {
+ const percentage = (1 - (hardened / original)) * 100;
+ if (isSize) {
+ original = formatSizeString(formatBytes(original, 2));
+ const unit = original.split(' ')[1];
+ hardened = formatSizeString(formatBytes(hardened, 2, unit));
+ }
+
+ // update text fields in svg
+ updateText(draw, `#${type}_title`, title);
+ updateText(draw, `#${type}_original_value`, original.toString());
+ updateText(draw, `#${type}_hardened_value`, hardened.toString());
+ updateText(draw, `#${type}_percentage`, `${Math.round(-percentage)}%`);
+
+ // calculate dash array and dash offset for progress line in svg
+ const calculateDasharray = (r) => Math.PI * r * 2;
+ const calculateDashoffset = (percentageShown, circumference) => ((100 - percentageShown) / 100) * circumference;
+ const dashArray = calculateDasharray(33.5);
+ const dashOffset = calculateDashoffset(100 - percentage, dashArray);
+ // set value for circle as progress bar
+ const progressCircle = draw.findOne(`#${type}_progress`);
+
+ progressCircle.attr({
+ 'stroke-dasharray': dashArray,
+ 'stroke-dashoffset': dashOffset,
+ });
+
+ // return back SVG as string
+ });
+ return draw.svg();
+}
async function main() {
const imgListPath = process.argv[2]
@@ -474,7 +733,6 @@ async function main() {
const imgListArray = imgList.split("\n");
for await (const imagePath of imgListArray) {
- console.log("image name=", imagePath);
try {
let imageYmlPath = fs.realpathSync(util.format('../community_images/%s/image.yml', imagePath));
diff --git a/report_shots/template_original_vs_hardened.svg b/report_shots/template_original_vs_hardened.svg
new file mode 100644
index 0000000000..3b6ead3f6e
--- /dev/null
+++ b/report_shots/template_original_vs_hardened.svg
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+ Original vs. this image
+
+
+ This image
+ 24
+
+
+
+
+
+
+
+
+ Original
+ 699
+
+
+
+
+
+
+
+
+
diff --git a/report_shots/template_savings_chart.svg b/report_shots/template_savings_chart.svg
index 1416271f2d..8a6893c513 100644
--- a/report_shots/template_savings_chart.svg
+++ b/report_shots/template_savings_chart.svg
@@ -8,6 +8,7 @@
}
#progress {
transform: rotate(-90deg);
+ transform-origin: 272.425px 69.988px;
}
@font-face {
font-family: 'InterMedium';
diff --git a/report_shots/template_savings_view.svg b/report_shots/template_savings_view.svg
new file mode 100644
index 0000000000..86feecad50
--- /dev/null
+++ b/report_shots/template_savings_view.svg
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+ Savings
+
+
+
+
+
+
+Vulnerabilities
+
+
+-78%
+This image
+Original
+174
+699
+
+
+Packages
+
+
+-78%
+This image
+Original
+174
+699
+
+
+Size
+
+
+-78%
+This image
+Original
+174
+699
+
+
+
+
diff --git a/report_shots/template_vulns_count.svg b/report_shots/template_vulns_count.svg
new file mode 100644
index 0000000000..261ee7bb39
--- /dev/null
+++ b/report_shots/template_vulns_count.svg
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+Vulnerabilities
+
+
+
+
+
+
+ 699
+
+
+
+
+
+
+
+
+
+
+
+
+
+Critical:
+6
+
+
+
+
+
+
+
+High:
+57
+
+
+
+
+
+
+
+Medium:
+102
+
+
+
+
+
+
+
+Low:
+529
+
+
+
+
+
+
+
+Unknown:
+5
+
+
+
+
+
+
diff --git a/report_shots/utils.js b/report_shots/utils.js
index 4aa2226e2f..b65faa2fde 100644
--- a/report_shots/utils.js
+++ b/report_shots/utils.js
@@ -67,9 +67,34 @@ const parseCSVFormatV2 = ({fields, data}, topLevel) => {
}
return result
}
+
+const capitalizeFirstLetter = (string) => {
+ if (!string) {
+ return string
+ }
+ return string.charAt(0).toUpperCase() + string.slice(1);
+}
+
+function formatSizeString(sizeString) {
+ const [size, unit] = sizeString.split(' ');
+ let number = parseFloat(size);
+
+ // Determine the number of significant digits and format accordingly
+ const digitCount = Math.floor(Math.log10(number)) + 1;
+
+ // Format based on the number of digits
+ const formattedSize = digitCount >= 3
+ ? Math.floor(number) // Show as an integer if 3 or more digits
+ : parseFloat(number.toPrecision(3)); // Show up to 3 significant digits
+
+ // Return the formatted size with the unit (no space before the unit)
+ return `${formattedSize}${unit}`;
+}
module.exports = {
parseJSON,
parseCSVFormatV2,
- formatBytes
+ formatBytes,
+ capitalizeFirstLetter,
+ formatSizeString
};