Skip to content

Commit

Permalink
# This is a combination of 4 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

report: concept for html report feature (needs design)

# The commit message #2 will be skipped:

# report: move rendering to html-template, consolidate logic, and add project name to default writeout

# The commit message #3 will be skipped:

# report: add color highlight for RISK score, center RISK text

# The commit message #4 will be skipped:

# logic, html: check for file extension, html clean-up
  • Loading branch information
mster committed Aug 29, 2019
1 parent 83c2a81 commit 560cc0e
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 33 deletions.
5 changes: 4 additions & 1 deletion commands/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
} = require('../lib/report/util')
const longReport = require('../lib/report/long')
const shortReport = require('../lib/report/short')
const htmlReport = require('../lib/report/html')
const { helpHeader } = require('../lib/help')
const {
COLORS,
Expand All @@ -30,7 +31,8 @@ module.exports.optionsList = optionsList

async function report (argv, _dir) {
const {
long
long,
html
} = argv
let { dir = _dir } = argv
if (!dir) dir = process.cwd()
Expand Down Expand Up @@ -162,6 +164,7 @@ async function report (argv, _dir) {

if (!long) shortReport(pkgScores, whitelisted, dir, argv)
if (long) longReport(pkgScores, whitelisted, dir, argv)
if (html) htmlReport(pkgScores, whitelisted, dir, html, argv)
if (hasFailures) process.exitCode = 1
}

Expand Down
217 changes: 217 additions & 0 deletions lib/report/html-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
'use strict'

const {
filterVulns,
SEVERITY_RMAP
} = require('./util')

module.exports = renderHTML

function renderHTML (title, summary, report, whitelist) {
const { riskCount, securityCount, insecureModules, complianceCount } = summary

let alternate = false
let whitelistInfo = ''
for (const pkg of whitelist) {
whitelistInfo += segment(pkg)
}

alternate = false
let pkgInfo = ''
for (const pkg of report) {
pkgInfo += segment(pkg)
}

function segment (pkg) {
const { name, version, maxSeverity, failures, license } = pkg
const pkgVulns = filterVulns(failures).map((v, i) => v !== 0
? `<p class="${['light1', 'yellow', 'orange', 'red'][i]}">
${v}&nbsp;${['Low', 'Medium', 'High', 'Critical'][i]}
</p>`
: ''
)
const pkgLicense = license && license.data && license.data.spdx ? license.data.spdx : 'UNKNOWN'
const pkgLicensePass = license && license.pass === true
const pkgSeverity = maxSeverity === 0
? `<p>${SEVERITY_RMAP[maxSeverity]}</p>`
: `<p class="${['light1', 'yellow', 'orange', 'red'][maxSeverity - 1]}">
${SEVERITY_RMAP[maxSeverity]}
</p>`

alternate = !alternate
return `
<tr class="module-element" ${alternate ? 'style="background:#2e3535"' : ''}>
<td>
<p><span class="white">${name}</span>@${version}</p>
</td>
<td style="text-align:center;">
<p>${pkgSeverity}</p>
<td>
<p>
<span class="${pkgLicensePass ? 'green">✓' : 'red">X'}</span>&nbsp;${pkgLicense}
</p>
</td>
<td>
<p>
${pkgVulns.join('').length === 0
? 'None'
: pkgVulns.reverse().join(' ')}
</p>
</td>
</tr>
`
}

const template = `
<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<head>
<title>NCM Report > ${title}</title>
<style>
body {
padding: 2%;
color: #89a19d;
font-family: Helvetica;
font-weight: 200;
}
a {
color: #5ac878;
text-decoration: none;
}
p {
margin: 0;
}
td {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 10px;
padding-right: 10px;
}
table {
width: 100%;
}
.title {
font-size: 28pt;
color:#e1e7e6;
}
.subtitle {
font-size: 20pt;
color: #e1e7e6;
}
.module-element {
margin: 4px;
}
.bottom-20 {
margin-bottom: 20px;
}
.left-20 {
margin-left: 20px;
}
.white {
color: #e1e7e6;
}
.light1 {
color: #89a19d;
}
.yellow {
color: #ffb726;
}
.orange {
color: #ff8b40;
}
.red {
color: #ff6040;
}
.green {
color: #5ac878;
}
</style>
</head>
<body style="background: #202525">
<div id="ns-branding" class="bottom-20">
<svg xmlns="http://www.w3.org/2000/svg" height="50" width="auto" viewBox="0 0 100 100">
<path d="M89.243 20.864L55.642 1.5a11.21 11.21 0 0 0-11.2 0L10.778 20.937a11.208 11.208 0 0 0-5.6 9.7l-.032 38.781a11.2 11.2 0 0 0 5.611 9.718L44.359 98.5a11.208 11.208 0 0 0 11.2 0l33.663-19.437a11.206 11.206 0 0 0 5.6-9.7l.028-38.784a11.2 11.2 0 0 0-5.607-9.715zM46.36 4.823a7.338 7.338 0 0 1 1.723-.718v16.552a1.917 1.917 0 0 0 3.834 0V4.079a7.369 7.369 0 0 1 1.81.741l33.6 19.366a7.322 7.322 0 0 1 1.522 1.169L68.486 37.113a22.518 22.518 0 0 0-36.983.014L11.186 25.418a7.329 7.329 0 0 1 1.51-1.16zM68.713 50a18.74 18.74 0 0 1-16.8 18.616V51.107l15.149-8.746A18.579 18.579 0 0 1 68.713 50zm-35.785-7.626l15.155 8.734v17.508a18.679 18.679 0 0 1-15.155-26.242zM50 47.786l-15.149-8.729a18.651 18.651 0 0 1 30.288-.012zM12.672 75.813a7.284 7.284 0 0 1-1.522-1.168l13.117-7.574a1.918 1.918 0 0 0-1.918-3.321L9.232 71.323a7.316 7.316 0 0 1-.251-1.9l.029-38.786a7.274 7.274 0 0 1 .254-1.9l20.318 11.71a22.53 22.53 0 0 0 18.5 32.015v22.945a1.9 1.9 0 0 0 .083.53 7.351 7.351 0 0 1-1.892-.757zm40.969 19.364a7.362 7.362 0 0 1-1.8.741 1.919 1.919 0 0 0 .077-.511V72.461A22.533 22.533 0 0 0 70.411 40.43l20.357-11.754a7.329 7.329 0 0 1 .251 1.9l-.028 38.784a7.33 7.33 0 0 1-.264 1.937L77.651 63.75a1.918 1.918 0 0 0-1.918 3.321l13.053 7.536a7.3 7.3 0 0 1-1.481 1.135z" fill="#89a19d"></path>
</svg>
<svg height="30" width="auto" viewBox="0 0 346.17 33.7" style="display:inline-block;fill:#fff;">
<path d="M24.54,33.07a1.88,1.88,0,0,1-1.47-.71L3.75,7.9V31.2A1.88,1.88,0,1,1,0,31.2V2.5A1.88,1.88,0,0,1,3.35,1.34L22.67,25.8V2.5a1.88,1.88,0,0,1,3.75,0V31.2a1.88,1.88,0,0,1-1.87,1.88Z"></path><path d="M48.92,33.7c-8.81,0-16-7.56-16-16.85S40.11,0,48.92,0s16,7.56,16,16.85S57.72,33.7,48.92,33.7Zm0-29.95c-6.74,0-12.22,5.88-12.22,13.1s5.48,13.1,12.22,13.1,12.22-5.88,12.22-13.1S55.66,3.75,48.92,3.75Z"></path><path d="M177.24,33.7c-8.81,0-16-7.56-16-16.85S168.43,0,177.24,0s16,7.56,16,16.85S186,33.7,177.24,33.7Zm0-29.95C170.5,3.75,165,9.63,165,16.85s5.48,13.1,12.22,13.1,12.22-5.88,12.22-13.1S184,3.75,177.24,3.75Z"></path><path d="M234.8,33.07a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,234.8.62h13.31c5.67,0,9.78,3.79,9.78,9s-4.11,9-9.78,9H236.67V31.2A1.88,1.88,0,0,1,234.8,33.07Zm1.88-18.18h11.43c3.55,0,6-2.16,6-5.26s-2.48-5.26-6-5.26H236.67Z"></path><path d="M212.47,33.7A13.51,13.51,0,0,1,199.16,20V2.5a1.88,1.88,0,0,1,3.75,0V20A9.57,9.57,0,1,0,222,20V2.5a1.88,1.88,0,0,1,3.75,0V20A13.51,13.51,0,0,1,212.47,33.7Z"></path><path d="M152.72,6.59l-.18-.12c-.12-.08-.31-.21-.51-.33a18.63,18.63,0,0,0-1.91-1A14.31,14.31,0,0,0,147,4a13,13,0,0,0-4.25-.17,8.14,8.14,0,0,0-4.29,1.67A4.4,4.4,0,0,0,136.9,9.7a3.81,3.81,0,0,0,.3,1,3.68,3.68,0,0,0,.61,1A6.93,6.93,0,0,0,140,13.22a17.68,17.68,0,0,0,3,1c.54.14,1.09.26,1.64.37s1.15.23,1.82.41a22.48,22.48,0,0,1,3.7,1.25,13.74,13.74,0,0,1,3.49,2.14,8.41,8.41,0,0,1,2.52,3.66c.11.36.2.74.28,1.11s.09.75.1,1.12v.89l0,.17,0,.1-.06.42,0,.21-.07.29a8.86,8.86,0,0,1-5.21,6,15.75,15.75,0,0,1-6.39,1.31,12.44,12.44,0,0,1-1.47-.07,14.11,14.11,0,0,1-1.46-.22,17.07,17.07,0,0,1-2.49-.72,20.61,20.61,0,0,1-3.68-1.84,21.46,21.46,0,0,1-2.13-1.54c-.47-.39-.72-.62-.72-.62l0,0a1.69,1.69,0,0,1,2.26-2.5s.2.17.61.47a19.79,19.79,0,0,0,1.86,1.2,17.77,17.77,0,0,0,3.1,1.43,13.37,13.37,0,0,0,2,.54c.35.06.71.12,1,.14a10.25,10.25,0,0,0,1.15,0,12.07,12.07,0,0,0,4.86-1,6.41,6.41,0,0,0,2-1.38,3.17,3.17,0,0,0,.38-.45,2.58,2.58,0,0,0,.31-.48,4.25,4.25,0,0,0-1.06-5.41,14,14,0,0,0-5.61-2.57c-.48-.14-1.17-.26-1.67-.38s-1.23-.26-1.84-.42a21.32,21.32,0,0,1-3.68-1.27A10.58,10.58,0,0,1,135,14.17,7.53,7.53,0,0,1,133.17,10a3.77,3.77,0,0,1,0-.48l0-.42v-.6l0-.53c0-.35.11-.7.18-1s.19-.68.32-1A8.25,8.25,0,0,1,136,2.67a11.8,11.8,0,0,1,6.3-2.54,15.79,15.79,0,0,1,5.47.29,16.09,16.09,0,0,1,3.94,1.48A17.75,17.75,0,0,1,154,3.31l.71.53a1.69,1.69,0,0,1-2,2.75Z"></path><path d="M79.8,33.07H73.15a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,73.15.62H79.8c12.62,0,18.27,8.15,18.27,16.22C98.07,27.16,91.41,33.07,79.8,33.07ZM75,29.32H79.8c12,0,14.52-6.78,14.52-12.47,0-7.58-5.7-12.47-14.52-12.47H75Z"></path><path d="M125.54,33.07H106.37a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,106.37.62h19.17a1.87,1.87,0,1,1,0,3.75H108.24V29.32h17.29a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M120.39,18.66h-14a1.88,1.88,0,0,1,0-3.75h14a1.87,1.87,0,0,1,0,3.75Z"></path><path d="M319.58,33.07H300.41a1.88,1.88,0,0,1-1.87-1.88V2.5A1.88,1.88,0,0,1,300.41.62h19.17a1.87,1.87,0,1,1,0,3.75H302.29V29.32h17.29a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M314.44,18.66H300.8a1.88,1.88,0,0,1,0-3.75h13.63a1.88,1.88,0,0,1,0,3.75Z"></path><path d="M256.16,33.07a1.87,1.87,0,0,1-1.53-.79L244.5,17.93a1.88,1.88,0,0,1,3.06-2.16l10.13,14.35a1.88,1.88,0,0,1-1.53,3Z"></path><path d="M291.05,28.93l-.58.55a17.13,17.13,0,0,1-2,1.54,16.17,16.17,0,0,1-8.36,2.66h-.78l-.63,0a10.23,10.23,0,0,1-1.39-.13,16.51,16.51,0,0,1-3.05-.7,16.71,16.71,0,0,1-5.73-3.27,16.91,16.91,0,0,1-5.82-12.72l0-.91c0-.28,0-.53.05-.84.1-.63.17-1.3.31-1.9a16.8,16.8,0,0,1,1.18-3.45,17,17,0,0,1,4.24-5.62A16.71,16.71,0,0,1,274.28.86,17.45,17.45,0,0,1,280.12,0a16.27,16.27,0,0,1,8.38,2.66,17.06,17.06,0,0,1,2,1.52l.63.6a1.69,1.69,0,0,1-2.24,2.51l-.09-.07-.53-.45a14,14,0,0,0-1.61-1.11A13.58,13.58,0,0,0,280,3.76a14,14,0,0,0-4.57.66A13,13,0,0,0,271,7a13.24,13.24,0,0,0-3.3,4.37A13,13,0,0,0,266.8,14c-.11.46-.14.88-.23,1.32,0,.23,0,.53-.05.8l0,.72A13.16,13.16,0,0,0,271,26.74a12.9,12.9,0,0,0,6.71,3.06,7.27,7.27,0,0,0,1.19.11l.63,0H280A13.5,13.5,0,0,0,286.61,28a14.42,14.42,0,0,0,1.59-1.09l.59-.5h0a1.69,1.69,0,0,1,2.26,2.51Z"></path><path d="M337.88.2a8.29,8.29,0,1,0,8.29,8.29A8.3,8.3,0,0,0,337.88.2Zm0,15.12a6.83,6.83,0,1,1,6.83-6.83A6.84,6.84,0,0,1,337.88,15.32Z"></path><path d="M335.87,12.12a.61.61,0,0,1-.61-.61v-6a.61.61,0,0,1,.61-.61h2.81a2.12,2.12,0,1,1,0,4.23h-2.2v2.43A.61.61,0,0,1,335.87,12.12Zm.61-4.26h2.2a.91.91,0,1,0,0-1.79h-2.2Z"></path><path d="M340.38,12.12a.61.61,0,0,1-.5-.26l-2.13-3a.61.61,0,0,1,1-.7l2.13,3a.61.61,0,0,1-.5,1Z"></path>
</svg>
</div>
<div id="title" class="bottom-20">
<div class="title">
<p>
NCM&nbsp;Project&nbsp;Report
<span class="light1">&nbsp;>&nbsp;</span>
${title}
</p>
</div>
<div class="left-20">
<p>
<b class="green">></b>
Powered by
<a href="https://docs.nodesource.com/ncmv2/docs">NodeSource Certified Modules v2</a>
</p>
</div>
</div>
<div id="summary" class="bottom-20 left-20">
<span class="subtitle">Summary</span>
<p><b class="white">${report.length}</b> packages checked</p>
<br>
<p><span class="red"><b>${riskCount[4]}</b> Critical Risk</span></p>
<p><span class="orange"><b>${riskCount[3]}</b> High Risk</span></p>
<p><span class="yellow"><b>${riskCount[2]}</b> Medium Risk</span></p>
<p><span class-"light1"><b>${riskCount[1]}</b> Low Risk</span></p>
<br>
<p>
<b class="white">${securityCount}</b>
security vulnerabilities found
${(securityCount > 0
? `across <b class="white">${insecureModules}</b> modules`
: '')}
</p>
<p><b class="white">${complianceCount}</b> noncompliant modules found</p>
${(whitelist.length > 0
? `<p><b class="white">${whitelist.length}</b> used modules whitelisted</p>`
: '')}
</div>
${(whitelist.length > 0
? `
<div id="whitelist-list">
<span class="subtitle">Whitelisted Modules</span>
<hr>
<table id="whitelist-table" class="bottom-20">
<tr style="margin-bottom:5px;">
<th>Module Name</th>
<th>Risk</th>
<th>License</th>
<th>Security</th>
</tr>
${whitelistInfo}
</table>
</div>
`
: '')}
<div id="module-list">
<p class="subtitle">${(whitelist.length > 0 ? 'Non-Whitelisted ' : '')}Modules</p>
<hr>
<table id="module-table" class="bottom-20">
<tr style="margin-bottom:5px;">
<th>Module Name</th>
<th>Risk</th>
<th>License</th>
<th>Security</th>
</tr>
${pkgInfo}
</table>
</div>
</body>
</html>
`

return template
}
56 changes: 56 additions & 0 deletions lib/report/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use strict'

const path = require('path')
const { promisify } = require('util')
const writeFile = promisify(require('fs').writeFile)
const {
success,
formatError
} = require('../ncm-style')
const {
summaryInfo
} = require('./util')
const renderTemplate = require('./html-template')
const L = console.log

module.exports = htmlReport

async function htmlReport (report, whitelist, dir, output) {
/* Output may only use the `.html` file format */
if (output !== true && !(/^.*\.html$/.test(path.basename(output)))) {
L()
L(formatError('Invalid file extension to write the HTML report. Please use `*.html`.'))
L()
process.exitCode = 1
return
}

const title = `${path.basename(dir) || 'NCM'}`
const summary = summaryInfo(report) // { riskCount, insecureModules, complianceCount, securityCount }

const htmlData = renderTemplate(
title,
summary,
report,
whitelist
)

/*
No write location was specified.
Setting output location to current working directory with generated report name.
*/
if (output === true) output = path.join(process.cwd(), `${title.toLowerCase()}-report-${Date.now()}.html`)

/* Write report to file */
try {
await writeFile(output, htmlData)
L()
L(success(`Wrote HTML report to: ${output}`))
L()
} catch (error) {
L()
L(formatError(`Unable to write HTML report to: ${output}`, error))
L()
process.exitCode = 1
}
}
24 changes: 2 additions & 22 deletions lib/report/summary.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const {
tooltip
} = require('../ncm-style')
const {
SEVERITY_RMAP
summaryInfo
} = require('./util')
const L = console.log
const chalk = require('chalk')
Expand All @@ -19,27 +19,7 @@ function summary (report, dir, filterOptions) {
L(chalk`${report.length} {${COLORS.light1} packages checked}`)
L()

const riskCount = [0, 0, 0, 0, 0]
let insecureModules = 0
let complianceCount = 0
let securityCount = 0

for (const pkg of report) {
let insecure = false
let pkgMaxSeverity = 0
for (const score of pkg.scores) {
if (score.group === 'quality') continue
if (score.group === 'compliance' && !score.pass) complianceCount++
if (score.group === 'security' && !score.pass) {
securityCount++
insecure = true
}
const scoreIndex = SEVERITY_RMAP.indexOf(score.severity)
pkgMaxSeverity = scoreIndex > pkgMaxSeverity ? scoreIndex : pkgMaxSeverity
}
riskCount[pkgMaxSeverity]++
if (insecure) insecureModules++
}
const { riskCount, insecureModules, complianceCount, securityCount } = summaryInfo(report)

L(chalk` {${COLORS.red} ! ${riskCount[4]}} critical risk`)
L(chalk` {${COLORS.orange} ${riskCount[3]}} high risk`)
Expand Down
Loading

0 comments on commit 560cc0e

Please sign in to comment.