Skip to content

Instantly share code, notes, and snippets.

@manchumahara
Created August 27, 2026 09:49
Show Gist options
  • Select an option

  • Save manchumahara/02e5fcf7061675a9804a786e014cc26f to your computer and use it in GitHub Desktop.

Select an option

Save manchumahara/02e5fcf7061675a9804a786e014cc26f to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VPS Security Audit — Dossier</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{
--ink:#12141c;
--ink-2:#1b2030;
--paper:#f6f3ea;
--paper-2:#efe9da;
--line:#dad5c6;
--ink-soft:#4b5160;
--ink-faint:#8a8f9c;
--red:#c0272d;
--red-soft:#f5dfdd;
--amber:#b9791a;
--amber-soft:#f6e6cc;
--green:#2e6b45;
--green-soft:#dfe9de;
--gold:#a8842f;
--radius:2px;
font-size:62.5%;
}
*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
background:var(--ink);
background-image:
radial-gradient(circle at 20% 0%, rgba(168,132,47,0.08), transparent 45%),
radial-gradient(circle at 90% 100%, rgba(46,107,69,0.07), transparent 45%);
color:var(--ink-soft);
font-family:'Inter',sans-serif;
font-size:1.5rem;
line-height:1.5;
min-height:100vh;
}
h1,h2,h3{font-family:'Space Grotesk',sans-serif;margin:0;color:var(--ink);}
.mono{font-family:'JetBrains Mono',monospace;}
a{color:inherit;}
/* ===================== hidden svg filters ===================== */
.vad-defs{position:absolute;width:0;height:0;overflow:hidden;}
/* ===================== shell ===================== */
.vad-shell{max-width:1040px;margin:0 auto;padding:4rem 2rem 8rem;}
@media (max-width:640px){ .vad-shell{padding:2.4rem 1.4rem 6rem;} }
/* ===================== loader / intake screen ===================== */
.vad-intake{
background:var(--paper);
border:1px solid var(--line);
border-radius:var(--radius);
padding:5rem 4rem;
text-align:center;
box-shadow:0 30px 60px -20px rgba(0,0,0,0.5);
}
@media (max-width:640px){ .vad-intake{padding:3.6rem 2rem;} }
.vad-intake__eyebrow{
font-family:'JetBrains Mono',monospace;
font-size:1.1rem;
letter-spacing:0.18em;
text-transform:uppercase;
color:var(--gold);
margin-bottom:1.4rem;
}
.vad-intake__title{font-size:3rem;font-weight:700;margin-bottom:1rem;}
.vad-intake__sub{max-width:46rem;margin:0 auto 3.2rem;color:var(--ink-soft);font-size:1.5rem;}
.vad-drop{
border:2px dashed var(--line);
border-radius:var(--radius);
padding:3.6rem 2rem;
cursor:pointer;
transition:border-color .15s ease, background .15s ease;
background:rgba(255,255,255,0.35);
}
.vad-drop:hover, .vad-drop.is-drag{border-color:var(--gold); background:rgba(168,132,47,0.06);}
.vad-drop__icon{width:4.4rem;height:4.4rem;margin:0 auto 1.4rem;color:var(--ink-faint);}
.vad-drop__label{font-weight:600;color:var(--ink);font-size:1.6rem;margin-bottom:.4rem;}
.vad-drop__hint{font-size:1.3rem;color:var(--ink-faint);}
.vad-intake input[type=file]{display:none;}
.vad-intake__status{
margin-top:2rem;font-family:'JetBrains Mono',monospace;font-size:1.25rem;color:var(--ink-faint);
min-height:1.8em;
}
.vad-intake__status.is-error{color:var(--red);}
.vad-btn{
display:inline-block;margin-top:1.6rem;padding:1rem 2rem;
background:var(--ink);color:var(--paper);border:none;border-radius:var(--radius);
font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.3rem;letter-spacing:.02em;
cursor:pointer;transition:transform .1s ease, background .15s ease;
}
.vad-btn:hover{background:var(--ink-2);transform:translateY(-1px);}
.vad-btn--ghost{background:transparent;color:var(--ink-soft);border:1px solid var(--line);}
.vad-btn--ghost:hover{background:var(--paper-2);color:var(--ink);}
/* ===================== report ===================== */
.vad-report{display:none;}
.vad-report.is-active{display:block;}
.vad-intake.is-hidden{display:none;}
.vad-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:2.4rem;gap:1rem;flex-wrap:wrap;}
.vad-topbar__brand{font-family:'JetBrains Mono',monospace;font-size:1.15rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);}
.vad-topbar__brand strong{color:var(--gold);}
/* header dossier card */
.vad-header{
position:relative;
background:var(--paper);
border:1px solid var(--line);
border-radius:var(--radius);
padding:3.4rem;
display:flex;justify-content:space-between;align-items:center;gap:3rem;
overflow:hidden;
box-shadow:0 30px 60px -25px rgba(0,0,0,0.55);
}
@media (max-width:760px){ .vad-header{flex-direction:column;align-items:flex-start;padding:2.4rem;} }
.vad-header::before{
content:"";position:absolute;inset:8px;border:1px solid rgba(75,81,96,0.18);pointer-events:none;
}
.vad-header__meta{position:relative;z-index:1;}
.vad-header__file{font-family:'JetBrains Mono',monospace;font-size:1.15rem;color:var(--gold);letter-spacing:.08em;text-transform:uppercase;margin-bottom:1rem;}
.vad-header__host{font-size:3.2rem;font-weight:700;color:var(--ink);margin-bottom:.6rem;word-break:break-word;}
.vad-header__sub{font-family:'JetBrains Mono',monospace;font-size:1.3rem;color:var(--ink-soft);}
.vad-header__sub span{color:var(--ink-faint);}
.vad-header__scanned{margin-top:1.6rem;font-size:1.2rem;color:var(--ink-faint);}
/* stamp */
.vad-stamp{
position:relative;z-index:1;flex:none;
width:15rem;height:15rem;border-radius:50%;
display:flex;align-items:center;justify-content:center;
transform:rotate(-9deg);
filter:url(#vadRoughen);
}
.vad-stamp::before{
content:"";position:absolute;inset:0;border-radius:50%;
border:4px solid currentColor;
}
.vad-stamp::after{
content:"";position:absolute;inset:10px;border-radius:50%;
border:1.5px solid currentColor;
}
.vad-stamp__inner{position:relative;text-align:center;}
.vad-stamp__score{display:block;font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:3rem;line-height:1;}
.vad-stamp__label{display:block;font-family:'JetBrains Mono',monospace;font-size:1.05rem;letter-spacing:.14em;margin-top:.4rem;text-transform:uppercase;}
.vad-stamp--risk{color:var(--red);}
.vad-stamp--attention{color:var(--amber);}
.vad-stamp--secure{color:var(--green);}
/* summary strip */
.vad-strip{
display:grid;grid-template-columns:repeat(4,1fr);gap:1px;
background:var(--line);
border:1px solid var(--line);
border-radius:var(--radius);
overflow:hidden;
margin:2rem 0 3.6rem;
}
@media (max-width:640px){ .vad-strip{grid-template-columns:repeat(2,1fr);} }
.vad-strip__cell{background:var(--paper);padding:2rem 1.8rem;}
.vad-strip__num{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:2.6rem;color:var(--ink);}
.vad-strip__cell--fail .vad-strip__num{color:var(--red);}
.vad-strip__cell--warn .vad-strip__num{color:var(--amber);}
.vad-strip__cell--pass .vad-strip__num{color:var(--green);}
.vad-strip__cap{font-family:'JetBrains Mono',monospace;font-size:1.1rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint);margin-top:.4rem;}
/* section shell */
.vad-section{margin-bottom:3.6rem;}
.vad-section__head{display:flex;align-items:baseline;gap:1rem;margin-bottom:1.6rem;flex-wrap:wrap;}
.vad-section__title{font-size:1.9rem;font-weight:700;color:var(--paper);}
.vad-section__count{font-family:'JetBrains Mono',monospace;font-size:1.2rem;color:var(--ink-faint);}
.vad-section__rule{flex:1;height:1px;background:rgba(246,243,234,0.14);min-width:2rem;}
/* system spec grid */
.vad-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;}
@media (max-width:760px){ .vad-grid{grid-template-columns:repeat(2,1fr);} }
.vad-grid__cell{background:var(--paper);padding:1.8rem;}
.vad-grid__key{font-family:'JetBrains Mono',monospace;font-size:1.05rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:.5rem;}
.vad-grid__val{font-size:1.4rem;color:var(--ink);font-weight:600;word-break:break-word;}
.vad-grid__val.mono{font-size:1.3rem;font-weight:500;}
/* findings */
.vad-filters{display:flex;gap:.8rem;margin-bottom:1.6rem;flex-wrap:wrap;}
.vad-chip{
font-family:'JetBrains Mono',monospace;font-size:1.1rem;letter-spacing:.06em;text-transform:uppercase;
padding:.7rem 1.4rem;border-radius:100px;border:1px solid rgba(246,243,234,0.25);
background:transparent;color:var(--paper);cursor:pointer;transition:all .12s ease;
}
.vad-chip.is-active{background:var(--paper);color:var(--ink);border-color:var(--paper);}
.vad-chip:hover:not(.is-active){border-color:var(--gold);color:var(--gold);}
.vad-findings{display:flex;flex-direction:column;gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;}
.vad-finding{background:var(--paper);padding:1.6rem 1.8rem;display:flex;gap:1.6rem;align-items:flex-start;}
@media (max-width:600px){ .vad-finding{flex-direction:column;gap:.8rem;} }
.vad-badge{
flex:none;font-family:'JetBrains Mono',monospace;font-weight:700;font-size:1.05rem;letter-spacing:.08em;
padding:.45rem 1rem;border-radius:2px;transform:rotate(-3deg);
border:2px solid currentColor;
}
.vad-badge--fail{color:var(--red);}
.vad-badge--warn{color:var(--amber);}
.vad-badge--pass{color:var(--green);}
.vad-finding__body{flex:1;min-width:0;}
.vad-finding__title{font-weight:700;color:var(--ink);font-size:1.5rem;margin-bottom:.3rem;}
.vad-finding__desc{color:var(--ink-soft);font-size:1.35rem;}
/* ports */
.vad-portsmeta{font-family:'JetBrains Mono',monospace;font-size:1.2rem;color:var(--ink-faint);margin-bottom:1.4rem;}
.vad-ports{display:flex;flex-wrap:wrap;gap:.8rem;}
.vad-port{
font-family:'JetBrains Mono',monospace;font-size:1.25rem;font-weight:700;
padding:.8rem 1.2rem;border-radius:2px;background:var(--paper);border:1px solid var(--line);
display:flex;flex-direction:column;align-items:flex-start;min-width:6.4rem;
}
.vad-port__num{color:var(--ink);}
.vad-port__tag{font-size:.95rem;font-weight:500;letter-spacing:.04em;text-transform:uppercase;margin-top:.2rem;}
.vad-port--risk{border-color:var(--red);} .vad-port--risk .vad-port__tag{color:var(--red);}
.vad-port--caution{border-color:var(--amber);} .vad-port--caution .vad-port__tag{color:var(--amber);}
.vad-port--ok{border-color:var(--green);} .vad-port--ok .vad-port__tag{color:var(--green);}
.vad-legend{display:flex;gap:1.8rem;margin-top:1.6rem;flex-wrap:wrap;font-family:'JetBrains Mono',monospace;font-size:1.1rem;color:var(--ink-faint);}
.vad-legend span{display:inline-flex;align-items:center;gap:.6rem;}
.vad-legend i{width:1rem;height:1rem;border-radius:2px;display:inline-block;}
/* footer */
.vad-footer{
margin-top:4rem;padding-top:2.4rem;border-top:1px solid rgba(246,243,234,0.14);
display:flex;justify-content:space-between;align-items:center;gap:1.4rem;flex-wrap:wrap;
font-family:'JetBrains Mono',monospace;font-size:1.15rem;color:var(--ink-faint);
}
.vad-footer__note{max-width:60rem;font-family:'Inter',sans-serif;font-size:1.3rem;line-height:1.6;}
@media (prefers-reduced-motion: reduce){
.vad-btn,.vad-drop,.vad-chip{transition:none;}
}
</style>
</head>
<body>
<svg class="vad-defs" aria-hidden="true">
<filter id="vadRoughen" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7" result="noise"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="3.2"/>
</filter>
</svg>
<div class="vad-shell">
<!-- ============== INTAKE SCREEN ============== -->
<section class="vad-intake" id="vadIntake">
<div class="vad-intake__eyebrow">File No. — VPS/SEC/AUDIT</div>
<h1 class="vad-intake__title">Load a VPS security audit</h1>
<p class="vad-intake__sub">Drop the raw <span class="mono">vps-audit-report.txt</span> output here, or browse for a file. If a file named <span class="mono">vps-audit-report.txt</span> sits next to this page and it's served over HTTP, it loads automatically.</p>
<label class="vad-drop" id="vadDrop" for="vadFileInput">
<svg class="vad-drop__icon" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.6">
<path d="M24 6v24m0 0-8-8m8 8 8-8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 32v6a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4v-6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div class="vad-drop__label">Drop report .txt here, or click to browse</div>
<div class="vad-drop__hint">Parsed entirely in your browser — nothing is uploaded anywhere</div>
</label>
<input type="file" id="vadFileInput" accept=".txt,text/plain">
<div><button class="vad-btn vad-btn--ghost" id="vadRetryDefault" type="button">Look for vps-audit-report.txt again</button></div>
<div class="vad-intake__status" id="vadStatus">Checking for vps-audit-report.txt in this folder…</div>
</section>
<!-- ============== REPORT SCREEN ============== -->
<section class="vad-report" id="vadReport">
<div class="vad-topbar">
<div class="vad-topbar__brand">VPS Security Audit <strong>Dossier</strong></div>
<button class="vad-btn vad-btn--ghost" id="vadLoadAnother" type="button">Load a different report</button>
</div>
<div class="vad-header">
<div class="vad-header__meta">
<div class="vad-header__file">Classification — Security Audit</div>
<div class="vad-header__host" id="hHost">—</div>
<div class="vad-header__sub"><span>OS</span> <span id="hOs" class="mono">—</span> &nbsp;·&nbsp; <span>IP</span> <span id="hIp" class="mono">—</span></div>
<div class="vad-header__scanned mono" id="hScanned">—</div>
</div>
<div class="vad-stamp" id="hStamp">
<div class="vad-stamp__inner">
<span class="vad-stamp__score" id="hScore">--</span>
<span class="vad-stamp__label" id="hVerdict">—</span>
</div>
</div>
</div>
<div class="vad-strip">
<div class="vad-strip__cell vad-strip__cell--fail"><div class="vad-strip__num" id="cFail">0</div><div class="vad-strip__cap">Failed checks</div></div>
<div class="vad-strip__cell vad-strip__cell--warn"><div class="vad-strip__num" id="cWarn">0</div><div class="vad-strip__cap">Warnings</div></div>
<div class="vad-strip__cell vad-strip__cell--pass"><div class="vad-strip__num" id="cPass">0</div><div class="vad-strip__cap">Passed checks</div></div>
<div class="vad-strip__cell"><div class="vad-strip__num" id="cTotal">0</div><div class="vad-strip__cap">Total checks</div></div>
</div>
<div class="vad-section">
<div class="vad-section__head"><h2 class="vad-section__title">System spec sheet</h2><div class="vad-section__rule"></div></div>
<div class="vad-grid" id="sysGrid"></div>
</div>
<div class="vad-section">
<div class="vad-section__head">
<h2 class="vad-section__title">Findings</h2>
<span class="vad-section__count" id="findCount"></span>
<div class="vad-section__rule"></div>
</div>
<div class="vad-filters" id="vadFilters">
<button class="vad-chip is-active" data-filter="all" type="button">All</button>
<button class="vad-chip" data-filter="fail" type="button">Fail</button>
<button class="vad-chip" data-filter="warn" type="button">Warn</button>
<button class="vad-chip" data-filter="pass" type="button">Pass</button>
</div>
<div class="vad-findings" id="findingsList"></div>
</div>
<div class="vad-section" id="portsSection">
<div class="vad-section__head"><h2 class="vad-section__title">Port exposure</h2><div class="vad-section__rule"></div></div>
<div class="vad-portsmeta" id="portsMeta"></div>
<div class="vad-ports" id="portsList"></div>
<div class="vad-legend">
<span><i style="background:var(--green)"></i>Expected / low risk</span>
<span><i style="background:var(--amber)"></i>Harden or verify</span>
<span><i style="background:var(--red)"></i>Publicly exposed — investigate</span>
</div>
</div>
<div class="vad-footer">
<div class="vad-footer__note" id="footNote">Review every failed check first, then warnings. Passed checks need no action.</div>
<div id="footTool">—</div>
</div>
</section>
</div>
<script>
(function(){
"use strict";
var RISK_PORTS = {
"3306":"MySQL","5432":"PostgreSQL","6379":"Redis","11211":"Memcached","27017":"MongoDB",
"5672":"RabbitMQ","15672":"RabbitMQ UI","25672":"RabbitMQ cluster","4369":"EPMD",
"9000":"App/console","8080":"HTTP-alt","3001":"App","7000":"App","7500":"App",
"8025":"Mail UI","1025":"SMTP test","631":"CUPS printing"
};
var CAUTION_PORTS = { "22":"SSH", "53":"DNS" };
var OK_PORTS = { "80":"HTTP", "443":"HTTPS", "51820":"WireGuard VPN" };
function classifyPort(p){
if (RISK_PORTS[p]) return { level:"risk", tag:RISK_PORTS[p] };
if (CAUTION_PORTS[p]) return { level:"caution", tag:CAUTION_PORTS[p] };
if (OK_PORTS[p]) return { level:"ok", tag:OK_PORTS[p] };
return { level:"caution", tag:"Unknown" };
}
function escapeHtml(s){
return String(s).replace(/[&<>"']/g, function(c){
return { "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;" }[c];
});
}
function parseReport(text){
var lines = text.replace(/\r/g, "").split("\n");
var data = {
tool:"", url:"", scannedAt:"",
system:{}, findings:[], ports:[], portsTotal:null, portsPublic:null,
notes:[]
};
var section = "head"; // head | system | security | summary
var findRe = /^\[(PASS|WARN|FAIL)\]\s*([^-]+?)\s*-\s*(.*)$/;
var kvRe = /^([A-Za-z][A-Za-z0-9 /()]+?):\s*(.+)$/;
var portsRe = /Total:\s*(\d+),\s*Public:\s*(\d+)\s*accessible ports\)?:?\s*([\d,\s]+)/i;
for (var i = 0; i < lines.length; i++){
var raw = lines[i];
var line = raw.trim();
if (!line) continue;
if (/^=+$/.test(line)) continue;
if (/^security audit results$/i.test(line)) { section = "security"; continue; }
if (/^system information summary$/i.test(line)) { section = "summary"; continue; }
if (/^system information$/i.test(line)) { section = "system"; continue; }
if (/^end of vps audit report$/i.test(line)) { section = "end"; continue; }
if (section === "head"){
if (/^https?:\/\//i.test(line)) { data.url = line; continue; }
var startedMatch = line.match(/^Starting audit at (.+)$/i);
if (startedMatch) { data.scannedAt = startedMatch[1]; continue; }
if (!data.tool && /audit tool/i.test(line)) { data.tool = line; continue; }
if (!data.tool) { data.tool = line; continue; }
continue;
}
var fm = line.match(findRe);
if (fm){
data.findings.push({ status: fm[1], title: fm[2].trim(), desc: fm[3].trim() });
if (/port security/i.test(fm[2])){
var pm = fm[3].match(portsRe);
if (pm){
data.portsTotal = pm[1];
data.portsPublic = pm[2];
data.ports = pm[3].split(",").map(function(p){ return p.trim(); }).filter(Boolean);
}
}
continue;
}
if (section === "end"){
data.notes.push(line);
continue;
}
var kv = line.match(kvRe);
if (kv && (section === "system" || section === "summary" || section === "security")){
var key = kv[1].trim();
var val = kv[2].trim();
if (section === "security"){
data.notes.push(key + ": " + val);
} else {
data.system[key] = val;
}
continue;
}
}
return data;
}
function scoreReport(findings){
var counts = { PASS:0, WARN:0, FAIL:0 };
findings.forEach(function(f){ counts[f.status] = (counts[f.status]||0) + 1; });
var total = findings.length || 1;
var weighted = counts.PASS * 1 + counts.WARN * 0.5 + counts.FAIL * 0;
var score = Math.round((weighted / total) * 100);
var verdict, tone;
if (score >= 85) { verdict = "Secure"; tone = "secure"; }
else if (score >= 55) { verdict = "Needs attention"; tone = "attention"; }
else { verdict = "At risk"; tone = "risk"; }
return { counts: counts, total: findings.length, score: score, verdict: verdict, tone: tone };
}
function renderReport(data){
var summary = scoreReport(data.findings);
document.getElementById("hHost").textContent = data.system["Hostname"] || "Unknown host";
document.getElementById("hOs").textContent = data.system["Operating System"] || "—";
document.getElementById("hIp").textContent = data.system["Public IP"] || "—";
document.getElementById("hScanned").textContent = data.scannedAt ? ("Scanned " + data.scannedAt) : "";
var stampEl = document.getElementById("hStamp");
stampEl.className = "vad-stamp vad-stamp--" + summary.tone;
document.getElementById("hScore").textContent = summary.score;
document.getElementById("hVerdict").textContent = summary.verdict;
document.getElementById("cFail").textContent = summary.counts.FAIL || 0;
document.getElementById("cWarn").textContent = summary.counts.WARN || 0;
document.getElementById("cPass").textContent = summary.counts.PASS || 0;
document.getElementById("cTotal").textContent = summary.total;
// system spec grid — curated key order, then whatever else exists
var order = ["Hostname","Operating System","Kernel Version","Uptime","CPU Model","CPU Cores","Total Memory","Total Disk Space","Public IP","Load Average"];
var grid = document.getElementById("sysGrid");
grid.innerHTML = "";
var shown = {};
order.concat(Object.keys(data.system)).forEach(function(key){
if (shown[key] || !data.system[key]) return;
shown[key] = true;
var cell = document.createElement("div");
cell.className = "vad-grid__cell";
cell.innerHTML = '<div class="vad-grid__key">' + escapeHtml(key) + '</div>' +
'<div class="vad-grid__val mono">' + escapeHtml(data.system[key]) + '</div>';
grid.appendChild(cell);
});
// findings
document.getElementById("findCount").textContent = summary.total + " checks";
var list = document.getElementById("findingsList");
var order2 = { FAIL:0, WARN:1, PASS:2 };
var sorted = data.findings.slice().sort(function(a,b){ return order2[a.status]-order2[b.status]; });
function paint(filter){
list.innerHTML = "";
sorted.filter(function(f){ return filter === "all" || f.status.toLowerCase() === filter; })
.forEach(function(f){
var row = document.createElement("div");
row.className = "vad-finding";
row.innerHTML =
'<span class="vad-badge vad-badge--' + f.status.toLowerCase() + '">' + f.status + '</span>' +
'<div class="vad-finding__body">' +
'<div class="vad-finding__title">' + escapeHtml(f.title) + '</div>' +
'<div class="vad-finding__desc">' + escapeHtml(f.desc) + '</div>' +
'</div>';
list.appendChild(row);
});
}
paint("all");
Array.prototype.forEach.call(document.querySelectorAll(".vad-chip"), function(chip){
chip.addEventListener("click", function(){
Array.prototype.forEach.call(document.querySelectorAll(".vad-chip"), function(c){ c.classList.remove("is-active"); });
chip.classList.add("is-active");
paint(chip.getAttribute("data-filter"));
});
});
// ports
var portsSection = document.getElementById("portsSection");
if (data.ports.length){
portsSection.style.display = "";
document.getElementById("portsMeta").textContent =
(data.portsTotal ? data.portsTotal + " open ports total" : data.ports.length + " open ports") +
(data.portsPublic ? " · " + data.portsPublic + " reachable from the public internet" : "");
var pl = document.getElementById("portsList");
pl.innerHTML = "";
data.ports.forEach(function(p){
var c = classifyPort(p);
var el = document.createElement("div");
el.className = "vad-port vad-port--" + c.level;
el.innerHTML = '<span class="vad-port__num">' + p + '</span><span class="vad-port__tag">' + c.tag + '</span>';
pl.appendChild(el);
});
} else {
portsSection.style.display = "none";
}
document.getElementById("footTool").textContent = data.tool || "";
if (data.notes.length){
document.getElementById("footNote").textContent = data.notes[data.notes.length-1];
}
document.getElementById("vadIntake").classList.add("is-hidden");
document.getElementById("vadReport").classList.add("is-active");
}
function loadText(text){
try {
var data = parseReport(text);
if (!data.findings.length){
setStatus("That file didn't look like a VPS audit report — no [PASS]/[WARN]/[FAIL] lines found.", true);
return;
}
renderReport(data);
} catch (e){
setStatus("Could not parse that file: " + e.message, true);
}
}
function setStatus(msg, isError){
var el = document.getElementById("vadStatus");
el.textContent = msg;
el.classList.toggle("is-error", !!isError);
}
function tryLoadDefault(){
setStatus("Checking for vps-audit-report.txt in this folder…", false);
fetch("vps-audit-report.txt", { cache: "no-store" })
.then(function(res){
if (!res.ok) throw new Error("not found");
return res.text();
})
.then(function(text){ loadText(text); })
.catch(function(){
setStatus("No vps-audit-report.txt found here (or the page was opened directly as a file — browsers block that lookup over file://). Drop a file below instead.", false);
});
}
// file input / drag & drop
var fileInput = document.getElementById("vadFileInput");
var drop = document.getElementById("vadDrop");
fileInput.addEventListener("change", function(e){
var file = e.target.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(ev){ loadText(ev.target.result); };
reader.readAsText(file);
});
["dragenter","dragover"].forEach(function(evt){
drop.addEventListener(evt, function(e){ e.preventDefault(); drop.classList.add("is-drag"); });
});
["dragleave","drop"].forEach(function(evt){
drop.addEventListener(evt, function(e){ e.preventDefault(); drop.classList.remove("is-drag"); });
});
drop.addEventListener("drop", function(e){
var file = e.dataTransfer.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function(ev){ loadText(ev.target.result); };
reader.readAsText(file);
});
document.getElementById("vadRetryDefault").addEventListener("click", tryLoadDefault);
document.getElementById("vadLoadAnother").addEventListener("click", function(){
document.getElementById("vadReport").classList.remove("is-active");
document.getElementById("vadIntake").classList.remove("is-hidden");
tryLoadDefault();
});
tryLoadDefault();
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment