HTML Security Dashboard
The pompelmi dashboard generates a self-contained .html report from any scan
result — no external dependencies, no internet connection required. The file embeds all CSS
inline so you can share it, attach it to a Jira ticket, or archive it alongside an audit.
The report includes:
- Large summary statistics: total scanned, clean, infected, errors, scan time
- A colour-coded status banner (green for all clean, red for threats found)
- A table of every scanned file with a verdict badge and virus name
- An Infected Files section with highlighted virus names
- Scan metadata: timestamp, connection info, pompelmi version
- Dark mode via
prefers-color-scheme - Print-friendly CSS — looks great on paper too
CLI usage
Generate a report after a scan
Add --report to any pompelmi scan command:
npx pompelmi scan ./uploads --report
This saves pompelmi-report.html in the current directory after scanning
completes. Use --output to choose a different path:
npx pompelmi scan ./uploads --report --output security-report.html
With a running clamd
npx pompelmi scan ./uploads \ --host 127.0.0.1 \ --port 3310 \ --report \ --output /tmp/scan-$(date +%Y%m%d).html
--report and --json flags are independent — you can use both
at the same time to get machine-readable JSON output on stdout and an HTML file on
disk.
API usage
Import generateDashboard directly to build reports programmatically.
It accepts the same result shapes as scanDirectory() and the CLI scan loop.
From scanDirectory()
const { scanDirectory, generateDashboard } = require('pompelmi');
const results = await scanDirectory('/uploads');
const html = generateDashboard(results, {
elapsed: 1234, // ms — shown as scan time
host: 'localhost',
port: 3310,
outputPath: 'report.html', // writes to disk AND returns the string
});
console.log('Report saved — bytes:', html.length);
From a manual scan loop
const { scan, Verdict, generateDashboard } = require('pompelmi');
const files = ['./a.pdf', './b.exe', './c.txt'];
const start = Date.now();
const rows = await Promise.all(files.map(async (file) => {
const v = await scan(file, { host: 'localhost', port: 3310 });
return {
file,
verdict: v === Verdict.Clean ? 'clean'
: v === Verdict.Malicious ? 'infected'
: 'error',
viruses: [],
};
}));
generateDashboard(rows, {
elapsed: Date.now() - start,
outputPath: 'report.html',
});
generateDashboard(scanResults, options)
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
scanResults |
ScanRow[] | DirectoryScanResult |
Yes |
Array of {file, verdict, viruses?} objects from a manual scan, or the object
returned by scanDirectory() with {clean[], malicious[], errors[]}.
|
options.elapsed |
number | No | Total scan time in milliseconds. Shown in the stats card as "X.XXs". |
options.clamdVersion |
string | No | ClamAV version string to display in the metadata section. |
options.host |
string | No | clamd host used for the scan — shown in metadata. |
options.port |
number | No | clamd port used for the scan — shown in metadata. |
options.socket |
string | No | UNIX socket path used for the scan — shown in metadata. |
options.outputPath |
string | No |
If provided, the HTML is written to this path with fs.writeFileSync in addition
to being returned. Relative paths are resolved from the current working directory.
|
Return value
Returns the full HTML string regardless of whether outputPath was set.
You can write it wherever you want, embed it in a response, or compare it in tests.
TypeScript
import { generateDashboard, DashboardOptions, ScanRow } from 'pompelmi';
const rows: ScanRow[] = [
{ file: '/a.pdf', verdict: 'clean', viruses: [] },
{ file: '/b.exe', verdict: 'infected', viruses: ['Win.Malware.X'] },
];
const html: string = generateDashboard(rows, { outputPath: 'report.html' });
Examples
CI pipeline: save report as an artifact
- name: Scan uploads
run: |
npx pompelmi scan ./uploads \
--host localhost \
--port 3310 \
--report \
--output scan-report.html \
--share-card \
--output scan-card.svg
- name: Upload scan report
uses: actions/upload-artifact@v4
if: always()
with:
name: pompelmi-security-report
path: |
scan-report.html
scan-card.svg
Express route: return HTML report on demand
const express = require('express');
const { scanDirectory, generateDashboard } = require('pompelmi');
const app = express();
app.get('/admin/scan-report', async (req, res) => {
const results = await scanDirectory('/var/uploads');
const html = generateDashboard(results, { host: 'localhost', port: 3310 });
res.setHeader('Content-Type', 'text/html');
res.send(html);
});
Embed scan card in README
npx pompelmi scan ./uploads --share-card --output docs/scan-card.svg # In README.md: 