Skip to main content

INDEX_HTML

Constant INDEX_HTML 

Source
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                ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", \'\"\': \"&quot;\", \"\'\": \"&#39;\" }[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.