Skip to content

Instantly share code, notes, and snippets.

@0x4D31
Created July 4, 2025 23:35
Show Gist options
  • Save 0x4D31/78481b5446882309a6c2ff253915ea15 to your computer and use it in GitHub Desktop.
Save 0x4D31/78481b5446882309a6c2ff253915ea15 to your computer and use it in GitHub Desktop.
example SSE client to view Finch events
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Finch SSE Events</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
--bg: #fff;
--card-bg: #f8f9fa;
--key-color: #0056b3;
--val-color: #495057;
--label-color: #6c757d;
--text-sm: 0.82rem;
--highlight-bg: #fff3cd;
}
html,body{margin:0;padding:0;background:var(--bg);font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;color:#212529}
header{padding:.75rem 1rem;background:#0d6efd;color:#fff}
h1{margin:0;font-size:1.25rem;font-weight:600}
.container{
max-width: 1200px;
margin: 0 auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
box-sizing: border-box;
}
#search{
width: 100%;
padding: .75rem;
font-size: 1rem;
border: 1px solid #ced4da;
border-radius: 6px;
box-sizing: border-box;
}
#log{flex:1;height:65vh;overflow:auto}
.event{
background: var(--card-bg);
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 1rem;
margin-bottom: .75rem;
box-shadow: 0 1px 2px rgba(0,0,0,.04);
width: 100%;
box-sizing: border-box;
}
.metadata{font-weight:600;margin-bottom:.35em;font-size:1rem}
.details{margin-left:1.2em;font-size:var(--text-sm);line-height:1.28;}
.details div{margin:1px 0}
.details .label{color:var(--label-color);display:inline-block;margin-right:0.18em;font-size:0.95em;}
.details .value{font-family:ui-monospace,Menlo,Consolas,"Liberation Mono",monospace;font-size:1em;}
.raw{background:#fff;border:1px solid #e2e3e5;border-radius:4px;padding:.5em .7em;font-size:var(--text-sm);white-space:normal;word-break:break-all;overflow:auto;box-sizing:border-box;}
.j-key{color:var(--key-color)}
.j-val{color:var(--val-color)}
mark{background:var(--highlight-bg);color:inherit;padding:0}
@media(max-width:600px){
.container{padding:.3em}
.event{padding:.7em}
.details{margin-left:.5em}
.details .label{min-width:0;}
}
</style>
</head>
<body>
<header><h1>Finch SSE Events</h1></header>
<div class="container">
<input id="search" placeholder="Filter events…" />
<div id="log" aria-live="polite"></div>
</div>
<script>
(() => {
const log = document.getElementById('log');
const input = document.getElementById('search');
const events = [];
// Helpers
const esc = s => s.replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));
const highlight = (text, kw) =>
kw
? text.replace(new RegExp(kw.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'),'gi'), m=>`<mark>${m}</mark>`)
: text;
// Highlighting only keys and values (not the tags)
function jsonPrettyHighlight(j, kw) {
// color and highlight keys and values
return esc(j).replace(
/(\"([^\"\\]|\\.)*\")(?=:)|(:)\s*(\"([^\"\\]|\\.)*\"|\d+|true|false|null)/g,
(m, key, _, colon, val) => {
if (key) return `<span class="j-key">${highlight(key, kw)}</span>`;
if (val) return `${colon || ''} <span class="j-val">${highlight(val, kw)}</span>`;
return m;
}
);
}
function render() {
const kw = input.value.toLowerCase();
log.innerHTML = '';
events.forEach(({obj,raw}) => {
if (kw && !raw.toLowerCase().includes(kw)) return;
const m=obj;
const src = m.srcIP || '—';
const port = m.dstPort || '—';
let path = m.request || '';
if (typeof path!=='string') path='';
if (path && !path.startsWith('/')) path='/'+path;
// event card
const card = document.createElement('div');
card.className='event';
card.innerHTML = `
<div class="metadata">${highlight(`${src} → :${port}${path}`,kw)}</div>
<div class="details">
${detailHTML('JA3', m.ja3 || '—', kw)}
${detailHTML('JA4', m.ja4 || '—', kw)}
${detailHTML('JA4H', m.ja4h || '—', kw)}
${detailHTML('UA', m.userAgent, kw)}
</div>
<div class="raw">${jsonPrettyHighlight(JSON.stringify(m), kw)}</div>`;
log.append(card);
});
log.scrollTop=log.scrollHeight;
}
// tiny templater for labelled rows
const detailHTML = (label,val,kw)=>
`<div><span class="label">${label}:</span><span class="value">${highlight(esc(val),kw)}</span></div>`;
// stream
const es=new EventSource(location.protocol.startsWith('http')?'/events':'http://localhost:9036/events');
es.onmessage = e => {try{events.push({obj:JSON.parse(e.data),raw:e.data})}catch{events.push({obj:{raw:e.data},raw:e.data})};render()};
es.onerror = () => {events.push({obj:{msg:'Connection lost. Retrying…'},raw:'Connection lost.'});render()};
input.addEventListener('input',render);
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment