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
The --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' });

Share card (SVG)

generateShareCard produces a compact SVG card showing the scan summary. It is suitable for embedding in a GitHub README, attaching to a Slack message, or posting on social media.

CLI

npx pompelmi scan ./uploads --share-card

Saves pompelmi-scan-card.svg in the current directory. Custom path:

npx pompelmi scan ./uploads --share-card --output scan-result.svg

API

const { generateShareCard } = require('pompelmi');

const svg = generateShareCard(results, {
  outputPath: 'pompelmi-scan-card.svg',
});

// Embed in README:
// ![scan result](pompelmi-scan-card.svg)

Output

The SVG card is 560 × 200 px and includes:

  • pompelmi grapefruit logo and branding
  • Headline: "X files scanned — All clean" or "X files scanned — Y infected"
  • Stat row: total / clean / infected counts
  • Scan date and pompelmi version in the footer
  • Green theme for clean scans, red theme for infected scans

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:
![Scan result](docs/scan-card.svg)