Skip to content

Commit e527129

Browse files
MDA2AVclaude
andcommitted
Add scrollbar styling, language filtering, and fix test glossary links
- Style horizontal scrollbar on category tables with custom CSS (WebKit + Firefox, dark mode aware) - Add language field to all 12 probe.json files and thread through CI pipeline - Add language filter buttons on summary and all category pages - Fix test name links to point to correct glossary doc pages instead of broken /glossary/#test- URLs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b58e969 commit e527129

18 files changed

Lines changed: 223 additions & 25 deletions

File tree

.github/workflows/probe.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ jobs:
3232
for f in src/Servers/*/probe.json; do
3333
dir=$(basename "$(dirname "$f")")
3434
name=$(jq -r .name "$f")
35-
SERVERS=$(echo "$SERVERS" | jq -c --arg d "$dir" --arg n "$name" '. + [{"dir": $d, "name": $n}]')
35+
lang=$(jq -r '.language // ""' "$f")
36+
SERVERS=$(echo "$SERVERS" | jq -c --arg d "$dir" --arg n "$name" --arg l "$lang" '. + [{"dir": $d, "name": $n, "language": $l}]')
3637
done
3738
echo "servers=$SERVERS" >> "$GITHUB_OUTPUT"
3839
echo "Discovered: $(echo "$SERVERS" | jq -r '.[].name' | tr '\n' ', ')"
@@ -64,8 +65,9 @@ jobs:
6465
for row in $(echo "$SERVERS" | jq -r '.[] | @base64'); do
6566
dir=$(echo "$row" | base64 -d | jq -r '.dir')
6667
name=$(echo "$row" | base64 -d | jq -r '.name')
68+
lang=$(echo "$row" | base64 -d | jq -r '.language')
6769
if echo "$CHANGED" | grep -q "^src/Servers/${dir}/"; then
68-
AFFECTED=$(echo "$AFFECTED" | jq -c --arg d "$dir" --arg n "$name" '. + [{"dir": $d, "name": $n}]')
70+
AFFECTED=$(echo "$AFFECTED" | jq -c --arg d "$dir" --arg n "$name" --arg l "$lang" '. + [{"dir": $d, "name": $n, "language": $l}]')
6971
fi
7072
done
7173
@@ -607,14 +609,14 @@ jobs:
607609
608610
# ── Process each server ──────────────────────────────────────
609611
servers_config = json.loads(os.environ['PROBE_SERVERS'])
610-
SERVERS = [(s['name'], f"probe-{s['dir']}.json") for s in servers_config]
612+
SERVERS = [(s['name'], f"probe-{s['dir']}.json", s.get('language', '')) for s in servers_config]
611613
612614
commit_id = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode().strip()
613615
commit_msg = subprocess.check_output(['git', 'log', '-1', '--format=%s']).decode().strip()
614616
commit_time = subprocess.check_output(['git', 'log', '-1', '--format=%cI']).decode().strip()
615617
616618
server_data = []
617-
for name, path in SERVERS:
619+
for name, path, language in SERVERS:
618620
p = pathlib.Path(path)
619621
if not p.exists():
620622
print(f'::warning::{name}: result file {path} not found, skipping')
@@ -623,6 +625,7 @@ jobs:
623625
raw = json.load(f)
624626
ev = evaluate(raw)
625627
ev['name'] = name
628+
ev['language'] = language
626629
server_data.append(ev)
627630
s = ev['summary']
628631
print(f"{name}: {s['passed']}/{s['scored']} passed, {s['failed']} failed, {s['warnings']} warnings")

docs/content/compliance/_index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Each test sends a request that violates a specific **MUST** or **MUST NOT** requ
1818
- **Host header** &mdash; missing or duplicate Host with conflicting values (RFC 9112 &sect;7.1, RFC 9110 &sect;5.4)
1919
- **Content-Length** &mdash; non-numeric, plus sign, overflow (RFC 9112 &sect;6.1)
2020

21+
<div id="lang-filter"></div>
2122
<div id="table-compliance"><p><em>Loading...</em></p></div>
2223

2324
<script src="/Http11Probe/probe/data.js"></script>
@@ -28,7 +29,11 @@ Each test sends a request that violates a specific **MUST** or **MUST NOT** requ
2829
document.getElementById('table-compliance').innerHTML = '<p><em>No probe data available yet. Run the Probe workflow manually on <code>main</code> to generate results.</em></p>';
2930
return;
3031
}
31-
var ctx = ProbeRender.buildLookups(window.PROBE_DATA.servers);
32-
ProbeRender.renderTable('table-compliance', 'Compliance', ctx);
32+
function render(data) {
33+
var ctx = ProbeRender.buildLookups(data.servers);
34+
ProbeRender.renderTable('table-compliance', 'Compliance', ctx);
35+
}
36+
render(window.PROBE_DATA);
37+
ProbeRender.renderLanguageFilter('lang-filter', window.PROBE_DATA, render);
3338
})();
3439
</script>

docs/content/malformed-input/_index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ A well-implemented server should respond with `400 Bad Request`, `414 URI Too Lo
1919
- **Incomplete/empty requests** &mdash; partial HTTP or zero bytes sent
2020
- **Whitespace-only request** &mdash; just spaces/tabs with no method or URI
2121

22+
<div id="lang-filter"></div>
2223
<div id="table-malformed"><p><em>Loading...</em></p></div>
2324

2425
<script src="/Http11Probe/probe/data.js"></script>
@@ -29,7 +30,11 @@ A well-implemented server should respond with `400 Bad Request`, `414 URI Too Lo
2930
document.getElementById('table-malformed').innerHTML = '<p><em>No probe data available yet. Run the Probe workflow manually on <code>main</code> to generate results.</em></p>';
3031
return;
3132
}
32-
var ctx = ProbeRender.buildLookups(window.PROBE_DATA.servers);
33-
ProbeRender.renderTable('table-malformed', 'MalformedInput', ctx);
33+
function render(data) {
34+
var ctx = ProbeRender.buildLookups(data.servers);
35+
ProbeRender.renderTable('table-malformed', 'MalformedInput', ctx);
36+
}
37+
render(window.PROBE_DATA);
38+
ProbeRender.renderLanguageFilter('lang-filter', window.PROBE_DATA, render);
3439
})();
3540
</script>

docs/content/probe-results/_index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ HTTP/1.1 compliance comparison across frameworks. Each test sends a specific mal
88

99
## Summary
1010

11+
<div id="lang-filter"></div>
1112
<div id="probe-summary"><p><em>Loading probe data...</em></p></div>
1213

1314
{{< callout type="info" >}}
@@ -23,5 +24,8 @@ These results are from CI runs (`ubuntu-latest`). Click on the **Compliance**, *
2324
return;
2425
}
2526
ProbeRender.renderSummary('probe-summary', window.PROBE_DATA);
27+
ProbeRender.renderLanguageFilter('lang-filter', window.PROBE_DATA, function (filtered) {
28+
ProbeRender.renderSummary('probe-summary', filtered);
29+
});
2630
})();
2731
</script>

docs/content/smuggling/_index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ These tests send requests with ambiguous framing &mdash; conflicting `Content-Le
2323
Some tests are **unscored** (marked with `*`). These send payloads where the RFC permits multiple valid interpretations &mdash; for example, OWS trimming or case-insensitive TE matching. A `2xx` response is RFC-compliant but shown as a warning since stricter rejection is preferred.
2424
{{< /callout >}}
2525

26+
<div id="lang-filter"></div>
2627
<div id="table-smuggling"><p><em>Loading...</em></p></div>
2728

2829
<script src="/Http11Probe/probe/data.js"></script>
@@ -33,7 +34,11 @@ Some tests are **unscored** (marked with `*`). These send payloads where the RFC
3334
document.getElementById('table-smuggling').innerHTML = '<p><em>No probe data available yet. Run the Probe workflow manually on <code>main</code> to generate results.</em></p>';
3435
return;
3536
}
36-
var ctx = ProbeRender.buildLookups(window.PROBE_DATA.servers);
37-
ProbeRender.renderTable('table-smuggling', 'Smuggling', ctx);
37+
function render(data) {
38+
var ctx = ProbeRender.buildLookups(data.servers);
39+
ProbeRender.renderTable('table-smuggling', 'Smuggling', ctx);
40+
}
41+
render(window.PROBE_DATA);
42+
ProbeRender.renderLanguageFilter('lang-filter', window.PROBE_DATA, render);
3843
})();
3944
</script>

docs/static/probe/render.js

Lines changed: 179 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,114 @@ window.ProbeRender = (function () {
77
var EXPECT_BG = '#444c56';
88
var pillCss = 'text-align:center;padding:2px 4px;font-size:11px;font-weight:600;color:#fff;border-radius:3px;min-width:28px;display:inline-block;line-height:18px;';
99

10+
// ── Scrollbar styling (injected once) ──────────────────────────
11+
var scrollStyleInjected = false;
12+
function injectScrollStyle() {
13+
if (scrollStyleInjected) return;
14+
scrollStyleInjected = true;
15+
var isDark = document.documentElement.classList.contains('dark');
16+
var trackBg = isDark ? '#2a2f38' : '#e5e7eb';
17+
var thumbBg = isDark ? '#4b5563' : '#94a3b8';
18+
var thumbHover = isDark ? '#6b7280' : '#64748b';
19+
var css = '.probe-scroll{overflow-x:auto}'
20+
+ '.probe-scroll::-webkit-scrollbar{height:8px}'
21+
+ '.probe-scroll::-webkit-scrollbar-track{background:' + trackBg + ';border-radius:4px}'
22+
+ '.probe-scroll::-webkit-scrollbar-thumb{background:' + thumbBg + ';border-radius:4px}'
23+
+ '.probe-scroll::-webkit-scrollbar-thumb:hover{background:' + thumbHover + '}'
24+
+ '.probe-scroll{scrollbar-width:thin;scrollbar-color:' + thumbBg + ' ' + trackBg + '}';
25+
var style = document.createElement('style');
26+
style.textContent = css;
27+
document.head.appendChild(style);
28+
}
29+
30+
// ── Test ID → doc page URL mapping ─────────────────────────────
31+
var TEST_URLS = {
32+
'COMP-ABSOLUTE-FORM': '/Http11Probe/docs/request-line/absolute-form/',
33+
'COMP-ASTERISK-WITH-GET': '/Http11Probe/docs/request-line/asterisk-with-get/',
34+
'COMP-BASELINE': '',
35+
'COMP-CONNECT-EMPTY-PORT': '/Http11Probe/docs/request-line/connect-empty-port/',
36+
'COMP-DUPLICATE-HOST-SAME': '/Http11Probe/docs/host-header/duplicate-host-same/',
37+
'COMP-HOST-WITH-PATH': '/Http11Probe/docs/host-header/host-with-path/',
38+
'COMP-HOST-WITH-USERINFO': '/Http11Probe/docs/host-header/host-with-userinfo/',
39+
'COMP-LEADING-CRLF': '/Http11Probe/docs/line-endings/leading-crlf/',
40+
'COMP-METHOD-CASE': '/Http11Probe/docs/request-line/method-case/',
41+
'COMP-OPTIONS-STAR': '/Http11Probe/docs/request-line/options-star/',
42+
'COMP-UNKNOWN-TE-501': '/Http11Probe/docs/request-line/unknown-te-501/',
43+
'COMP-WHITESPACE-BEFORE-HEADERS': '/Http11Probe/docs/headers/whitespace-before-headers/',
44+
'MAL-BINARY-GARBAGE': '/Http11Probe/docs/malformed-input/binary-garbage/',
45+
'MAL-CHUNK-EXTENSION-LONG': '/Http11Probe/docs/malformed-input/chunk-extension-long/',
46+
'MAL-CHUNK-SIZE-OVERFLOW': '/Http11Probe/docs/malformed-input/chunk-size-overflow/',
47+
'MAL-CL-OVERFLOW': '/Http11Probe/docs/malformed-input/cl-overflow/',
48+
'MAL-CONTROL-CHARS-HEADER': '/Http11Probe/docs/malformed-input/control-chars-header/',
49+
'MAL-EMPTY-REQUEST': '/Http11Probe/docs/malformed-input/empty-request/',
50+
'MAL-H2-PREFACE': '/Http11Probe/docs/malformed-input/h2-preface/',
51+
'MAL-INCOMPLETE-REQUEST': '/Http11Probe/docs/malformed-input/incomplete-request/',
52+
'MAL-LONG-HEADER-NAME': '/Http11Probe/docs/malformed-input/long-header-name/',
53+
'MAL-LONG-HEADER-VALUE': '/Http11Probe/docs/malformed-input/long-header-value/',
54+
'MAL-LONG-METHOD': '/Http11Probe/docs/malformed-input/long-method/',
55+
'MAL-LONG-URL': '/Http11Probe/docs/malformed-input/long-url/',
56+
'MAL-MANY-HEADERS': '/Http11Probe/docs/malformed-input/many-headers/',
57+
'MAL-NON-ASCII-HEADER-NAME': '/Http11Probe/docs/malformed-input/non-ascii-header-name/',
58+
'MAL-NON-ASCII-URL': '/Http11Probe/docs/malformed-input/non-ascii-url/',
59+
'MAL-NUL-IN-HEADER-VALUE': '/Http11Probe/docs/malformed-input/nul-in-header-value/',
60+
'MAL-NUL-IN-URL': '/Http11Probe/docs/malformed-input/nul-in-url/',
61+
'MAL-WHITESPACE-ONLY-LINE': '/Http11Probe/docs/malformed-input/whitespace-only-line/',
62+
'RFC9110-5.4-DUPLICATE-HOST': '/Http11Probe/docs/host-header/duplicate-host/',
63+
'RFC9110-5.6.2-SP-BEFORE-COLON': '/Http11Probe/docs/headers/sp-before-colon/',
64+
'RFC9110-8.6-DUPLICATE-CL': '/Http11Probe/docs/smuggling/duplicate-cl/',
65+
'RFC9112-2.2-BARE-LF-HEADER': '/Http11Probe/docs/line-endings/bare-lf-header/',
66+
'RFC9112-2.2-BARE-LF-REQUEST-LINE': '/Http11Probe/docs/line-endings/bare-lf-request-line/',
67+
'RFC9112-2.3-HTTP09-REQUEST': '/Http11Probe/docs/request-line/http09-request/',
68+
'RFC9112-2.3-INVALID-VERSION': '/Http11Probe/docs/request-line/invalid-version/',
69+
'RFC9112-3-CR-ONLY-LINE-ENDING': '/Http11Probe/docs/line-endings/cr-only-line-ending/',
70+
'RFC9112-3-MISSING-TARGET': '/Http11Probe/docs/request-line/missing-target/',
71+
'RFC9112-3-MULTI-SP-REQUEST-LINE': '/Http11Probe/docs/request-line/multi-sp-request-line/',
72+
'RFC9112-3.2-FRAGMENT-IN-TARGET': '/Http11Probe/docs/request-line/fragment-in-target/',
73+
'RFC9112-5-EMPTY-HEADER-NAME': '/Http11Probe/docs/headers/empty-header-name/',
74+
'RFC9112-5-HEADER-NO-COLON': '/Http11Probe/docs/headers/header-no-colon/',
75+
'RFC9112-5-INVALID-HEADER-NAME': '/Http11Probe/docs/headers/invalid-header-name/',
76+
'RFC9112-5.1-OBS-FOLD': '/Http11Probe/docs/headers/obs-fold/',
77+
'RFC9112-6.1-CL-LEADING-ZEROS': '/Http11Probe/docs/smuggling/cl-leading-zeros/',
78+
'RFC9112-6.1-CL-NEGATIVE': '/Http11Probe/docs/smuggling/cl-negative/',
79+
'RFC9112-6.1-CL-NON-NUMERIC': '/Http11Probe/docs/content-length/cl-non-numeric/',
80+
'RFC9112-6.1-CL-PLUS-SIGN': '/Http11Probe/docs/content-length/cl-plus-sign/',
81+
'RFC9112-6.1-CL-TE-BOTH': '/Http11Probe/docs/smuggling/cl-te-both/',
82+
'RFC9112-7.1-MISSING-HOST': '/Http11Probe/docs/host-header/missing-host/',
83+
'SMUG-BARE-CR-HEADER-VALUE': '/Http11Probe/docs/smuggling/bare-cr-header-value/',
84+
'SMUG-CHUNK-BARE-SEMICOLON': '/Http11Probe/docs/smuggling/chunk-bare-semicolon/',
85+
'SMUG-CHUNK-HEX-PREFIX': '/Http11Probe/docs/smuggling/chunk-hex-prefix/',
86+
'SMUG-CHUNK-LEADING-SP': '/Http11Probe/docs/smuggling/chunk-leading-sp/',
87+
'SMUG-CHUNK-MISSING-TRAILING-CRLF': '/Http11Probe/docs/smuggling/chunk-missing-trailing-crlf/',
88+
'SMUG-CHUNK-UNDERSCORE': '/Http11Probe/docs/smuggling/chunk-underscore/',
89+
'SMUG-CHUNKED-WITH-PARAMS': '/Http11Probe/docs/smuggling/chunked-with-params/',
90+
'SMUG-CL-COMMA-DIFFERENT': '/Http11Probe/docs/smuggling/cl-comma-different/',
91+
'SMUG-CL-COMMA-SAME': '/Http11Probe/docs/smuggling/cl-comma-same/',
92+
'SMUG-CL-EXTRA-LEADING-SP': '/Http11Probe/docs/smuggling/cl-extra-leading-sp/',
93+
'SMUG-CL-HEX-PREFIX': '/Http11Probe/docs/smuggling/cl-hex-prefix/',
94+
'SMUG-CL-INTERNAL-SPACE': '/Http11Probe/docs/smuggling/cl-internal-space/',
95+
'SMUG-CL-OCTAL': '/Http11Probe/docs/smuggling/cl-octal/',
96+
'SMUG-CL-TRAILING-SPACE': '/Http11Probe/docs/smuggling/cl-trailing-space/',
97+
'SMUG-CLTE-PIPELINE': '/Http11Probe/docs/smuggling/clte-pipeline/',
98+
'SMUG-EXPECT-100-CL': '/Http11Probe/docs/smuggling/expect-100-cl/',
99+
'SMUG-HEADER-INJECTION': '/Http11Probe/docs/smuggling/header-injection/',
100+
'SMUG-TE-CASE-MISMATCH': '/Http11Probe/docs/smuggling/te-case-mismatch/',
101+
'SMUG-TE-DOUBLE-CHUNKED': '/Http11Probe/docs/smuggling/te-double-chunked/',
102+
'SMUG-TE-DUPLICATE-HEADERS': '/Http11Probe/docs/smuggling/te-duplicate-headers/',
103+
'SMUG-TE-EMPTY-VALUE': '/Http11Probe/docs/smuggling/te-empty-value/',
104+
'SMUG-TE-HTTP10': '/Http11Probe/docs/smuggling/te-http10/',
105+
'SMUG-TE-LEADING-COMMA': '/Http11Probe/docs/smuggling/te-leading-comma/',
106+
'SMUG-TE-NOT-FINAL-CHUNKED': '/Http11Probe/docs/smuggling/te-not-final-chunked/',
107+
'SMUG-TE-SP-BEFORE-COLON': '/Http11Probe/docs/smuggling/te-sp-before-colon/',
108+
'SMUG-TE-TRAILING-SPACE': '/Http11Probe/docs/smuggling/te-trailing-space/',
109+
'SMUG-TE-XCHUNKED': '/Http11Probe/docs/smuggling/te-xchunked/',
110+
'SMUG-TECL-PIPELINE': '/Http11Probe/docs/smuggling/tecl-pipeline/',
111+
'SMUG-TRANSFER_ENCODING': '/Http11Probe/docs/smuggling/transfer-encoding-underscore/'
112+
};
113+
114+
function testUrl(tid) {
115+
return TEST_URLS[tid] || '';
116+
}
117+
10118
function pill(bg, label) {
11119
return '<span style="' + pillCss + 'background:' + bg + ';">' + label + '</span>';
12120
}
@@ -73,6 +181,7 @@ window.ProbeRender = (function () {
73181
}
74182

75183
function renderTable(targetId, categoryKey, ctx) {
184+
injectScrollStyle();
76185
var el = document.getElementById(targetId);
77186
if (!el) return;
78187
var names = ctx.names, lookup = ctx.lookup, testIds = ctx.testIds;
@@ -93,7 +202,7 @@ window.ProbeRender = (function () {
93202
return tid.replace(/^(RFC\d+-[\d.]+-|COMP-|SMUG-|MAL-)/, '');
94203
});
95204

96-
var t = '<div style="overflow-x:auto;"><table style="border-collapse:collapse;font-size:12px;white-space:nowrap;">';
205+
var t = '<div class="probe-scroll"><table style="border-collapse:collapse;font-size:12px;white-space:nowrap;">';
97206

98207
// Column header row (diagonal labels)
99208
t += '<thead><tr>';
@@ -102,11 +211,17 @@ window.ProbeRender = (function () {
102211
var first = lookup[names[0]][tid];
103212
var isUnscored = first.scored === false;
104213
var opacity = isUnscored ? 'opacity:0.55;' : '';
214+
var url = testUrl(tid);
105215
t += '<th style="padding:0;height:110px;width:30px;vertical-align:bottom;' + opacity + '">';
106216
t += '<div style="width:30px;height:110px;position:relative;">';
107-
t += '<a href="/Http11Probe/glossary/#test-' + tid + '" style="font-size:10px;font-weight:500;color:inherit;text-decoration:none;position:absolute;bottom:6px;left:50%;transform-origin:bottom left;transform:rotate(-55deg);white-space:nowrap;" title="' + first.description + '">' + shortLabels[i];
217+
if (url) {
218+
t += '<a href="' + url + '" style="font-size:10px;font-weight:500;color:inherit;text-decoration:none;position:absolute;bottom:6px;left:50%;transform-origin:bottom left;transform:rotate(-55deg);white-space:nowrap;" title="' + first.description + '">' + shortLabels[i];
219+
} else {
220+
t += '<span style="font-size:10px;font-weight:500;color:inherit;position:absolute;bottom:6px;left:50%;transform-origin:bottom left;transform:rotate(-55deg);white-space:nowrap;" title="' + first.description + '">' + shortLabels[i];
221+
}
108222
if (isUnscored) t += '*';
109-
t += '</a></div></th>';
223+
t += url ? '</a>' : '</span>';
224+
t += '</div></th>';
110225
});
111226
t += '</tr></thead><tbody>';
112227

@@ -145,12 +260,73 @@ window.ProbeRender = (function () {
145260
el.innerHTML = t;
146261
}
147262

263+
// ── Language filter ────────────────────────────────────────────
264+
function renderLanguageFilter(targetId, data, onChange) {
265+
var el = document.getElementById(targetId);
266+
if (!el || !data.servers || data.servers.length === 0) return;
267+
268+
var langs = {};
269+
data.servers.forEach(function (sv) {
270+
if (sv.language) langs[sv.language] = true;
271+
});
272+
var langList = Object.keys(langs).sort();
273+
if (langList.length === 0) return;
274+
275+
var isDark = document.documentElement.classList.contains('dark');
276+
var baseBg = isDark ? '#21262d' : '#f6f8fa';
277+
var baseFg = isDark ? '#c9d1d9' : '#24292f';
278+
var baseBorder = isDark ? '#30363d' : '#d0d7de';
279+
var activeBg = isDark ? '#1f6feb' : '#0969da';
280+
281+
var btnStyle = 'display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;'
282+
+ 'border-radius:20px;cursor:pointer;border:1px solid ' + baseBorder + ';'
283+
+ 'margin-right:6px;margin-bottom:6px;transition:all 0.15s;';
284+
285+
var html = '<div style="margin-bottom:12px;">';
286+
html += '<button class="probe-lang-btn" data-lang="" style="' + btnStyle
287+
+ 'background:' + activeBg + ';color:#fff;border-color:' + activeBg + ';">All</button>';
288+
langList.forEach(function (lang) {
289+
html += '<button class="probe-lang-btn" data-lang="' + lang + '" style="' + btnStyle
290+
+ 'background:' + baseBg + ';color:' + baseFg + ';">' + lang + '</button>';
291+
});
292+
html += '</div>';
293+
el.innerHTML = html;
294+
295+
var buttons = el.querySelectorAll('.probe-lang-btn');
296+
buttons.forEach(function (btn) {
297+
btn.addEventListener('click', function () {
298+
var lang = btn.getAttribute('data-lang');
299+
buttons.forEach(function (b) {
300+
if (b === btn) {
301+
b.style.background = activeBg;
302+
b.style.color = '#fff';
303+
b.style.borderColor = activeBg;
304+
} else {
305+
b.style.background = baseBg;
306+
b.style.color = baseFg;
307+
b.style.borderColor = baseBorder;
308+
}
309+
});
310+
if (!lang) {
311+
onChange(data);
312+
} else {
313+
var filtered = {
314+
commit: data.commit,
315+
servers: data.servers.filter(function (sv) { return sv.language === lang; })
316+
};
317+
onChange(filtered);
318+
}
319+
});
320+
});
321+
}
322+
148323
return {
149324
pill: pill,
150325
verdictBg: verdictBg,
151326
buildLookups: buildLookups,
152327
renderSummary: renderSummary,
153328
renderTable: renderTable,
329+
renderLanguageFilter: renderLanguageFilter,
154330
EXPECT_BG: EXPECT_BG
155331
};
156332
})();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"name": "Apache"}
1+
{"name": "Apache", "language": "C"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"name": "Kestrel"}
1+
{"name": "Kestrel", "language": "C#"}

src/Servers/CaddyServer/probe.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"name": "Caddy"}
1+
{"name": "Caddy", "language": "Go"}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"name": "Express"}
1+
{"name": "Express", "language": "JavaScript"}

0 commit comments

Comments
 (0)