23 lines
7.0 KiB
HTML
23 lines
7.0 KiB
HTML
<!doctype html>
|
||
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Greenfield SIEM</title>
|
||
<style>
|
||
:root{font-family:Inter,Segoe UI,Arial,sans-serif;color:#172033;background:#f4f7fb}*{box-sizing:border-box}body{margin:0}header{background:#101828;color:white;padding:16px 24px;display:flex;justify-content:space-between;align-items:center}header b{font-size:18px}main{padding:22px;max-width:1800px;margin:auto}.cards{display:grid;grid-template-columns:repeat(4,minmax(170px,1fr));gap:12px}.card,.panel{background:white;border:1px solid #e4e7ec;border-radius:10px;padding:16px;box-shadow:0 1px 2px #1018280f}.value{font-size:28px;font-weight:700;margin-top:8px}.muted{color:#667085;font-size:13px}.tabs{display:flex;gap:8px;margin:18px 0}.tabs button,button{border:1px solid #d0d5dd;background:white;border-radius:7px;padding:8px 12px;cursor:pointer}.tabs button.active,button.primary{background:#175cd3;color:white;border-color:#175cd3}.filters{display:grid;grid-template-columns:repeat(6,minmax(120px,1fr));gap:8px;margin-bottom:10px}input,select{width:100%;padding:8px;border:1px solid #d0d5dd;border-radius:7px}table{width:100%;border-collapse:collapse;font-size:13px}th,td{text-align:left;border-bottom:1px solid #eaecf0;padding:8px;vertical-align:top}th{color:#475467;background:#f9fafb;position:sticky;top:0}.tablewrap{max-height:65vh;overflow:auto}.sev-critical{color:#b42318;font-weight:700}.sev-high{color:#c4320a;font-weight:700}.sev-medium{color:#b54708;font-weight:700}.pill{display:inline-block;padding:2px 7px;border-radius:999px;background:#eef2f6;font-size:12px}.hidden{display:none}.error{background:#fef3f2;color:#b42318;padding:10px;border-radius:6px;margin:10px 0}@media(max-width:900px){.cards{grid-template-columns:1fr 1fr}.filters{grid-template-columns:1fr 1fr}}
|
||
</style></head><body>
|
||
<header><b>Greenfield SIEM</b><span class="muted" style="color:#d0d5dd">Tenant: {{.Tenant}}</span></header>
|
||
<main><div id="err"></div><div class="cards"><div class="card"><div class="muted">Events / 24h</div><div id="events24" class="value">–</div></div><div class="card"><div class="muted">Aktive Hosts / 24h</div><div id="hosts24" class="value">–</div></div><div class="card"><div class="muted">Offene High/Critical</div><div id="highdet" class="value">–</div></div><div class="card"><div class="muted">Pipeline</div><div id="pipeline" class="value">OK</div></div></div>
|
||
<div class="tabs"><button class="active" data-tab="events">Events</button><button data-tab="detections">Detections</button><button data-tab="agents">Agents</button></div>
|
||
<section id="events" class="panel"><div class="filters"><input id="fhost" placeholder="Host"><input id="fuser" placeholder="User"><input id="fip" placeholder="Source-IP"><input id="fcode" placeholder="Event-ID"><input id="fchannel" placeholder="Channel"><select id="fhours"><option value="1">1 Stunde</option><option value="24" selected>24 Stunden</option><option value="168">7 Tage</option><option value="720">30 Tage</option><option value="2160">90 Tage</option></select></div><button class="primary" onclick="loadEvents()">Suchen</button><div class="tablewrap"><table><thead><tr><th>Zeit</th><th>Host</th><th>Event</th><th>Aktion</th><th>User</th><th>Source-IP</th><th>Workstation</th><th>Prozess</th></tr></thead><tbody id="eventrows"></tbody></table></div></section>
|
||
<section id="detections" class="panel hidden"><div class="tablewrap"><table><thead><tr><th>Zuletzt</th><th>Schwere</th><th>Regel</th><th>Host/User</th><th>Zusammenfassung</th><th>Status</th></tr></thead><tbody id="detrows"></tbody></table></div></section>
|
||
<section id="agents" class="panel hidden"><div class="tablewrap"><table><thead><tr><th>Host</th><th>Enabled</th><th>IP</th><th>First seen</th><th>Last seen</th></tr></thead><tbody id="agentrows"></tbody></table></div></section></main>
|
||
<script>
|
||
const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
async function get(url,opt){const r=await fetch(url,opt);const d=await r.json();if(!r.ok)throw new Error(d.error||r.statusText);return d}
|
||
function fail(e){document.getElementById('err').innerHTML='<div class="error">'+esc(e.message)+'</div>';document.getElementById('pipeline').textContent='Fehler'}
|
||
async function summary(){try{const d=await get('/api/summary');events24.textContent=Number(d.events_24h||0).toLocaleString('de-DE');hosts24.textContent=Number(d.active_hosts||0).toLocaleString('de-DE');const x=d.detections||{};highdet.textContent=Number((x.high||0)+(x.critical||0)).toLocaleString('de-DE')}catch(e){fail(e)}}
|
||
async function loadEvents(){try{const q=new URLSearchParams({hours:fhours.value,limit:'500'});[['host',fhost],['user',fuser],['ip',fip],['event_code',fcode],['channel',fchannel]].forEach(([k,x])=>{if(x.value.trim())q.set(k,x.value.trim())});const d=await get('/api/events?'+q);eventrows.innerHTML=d.map(x=>`<tr><td>${esc(x.event_time)}</td><td>${esc(x.host_name)}</td><td><b>${esc(x.event_code)}</b><br><span class="pill">${esc(x.channel)}</span></td><td>${esc(x.category)} / ${esc(x.action)}<br><span class="muted">${esc(x.outcome)}</span></td><td>${esc(x.user_name||x.target_user||x.subject_user)}</td><td>${esc(x.source_ip)}</td><td>${esc(x.workstation)}</td><td>${esc(x.process_path||x.message)}</td></tr>`).join('')}catch(e){fail(e)}}
|
||
async function loadDetections(){try{const d=await get('/api/detections');detrows.innerHTML=d.map(x=>`<tr><td>${esc(x.LastSeen||x.last_seen)}</td><td class="sev-${esc(x.Severity||x.severity)}">${esc(x.Severity||x.severity)}</td><td>${esc(x.RuleName||x.rule_name)}</td><td>${esc(x.Hostname||x.hostname)}<br>${esc(x.UserName||x.user_name)}<br><span class="muted">${esc(x.Workstation||x.workstation||x.SourceIP||x.source_ip)}</span></td><td>${esc(x.Summary||x.summary)}</td><td><select onchange="setStatus(${Number(x.ID||x.id)},this.value)">${['open','investigating','closed','false_positive'].map(s=>`<option ${s==(x.Status||x.status)?'selected':''}>${s}</option>`).join('')}</select></td></tr>`).join('')}catch(e){fail(e)}}
|
||
async function setStatus(id,status){try{await get('/api/detections/status',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,status})});summary()}catch(e){fail(e)}}
|
||
async function loadAgents(){try{const d=await get('/api/agents');agentrows.innerHTML=d.map(x=>`<tr><td>${esc(x.hostname||x.Hostname)}</td><td>${(x.enabled??x.Enabled)?'ja':'nein'}</td><td>${esc(x.last_ip||x.LastIP)}</td><td>${esc(x.first_seen||x.FirstSeen)}</td><td>${esc(x.last_seen||x.LastSeen)}</td></tr>`).join('')}catch(e){fail(e)}}
|
||
document.querySelectorAll('.tabs button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.tabs button').forEach(x=>x.classList.remove('active'));b.classList.add('active');document.querySelectorAll('main section').forEach(x=>x.classList.add('hidden'));document.getElementById(b.dataset.tab).classList.remove('hidden');if(b.dataset.tab==='detections')loadDetections();if(b.dataset.tab==='agents')loadAgents()});summary();loadEvents();setInterval(summary,15000);
|
||
</script></body></html>
|