const INDEX_HTML: &str = "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>MS-SMB2 Test Dashboard</title>\n <style>\n :root {\n --bg: #0d1117;\n --panel: #161b22;\n --border: #30363d;\n --text: #e6edf3;\n --muted: #8b949e;\n --pass: #2ea043;\n --fail: #f85149;\n --skip: #d29922;\n --unknown: #6e7681;\n }\n\n * {\n box-sizing: border-box;\n }\n\n body {\n margin: 0;\n background: var(--bg);\n color: var(--text);\n font-family: -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;\n }\n\n header {\n padding: 20px 24px;\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: baseline;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n h1 {\n font-size: 18px;\n margin: 0;\n }\n\n .updated {\n color: var(--muted);\n font-size: 12px;\n }\n\n main {\n padding: 24px;\n max-width: 1100px;\n margin: 0 auto;\n }\n\n .cards {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 20px;\n }\n\n .card {\n background: var(--panel);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 14px 18px;\n min-width: 120px;\n }\n\n .card .n {\n font-size: 26px;\n font-weight: 700;\n }\n\n .card .l {\n color: var(--muted);\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: .04em;\n }\n\n .n.pass {\n color: var(--pass);\n }\n\n .n.fail {\n color: var(--fail);\n }\n\n .n.skip {\n color: var(--skip);\n }\n\n .n.unknown {\n color: var(--unknown);\n }\n\n .bar {\n height: 14px;\n border-radius: 7px;\n overflow: hidden;\n display: flex;\n background: var(--border);\n margin-bottom: 24px;\n }\n\n .bar>span {\n height: 100%;\n }\n\n .bar .p {\n background: var(--pass);\n }\n\n .bar .f {\n background: var(--fail);\n }\n\n .bar .s {\n background: var(--skip);\n }\n\n .bar .u {\n background: var(--unknown);\n }\n\n table {\n width: 100%;\n border-collapse: collapse;\n background: var(--panel);\n border: 1px solid var(--border);\n border-radius: 8px;\n overflow: hidden;\n }\n\n th,\n td {\n text-align: left;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n font-size: 13px;\n }\n\n th {\n color: var(--muted);\n font-weight: 600;\n cursor: pointer;\n user-select: none;\n }\n\n tr:last-child td {\n border-bottom: none;\n }\n\n td.note {\n color: var(--muted);\n font-size: 12px;\n max-width: 380px;\n white-space: normal;\n }\n\n .pill {\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n }\n\n .pill.pass {\n background: rgba(46, 160, 67, .15);\n color: var(--pass);\n }\n\n .pill.fail {\n background: rgba(248, 81, 73, .15);\n color: var(--fail);\n }\n\n .pill.skip {\n background: rgba(210, 153, 34, .15);\n color: var(--skip);\n }\n\n .pill.unknown {\n background: rgba(110, 118, 129, .2);\n color: var(--unknown);\n }\n\n .commit {\n color: #58a6ff;\n text-decoration: none;\n font-family: ui-monospace,\n SFMono-Regular, Menlo, monospace;\n font-size: 12px;\n }\n\n .commit:hover {\n text-decoration: underline;\n }\n\n .suites {\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n margin-bottom: 20px;\n }\n\n .suite {\n background: var(--panel);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 14px 16px;\n flex: 1 1 300px;\n max-width: 360px;\n }\n\n .suite h3 {\n margin: 0 0 6px;\n font-size: 14px;\n text-transform: capitalize;\n }\n\n .suite .desc {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n margin-bottom: 10px;\n }\n\n .suite .mini {\n color: var(--text);\n font-size: 12px;\n }\n\n .suite .sbar {\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n display: flex;\n background: var(--border);\n margin-top: 8px;\n }\n\n .suite .sbar>span {\n height: 100%;\n }\n\n .controls {\n display: flex;\n gap: 10px;\n margin-bottom: 12px;\n flex-wrap: wrap;\n }\n\n select,\n input {\n background: var(--panel);\n color: var(--text);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 6px 10px;\n font-size: 13px;\n }\n\n .cat-head {\n margin: 26px 0 8px;\n font-size: 14px;\n color: var(--muted);\n }\n\n .empty {\n color: var(--muted);\n padding: 40px;\n text-align: center;\n }\n </style>\n</head>\n\n<body>\n <header>\n <h1>MS-SMB2 Test Dashboard</h1>\n <span class=\"updated\" id=\"updated\">loading\u{2026}</span>\n </header>\n <main>\n <div class=\"controls\">\n <select id=\"suiteFilter\">\n <option value=\"\">All suites</option>\n </select>\n <select id=\"statusFilter\">\n <option value=\"\">All statuses</option>\n <option value=\"pass\">Passed</option>\n <option value=\"fail\">Failed</option>\n <option value=\"skip\">Skipped</option>\n <option value=\"unknown\">Unknown</option>\n </select>\n <select id=\"categoryFilter\">\n <option value=\"\">All categories</option>\n </select>\n <input id=\"search\" type=\"search\" placeholder=\"Filter by test name\u{2026}\" />\n </div>\n <div class=\"cards\" id=\"cards\"></div>\n <div class=\"bar\" id=\"bar\"></div>\n <div class=\"suites\" id=\"suites\"></div>\n <table>\n <thead>\n <tr>\n <th data-k=\"name\">Test</th>\n <th data-k=\"suite\">Suite</th>\n <th data-k=\"category\">Category</th>\n <th data-k=\"status\">Status</th>\n <th data-k=\"duration_ms\">ms</th>\n <th data-k=\"commit\">Passed @</th>\n <th data-k=\"note\">Note</th>\n </tr>\n </thead>\n <tbody id=\"rows\"></tbody>\n </table>\n <div class=\"empty\" id=\"empty\" hidden>No results yet. Run the suite to populate the CSV.</div>\n </main>\n <script>\n let TESTS = [];\n let LAST_JSON = \"\";\n let sortKey = \"status\", sortDir = 1;\n\n const SUITE_INFO = {\n protocol: \"Microsoft\'s official MS-SMB2 Server Test Suite (Windows Protocol \" +\n \"Test Suites). The .NET SMB2 SDK drives rustsmb over the wire and asserts \" +\n \"conformance to the [MS-SMB2] specification.\",\n unit: \"In-process Rust unit & integration tests (cargo test --workspace) \u{2014} \" +\n \"codecs, wire (de)serialization, state machines and per-crate logic.\",\n system: \"End-to-end interop tests: the Python smbprotocol client talks to a \" +\n \"live rustsmb server via smb-testrunner to validate real-world behavior.\",\n };\n const REPO = \"https://github.com/farazshaikh/smb-server-rs\";\n\n async function refresh() {\n try {\n // Rebuild the DOM only when the data changed, so idle 5s polls\n // don\'t wipe the user\'s text selection.\n const text = await fetch(\"/api/tests\").then(r => r.text());\n if (text !== LAST_JSON) {\n LAST_JSON = text;\n TESTS = JSON.parse(text);\n render();\n }\n document.getElementById(\"updated\").textContent =\n \"updated \" + new Date().toLocaleTimeString() + \" \u{b7} \" + TESTS.length + \" tests\";\n } catch (e) {\n document.getElementById(\"updated\").textContent = \"error contacting API\";\n }\n }\n\n // Rows in scope for the summary panels: everything except the status filter, so\n // the cards/bar reflect the selected suite/category/search.\n function scopedRows() {\n const suite = document.getElementById(\"suiteFilter\").value;\n const cat = document.getElementById(\"categoryFilter\").value;\n const q = document.getElementById(\"search\").value.toLowerCase();\n return TESTS.filter(t =>\n (!suite || t.suite === suite) &&\n (!cat || t.category === cat) &&\n (!q || t.name.toLowerCase().includes(q)));\n }\n\n function summarize(rows) {\n const s = { total: rows.length, passed: 0, failed: 0, skipped: 0, unknown: 0 };\n for (const r of rows) {\n if (r.status === \"pass\") s.passed++;\n else if (r.status === \"fail\") s.failed++;\n else if (r.status === \"skip\") s.skipped++;\n else s.unknown++;\n }\n return s;\n }\n\n // Group any row set into [{key, passed, failed, skipped, unknown, total}].\n function groupBy(rows, field) {\n const map = new Map();\n for (const r of rows) {\n const g = map.get(r[field]) || { key: r[field], passed: 0, failed: 0, skipped: 0, unknown: 0, total: 0 };\n g[({ pass: \"passed\", fail: \"failed\", skip: \"skipped\" })[r.status] || \"unknown\"]++;\n g.total++;\n map.set(r[field], g);\n }\n return [...map.values()].sort((a, b) => a.key < b.key ? -1 : 1);\n }\n\n function render() {\n const scoped = scopedRows();\n renderCards(summarize(scoped));\n renderBar(summarize(scoped));\n renderSuites(groupBy(TESTS, \"suite\"));\n fillSuites(groupBy(TESTS, \"suite\"));\n fillCategories(groupBy(scopeBySuite(), \"category\"));\n renderRows();\n }\n\n // Rows in the currently selected suite (or all), for the category dropdown.\n function scopeBySuite() {\n const suite = document.getElementById(\"suiteFilter\").value;\n return suite ? TESTS.filter(t => t.suite === suite) : TESTS;\n }\n\n function renderCards(s) {\n const rate = s.total ? ((s.passed / s.total) * 100).toFixed(1) : \"0.0\";\n document.getElementById(\"cards\").innerHTML = `\n <div class=\"card\"><div class=\"n\">${s.total}</div><div class=\"l\">Total</div></div>\n <div class=\"card\"><div class=\"n pass\">${s.passed}</div><div class=\"l\">Passed</div></div>\n <div class=\"card\"><div class=\"n fail\">${s.failed}</div><div class=\"l\">Failed</div></div>\n <div class=\"card\"><div class=\"n skip\">${s.skipped}</div><div class=\"l\">Skipped</div></div>\n <div class=\"card\"><div class=\"n unknown\">${s.unknown}</div><div class=\"l\">Unknown</div></div>\n <div class=\"card\"><div class=\"n\">${rate}%</div><div class=\"l\">Pass rate</div></div>`;\n }\n\n function renderBar(s) {\n const t = s.total || 1;\n document.getElementById(\"bar\").innerHTML =\n `<span class=\"p\" style=\"width:${(s.passed / t) * 100}%\"></span>` +\n `<span class=\"f\" style=\"width:${(s.failed / t) * 100}%\"></span>` +\n `<span class=\"s\" style=\"width:${(s.skipped / t) * 100}%\"></span>` +\n `<span class=\"u\" style=\"width:${(s.unknown / t) * 100}%\"></span>`;\n }\n\n function renderSuites(suites) {\n const selected = document.getElementById(\"suiteFilter\").value;\n document.getElementById(\"suites\").innerHTML = suites.map(c => {\n const t = c.total || 1;\n const desc = SUITE_INFO[c.key] || \"\";\n const active = (!selected || selected === c.key) ? \"\" : \' style=\"opacity:.45\"\';\n return `<div class=\"suite\"${active}>\n <h3>${escapeHtml(c.key)}</h3>\n <div class=\"desc\">${escapeHtml(desc)}</div>\n <div class=\"mini\">${c.passed}/${c.total} passed \u{b7} ${c.failed} failed` +\n `${c.unknown ? \" \u{b7} \" + c.unknown + \" unknown\" : \"\"}</div>\n <div class=\"sbar\">\n <span class=\"p\" style=\"width:${(c.passed / t) * 100}%;background:var(--pass)\"></span>\n <span class=\"f\" style=\"width:${(c.failed / t) * 100}%;background:var(--fail)\"></span>\n <span class=\"s\" style=\"width:${(c.skipped / t) * 100}%;background:var(--skip)\"></span>\n <span class=\"u\" style=\"width:${(c.unknown / t) * 100}%;background:var(--unknown)\"></span>\n </div></div>`;\n }).join(\"\");\n }\n\n function fillSuites(suites) {\n const sel = document.getElementById(\"suiteFilter\");\n const current = sel.value;\n sel.innerHTML = \'<option value=\"\">All suites</option>\' +\n suites.map(c => `<option value=\"${c.key}\">${c.key} (${c.passed}/${c.total})</option>`).join(\"\");\n sel.value = current;\n }\n\n function fillCategories(categories) {\n const sel = document.getElementById(\"categoryFilter\");\n const current = sel.value;\n sel.innerHTML = \'<option value=\"\">All categories</option>\' +\n categories.map(c => `<option value=\"${c.key}\">${c.key} ` +\n `(${c.passed}/${c.total})</option>`).join(\"\");\n sel.value = current;\n }\n\n function renderRows() {\n const suite = document.getElementById(\"suiteFilter\").value;\n const status = document.getElementById(\"statusFilter\").value;\n const cat = document.getElementById(\"categoryFilter\").value;\n const q = document.getElementById(\"search\").value.toLowerCase();\n let rows = TESTS.filter(t =>\n (!suite || t.suite === suite) &&\n (!status || t.status === status) &&\n (!cat || t.category === cat) &&\n (!q || t.name.toLowerCase().includes(q)));\n\n rows.sort((a, b) => {\n const av = a[sortKey], bv = b[sortKey];\n return (av < bv ? -1 : av > bv ? 1 : 0) * sortDir;\n });\n\n document.getElementById(\"empty\").hidden = rows.length > 0;\n document.getElementById(\"rows\").innerHTML = rows.map(t => `\n <tr>\n <td>${escapeHtml(t.name)}</td>\n <td>${escapeHtml(t.suite)}</td>\n <td>${escapeHtml(t.category)}</td>\n <td><span class=\"pill ${t.status}\">${t.status}</span></td>\n <td>${t.duration_ms}</td>\n <td>${commitCell(t.commit)}</td>\n <td class=\"note\">${escapeHtml(t.note || \"\")}</td>\n </tr>`).join(\"\");\n }\n\n function commitCell(commit) {\n if (!commit) return \"\";\n return `<a class=\"commit\" href=\"${REPO}/commit/${encodeURIComponent(commit)}\" ` +\n `target=\"_blank\" rel=\"noopener\">${escapeHtml(commit)}</a>`;\n }\n\n function escapeHtml(s) {\n return s.replace(/[&<>\"\']/g, c =>\n ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", \'\"\': \""\", \"\'\": \"'\" }[c]));\n }\n\n document.querySelectorAll(\"th[data-k]\").forEach(th =>\n th.addEventListener(\"click\", () => {\n const k = th.dataset.k;\n sortDir = (sortKey === k) ? -sortDir : 1;\n sortKey = k;\n renderRows();\n }));\n [\"suiteFilter\", \"statusFilter\", \"categoryFilter\", \"search\"].forEach(id =>\n document.getElementById(id).addEventListener(\"input\", render));\n\n refresh();\n setInterval(refresh, 5000);\n </script>\n</body>\n\n</html>";Expand description
The single-page dashboard, embedded so the binary is self-contained.