activity-simulation / index.html.backup
Augusto Diaz
Space: dashboard only
5b7eaab
Raw
History Blame Contribute Delete
17.2 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ActivitySimDetect Dashboard</title>
<style>
:root{ --white:#FFFFFF; --black:#000000; --blue:#1E59F0; --light-blue:#F7F7F7; --dark-gray:#7C7D7E; --gray:#E1E4E6; --gray-2:#F9FAFB; --light-gray:#F7F7F7; --dark-blue:#404E90; --red:#D92D20; --green:#13A964; --radius:14px; }
html,body{height:100%}
body{margin:0;font-family:Inter,system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:linear-gradient(135deg,#F9FAFB 0%, #E1E4E6 100%);color:#0b1020;display:flex;flex-direction:column}
.header{padding:24px 28px;display:flex;justify-content:space-between;align-items:center;background:linear-gradient(135deg,#1E59F0 0%, #404E90 100%);color:var(--white)}
.header .brand{font-weight:700;letter-spacing:.4px}
.container{padding:24px;display:grid;grid-template-columns:repeat(4,minmax(280px,1fr));gap:20px;flex:1;max-width:100%;margin:0 auto}
/* Place Risk History in right-most column on wide screens */
@media (min-width: 1200px){
.history{grid-column:4}
}
@media (max-width: 1199px){
.container{grid-template-columns:repeat(3,minmax(280px,1fr))}
.history{grid-column:auto}
}
@media (max-width: 900px){
.container{grid-template-columns:repeat(2,minmax(260px,1fr))}
}
@media (max-width: 600px){
.container{grid-template-columns:1fr}
}
.card{background:var(--white);border-radius:var(--radius);box-shadow:0 10px 30px rgba(16,24,40,.08);border:1px solid #EEF2F6}
.card h3{margin:16px 16px 0 16px}
.card .content{padding:16px}
.pill{display:inline-block;padding:6px 10px;border-radius:999px;font-size:12px;border:1px solid #EEF2F6}
.pill.low{background:#ECFDF3;color:#027A48;border-color:#ABEFC6}
.pill.med{background:#FFF6ED;color:#B54708;border-color:#FEDF89}
.pill.high{background:#FEF3F2;color:#B42318;border-color:#FECDCA}
.muted{color:#667085}
.footer{padding:24px;text-align:center;color:#667085}
.bar{height:18px;border-radius:999px;background:linear-gradient(90deg,#13A964,#F59E0B,#D92D20)}
.scroll{overflow:auto}
.btn{appearance:none;border:0;border-radius:10px;padding:10px 14px;font-weight:600;cursor:pointer;box-shadow:0 2px 6px rgba(16,24,40,.08);transition:transform .06s ease,box-shadow .2s ease}
.btn:active{transform:translateY(1px)}
.btn-seed{background:#13A964;color:#FFFFFF}
.btn-clear{background:#B54708;color:#FFFFFF}
.btn-evict{background:#1E59F0;color:#FFFFFF}
.btn:hover{box-shadow:0 6px 16px rgba(16,24,40,.12)}
</style>
</head>
<body>
<div class="header">
<div class="brand">Activity Simulation Detection</div>
</div>
<div class="container">
<div class="card">
<h3>Machine Risk</h3>
<div class="content">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<div>
<div class="muted">machineId</div>
<div id="machineId">demo-machine</div>
</div>
<span id="riskLevel" class="pill low">Low</span>
</div>
<div class="bar"><div id="riskBar" style="height:100%;width:12%;border-radius:999px;background:#13A964"></div></div>
<div class="muted" style="margin-top:4px">Risk score: <span id="riskScore">0.12</span></div>
</div>
</div>
<div class="card">
<h3>Evidence</h3>
<div class="content">
<pre id="evidence" style="white-space:pre-wrap;margin:0" class="muted">No data yet.</pre>
</div>
</div>
<div class="card">
<h3>Device Trust (Top Observed)</h3>
<div class="content">
<div id="devices" class="muted">Loading…</div>
</div>
</div>
<div class="card">
<h3>Heuristics</h3>
<div class="content">
<div style="margin-bottom:10px">
<div class="muted" style="display:flex;justify-content:space-between">
<span>Inter-event Entropy</span>
<span id="entropyVal"></span>
</div>
<div class="bar"><div id="entropyBar" style="height:100%;width:0%;border-radius:999px;background:#13A964"></div></div>
</div>
<div style="margin-bottom:10px">
<div class="muted" style="display:flex;justify-content:space-between">
<span>Interval Regularity</span>
<span id="intervalVal"></span>
</div>
<div class="bar"><div id="intervalBar" style="height:100%;width:0%;border-radius:999px;background:#F59E0B"></div></div>
</div>
<div>
<div class="muted" style="display:flex;justify-content:space-between">
<span>Path Straightness</span>
<span id="straightVal"></span>
</div>
<div class="bar"><div id="straightBar" style="height:100%;width:0%;border-radius:999px;background:#D92D20"></div></div>
</div>
</div>
</div>
<div class="card">
<h3>ML Model (Level 2)</h3>
<div class="content">
<div style="margin-bottom:10px">
<div class="muted" style="display:flex;justify-content:space-between">
<span>ML Anomaly Score</span>
<span id="mlScoreVal"></span>
</div>
<div class="bar"><div id="mlScoreBar" style="height:100%;width:0%;border-radius:999px;background:#13A964"></div></div>
</div>
<div style="margin-bottom:10px">
<div class="muted" style="display:flex;justify-content:space-between">
<span>Model Confidence</span>
<span id="mlConfidenceVal"></span>
</div>
<div class="bar"><div id="mlConfidenceBar" style="height:100%;width:0%;border-radius:999px;background:#1E59F0"></div></div>
</div>
<div class="muted" style="font-size:12px;margin-top:8px">Model Status: <span id="modelStatus">Loading…</span></div>
<div style="margin-top:12px"><button id="refreshModel">Refresh Model</button></div>
</div>
</div>
<div class="card">
<h3>Machines</h3>
<div class="content">
<div id="machines" class="muted">Loading…</div>
</div>
</div>
<div class="card history">
<h3>Risk History</h3>
<div class="content scroll" id="historyContainer">
<div id="history" class="muted">Loading…</div>
</div>
</div>
</div>
<div class="footer" style="margin-top:auto">
<button id="seedBtn" class="btn btn-seed" style="margin-right:8px">Seed Demo</button>
<button id="clearBtn" class="btn btn-clear" style="margin-right:8px">Clear</button>
<button id="evictBtn" class="btn btn-evict">Evict Stale (120m)</button>
<div style="margin-top:12px">Augusto Diaz 2025</div>
</div>
<script>
const API = 'http://localhost:5208'
async function loadRisk(){
const machineId = 'demo-machine'
document.getElementById('machineId').textContent = machineId
try{
const resp = await fetch(API + '/risk/' + machineId)
if(!resp.ok){ throw new Error('No risk yet') }
const data = await resp.json()
const score = data.riskScore ?? 0
document.getElementById('riskScore').textContent = score.toFixed(2)
document.getElementById('riskBar').style.width = Math.round(score*100) + '%'
const levelEl = document.getElementById('riskLevel')
levelEl.textContent = data.riskLevel
levelEl.classList.remove('low','med','high')
if(score >= .75) levelEl.classList.add('high')
else if(score >= .4) levelEl.classList.add('med')
else levelEl.classList.add('low')
document.getElementById('evidence').textContent = JSON.stringify(data.evidence,null,2)
// Heuristic visuals (expect evidence.features = { interEventEntropy, fixedIntervalScore, pathStraightness, speedVariance, contextCoupling })
const feats = data.evidence && data.evidence.features || {}
const entropy = typeof feats.interEventEntropy === 'number' ? feats.interEventEntropy : null
const fixed = typeof feats.fixedIntervalScore === 'number' ? feats.fixedIntervalScore : null
const straight = typeof feats.pathStraightness === 'number' ? feats.pathStraightness : null
const speedVar = typeof feats.speedVariance === 'number' ? feats.speedVariance : null
const context = typeof feats.contextCoupling === 'number' ? feats.contextCoupling : null
// Level 2 ML Model visuals
const mlScore = typeof data.evidence.onnxScore === 'number' ? data.evidence.onnxScore : null
const heuristicScore = typeof data.evidence.heuristicRisk === 'number' ? data.evidence.heuristicRisk : null
if(entropy !== null){
document.getElementById('entropyVal').textContent = entropy.toFixed(2)
// Normalize entropy for display (0-3 range -> 0-1)
const normalizedEntropy = Math.max(0, Math.min(1, entropy / 3))
document.getElementById('entropyBar').style.width = normalizedEntropy * 100 + '%'
// Color coding: low entropy = red (suspicious), high entropy = green (normal)
const entropyBar = document.getElementById('entropyBar')
if(entropy < 1.0) entropyBar.style.background = '#D92D20' // Red for low entropy
else if(entropy < 2.0) entropyBar.style.background = '#F59E0B' // Yellow for medium
else entropyBar.style.background = '#13A964' // Green for high entropy
}
if(fixed !== null){
document.getElementById('intervalVal').textContent = fixed.toFixed(2)
document.getElementById('intervalBar').style.width = Math.max(0, Math.min(1, fixed)) * 100 + '%'
// Color coding: high regularity = red (suspicious), low regularity = green (normal)
const intervalBar = document.getElementById('intervalBar')
if(fixed > 0.7) intervalBar.style.background = '#D92D20' // Red for high regularity
else if(fixed > 0.3) intervalBar.style.background = '#F59E0B' // Yellow for medium
else intervalBar.style.background = '#13A964' // Green for low regularity
}
if(straight !== null){
document.getElementById('straightVal').textContent = straight.toFixed(2)
document.getElementById('straightBar').style.width = Math.max(0, Math.min(1, straight)) * 100 + '%'
// Color coding: high straightness = red (suspicious), low straightness = green (normal)
const straightBar = document.getElementById('straightBar')
if(straight > 0.6) straightBar.style.background = '#D92D20' // Red for very straight
else if(straight > 0.3) straightBar.style.background = '#F59E0B' // Yellow for medium
else straightBar.style.background = '#13A964' // Green for curved
}
// ML Model visualization
if(mlScore !== null){
document.getElementById('mlScoreVal').textContent = mlScore.toFixed(3)
document.getElementById('mlScoreBar').style.width = Math.max(0, Math.min(1, mlScore)) * 100 + '%'
// Color coding: high ML score = red (anomaly), low ML score = green (normal)
const mlScoreBar = document.getElementById('mlScoreBar')
if(mlScore > 0.7) mlScoreBar.style.background = '#D92D20' // Red for high anomaly
else if(mlScore > 0.3) mlScoreBar.style.background = '#F59E0B' // Yellow for medium
else mlScoreBar.style.background = '#13A964' // Green for low anomaly
}
// Model confidence (based on how much ML vs heuristic contributes)
if(mlScore !== null && heuristicScore !== null){
const confidence = Math.abs(mlScore - heuristicScore) < 0.2 ? 0.9 : 0.6 // High confidence if scores agree
document.getElementById('mlConfidenceVal').textContent = confidence.toFixed(2)
document.getElementById('mlConfidenceBar').style.width = confidence * 100 + '%'
// Color coding: high confidence = blue, low confidence = orange
const confidenceBar = document.getElementById('mlConfidenceBar')
if(confidence > 0.8) confidenceBar.style.background = '#1E59F0' // Blue for high confidence
else if(confidence > 0.6) confidenceBar.style.background = '#F59E0B' // Orange for medium
else confidenceBar.style.background = '#D92D20' // Red for low confidence
}
}catch(e){ document.getElementById('evidence').textContent = 'Waiting for data…' }
}
setInterval(loadRisk, 2000); loadRisk()
async function loadDevices(){
try{
const resp = await fetch(API + '/trust/devices')
if(!resp.ok) throw new Error()
const items = await resp.json()
const html = items.map(x => `<div style="display:flex;justify-content:space-between;margin:6px 0"><span>${x.vid}/${x.pid}</span><span>${x.count}</span></div>`).join('')
document.getElementById('devices').innerHTML = html || 'No inventory yet.'
}catch{ document.getElementById('devices').textContent = 'No inventory yet.' }
}
setInterval(loadDevices, 3000); loadDevices()
// Seed/Clear buttons
document.getElementById('seedBtn').onclick = async () => {
try{ await fetch(API + '/demo/seed', { method:'POST' }); loadRisk(); loadDevices(); } catch{}
}
document.getElementById('clearBtn').onclick = async () => {
try{ await fetch(API + '/demo/clear', { method:'POST' }); loadRisk(); loadDevices(); } catch{}
}
// Model meta
async function loadModel(){
try{
const resp = await fetch(API + '/model/meta')
if(!resp.ok) throw new Error()
const meta = await resp.json()
// Update model status
let status = 'Unknown'
if(meta.modelPresent && meta.oneClassSvmPresent){
status = 'Level 2 Active (Isolation Forest + One-Class SVM)'
} else if(meta.modelPresent){
status = 'Level 2 Partial (Isolation Forest only)'
} else {
status = 'Level 1 Only (Heuristics)'
}
document.getElementById('modelStatus').textContent = status
// Show detailed model info in evidence section for debugging
if(meta.level && meta.level.includes('Level 2')){
document.getElementById('evidence').textContent =
JSON.stringify({...meta, note: 'Level 2 ML models are active and contributing to risk assessment'}, null, 2)
}
}catch{
document.getElementById('modelStatus').textContent = 'Error loading model info'
}
}
document.getElementById('refreshModel').onclick = loadModel
setInterval(loadModel, 10000); loadModel()
// Machines list
async function loadMachines(){
try{
const resp = await fetch(API + '/machines')
if(!resp.ok) throw new Error()
const items = await resp.json()
const html = items.map(x => {
const hb = x.lastHeartbeat ? new Date(x.lastHeartbeat).toLocaleTimeString() : '—'
const ft = x.lastFeatures ? new Date(x.lastFeatures).toLocaleTimeString() : '—'
return `<div class="row" data-id="${x.machineId}" style="display:flex;justify-content:space-between;margin:6px 0;cursor:pointer"><span>${x.machineId}</span><span class="muted">hb:${hb} · ft:${ft}</span></div>`
}).join('')
document.getElementById('machines').innerHTML = html || 'No machines yet.'
document.querySelectorAll('#machines .row').forEach(el => el.addEventListener('click', () => selectMachine(el.dataset.id)))
}catch{ document.getElementById('machines').textContent = 'No machines yet.' }
}
setInterval(loadMachines, 5000); loadMachines()
// Risk history
async function loadHistory(){
const machineId = 'demo-machine'
try{
const resp = await fetch(`${API}/risk/${machineId}/history?take=20`)
if(!resp.ok) throw new Error()
const items = await resp.json()
const html = items.map(x => {
const t = new Date(x.timestampUtc).toLocaleTimeString()
const s = (x.riskScore ?? 0).toFixed(2)
return `<div style="display:flex;justify-content:space-between;margin:6px 0"><span>${t}</span><span>${s}</span></div>`
}).join('')
document.getElementById('history').innerHTML = html || 'No history yet.'
// Match heuristics height by measuring that card's content height
const heuristicsContent = document.querySelector('.card h3 + .content')
const targetH = heuristicsContent ? heuristicsContent.clientHeight : 180
document.getElementById('historyContainer').style.height = targetH + 'px'
}catch{ document.getElementById('history').textContent = 'No history yet.' }
}
setInterval(loadHistory, 4000); loadHistory()
// Select machine
function selectMachine(id){
document.getElementById('machineId').textContent = id
loadRisk(); loadHistory();
}
// Evict stale
document.getElementById('evictBtn').onclick = async () => {
try{
await fetch(API + '/admin/evict', { method:'POST' })
loadMachines(); loadDevices();
}catch{}
}
</script>
</body>
</html>