Compact sentinel card: single-line with mini ring + collapsible verdicts

- Entire sentinel status fits in one header row now
- Mini 28px countdown ring (was 64px) inline with title
- Scans/bans counts inline as text, not grid boxes
- Verdicts collapsed by default — click to expand
- Card padding reduced (8px vs 14px)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-26 04:03:57 -05:00
parent 3cdfc01835
commit 357918013d

View File

@ -740,7 +740,7 @@ async function loadThreats() {
// Sentinel status card // Sentinel status card
var sentinelCard = document.createElement('div'); var sentinelCard = document.createElement('div');
sentinelCard.style.cssText = 'background:rgba(0,0,0,0.3);border:2px solid rgba(217,70,239,0.3);border-radius:2px;padding:14px;margin-bottom:16px;backdrop-filter:blur(16px)'; sentinelCard.style.cssText = 'background:rgba(0,0,0,0.3);border:2px solid rgba(217,70,239,0.3);border-radius:2px;padding:8px 12px;margin-bottom:12px;backdrop-filter:blur(16px)';
var sHeader = document.createElement('div'); var sHeader = document.createElement('div');
sHeader.style.cssText = 'display:flex;align-items:center;gap:8px;margin-bottom:8px'; sHeader.style.cssText = 'display:flex;align-items:center;gap:8px;margin-bottom:8px';
var sDot = document.createElement('div'); var sDot = document.createElement('div');
@ -748,85 +748,61 @@ async function loadThreats() {
var sTitle = document.createElement('span'); var sTitle = document.createElement('span');
sTitle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#d946ef;font-weight:700'; sTitle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:11px;text-transform:uppercase;letter-spacing:1.5px;color:#d946ef;font-weight:700';
sTitle.textContent = 'AI Sentinel — ' + (sentinel.model || '?'); sTitle.textContent = 'AI Sentinel — ' + (sentinel.model || '?');
sHeader.appendChild(sDot);sHeader.appendChild(sTitle);sentinelCard.appendChild(sHeader); sHeader.appendChild(sDot);sHeader.appendChild(sTitle);
// Countdown + metrics row // Inline stats + countdown all in one row
var ss = sentinel.stats || {}; var ss = sentinel.stats || {};
var nextIn = sentinel.next_scan_in || 0; var nextIn = sentinel.next_scan_in || 0;
var interval = sentinel.interval || 300; var interval = sentinel.interval || 300;
var metricsRow = document.createElement('div');
metricsRow.style.cssText = 'display:grid;grid-template-columns:auto 1fr;gap:14px;align-items:center;margin-bottom:10px';
// Countdown ring
var ringWrap = document.createElement('div');
ringWrap.style.cssText = 'position:relative;width:64px;height:64px;flex-shrink:0';
var pct = interval > 0 ? ((interval - nextIn) / interval) : 0; var pct = interval > 0 ? ((interval - nextIn) / interval) : 0;
var deg = Math.round(pct * 360);
ringWrap.innerHTML = '<svg width="64" height="64" viewBox="0 0 64 64">'
+ '<circle cx="32" cy="32" r="28" fill="none" stroke="#2a2d35" stroke-width="4"/>'
+ '<circle cx="32" cy="32" r="28" fill="none" stroke="#d946ef" stroke-width="4" stroke-linecap="round"'
+ ' stroke-dasharray="' + (2 * Math.PI * 28).toFixed(1) + '"'
+ ' stroke-dashoffset="' + ((1 - pct) * 2 * Math.PI * 28).toFixed(1) + '"'
+ ' transform="rotate(-90 32 32)" style="transition:stroke-dashoffset 1s"/>'
+ '</svg>';
var countText = document.createElement('div');
countText.id = 'sentinel-countdown';
countText.style.cssText = 'position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:JetBrains Mono,monospace';
var countNum = document.createElement('div');
countNum.style.cssText = 'font-size:16px;font-weight:700;color:#d946ef;line-height:1';
countNum.textContent = Math.ceil(nextIn) + 's';
var countLabel = document.createElement('div');
countLabel.style.cssText = 'font-size:7px;color:#7a7872;text-transform:uppercase;letter-spacing:1px;margin-top:2px';
countLabel.textContent = 'next scan';
countText.appendChild(countNum); countText.appendChild(countLabel);
ringWrap.appendChild(countText);
metricsRow.appendChild(ringWrap);
// Stats grid // Mini ring
var statsGrid = document.createElement('div'); var ring = document.createElement('span');
statsGrid.style.cssText = 'display:grid;grid-template-columns:repeat(4,1fr);gap:6px'; ring.style.cssText = 'position:relative;width:28px;height:28px;flex-shrink:0;display:inline-block;vertical-align:middle;margin-left:auto';
[{v:ss.scans||0,l:'Scans',c:'#d946ef'},{v:ss.bans||0,l:'AI Bans',c:'#e05252'},{v:ss.last_run||'',l:'Last Run',c:'#e8e6e3',small:true},{v:(sentinel.interval||300)+'s',l:'Interval',c:'#7a7872'}].forEach(function(m){ ring.innerHTML = '<svg width="28" height="28" viewBox="0 0 28 28"><circle cx="14" cy="14" r="11" fill="none" stroke="#2a2d35" stroke-width="2.5"/><circle cx="14" cy="14" r="11" fill="none" stroke="#d946ef" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="'+(2*Math.PI*11).toFixed(1)+'" stroke-dashoffset="'+((1-pct)*2*Math.PI*11).toFixed(1)+'" transform="rotate(-90 14 14)" style="transition:stroke-dashoffset 1s"/></svg>';
var box = document.createElement('div'); var countTxt = document.createElement('span');
box.style.cssText = 'text-align:center'; countTxt.id = 'sentinel-countdown';
var val = document.createElement('div'); countTxt.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:JetBrains Mono,monospace;font-size:8px;font-weight:700;color:#d946ef';
val.style.cssText = 'font-family:JetBrains Mono,monospace;font-weight:700;color:'+m.c+';font-size:'+(m.small?'10px':'14px'); countTxt.textContent = Math.ceil(nextIn) + '';
val.textContent = m.v; ring.appendChild(countTxt);
var lab = document.createElement('div'); sHeader.appendChild(ring);
lab.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:7px;text-transform:uppercase;letter-spacing:1px;color:#7a7872;margin-top:2px';
lab.textContent = m.l;
box.appendChild(val);box.appendChild(lab);statsGrid.appendChild(box);
});
metricsRow.appendChild(statsGrid);
sentinelCard.appendChild(metricsRow);
// Start countdown timer // Compact stats inline
var inlineStats = document.createElement('span');
inlineStats.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:#7a7872;display:flex;gap:10px;margin-left:8px';
inlineStats.innerHTML = '<span><b style="color:#d946ef">'+(ss.scans||0)+'</b> scans</span><span><b style="color:#e05252">'+(ss.bans||0)+'</b> bans</span>';
sHeader.appendChild(inlineStats);
sentinelCard.appendChild(sHeader);
// Start countdown
if (window._sentinelTimer) clearInterval(window._sentinelTimer); if (window._sentinelTimer) clearInterval(window._sentinelTimer);
window._sentinelCountdown = nextIn; window._sentinelCountdown = nextIn;
window._sentinelTimer = setInterval(function(){ window._sentinelTimer = setInterval(function(){
window._sentinelCountdown = Math.max(0, window._sentinelCountdown - 1); window._sentinelCountdown = Math.max(0, window._sentinelCountdown - 1);
var el = document.getElementById('sentinel-countdown'); var el = document.getElementById('sentinel-countdown');
if (el) el.querySelector('div').textContent = Math.ceil(window._sentinelCountdown) + 's'; if (el) { el.textContent = Math.ceil(window._sentinelCountdown) || '...'; if (window._sentinelCountdown <= 0) { el.textContent = ''; el.style.color = '#4ade80'; clearInterval(window._sentinelTimer); } }
if (window._sentinelCountdown <= 0) {
clearInterval(window._sentinelTimer);
var el2 = document.getElementById('sentinel-countdown');
if (el2) { el2.querySelector('div').textContent = 'scanning...'; el2.querySelector('div').style.color = '#4ade80'; }
}
}, 1000); }, 1000);
if (ss.last_error) { if (ss.last_error) {
var sErr = document.createElement('div'); var sErr = document.createElement('div');
sErr.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:#e05252;border-left:2px solid #e05252;padding-left:8px;margin-bottom:8px'; sErr.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:9px;color:#e05252;border-left:2px solid #e05252;padding-left:8px;margin-bottom:4px';
sErr.textContent = 'Last error: ' + ss.last_error; sErr.textContent = 'Error: ' + ss.last_error;
sentinelCard.appendChild(sErr); sentinelCard.appendChild(sErr);
} }
// Recent AI verdicts // Recent AI verdicts collapsible
var verdicts = sentinel.recent_verdicts || []; var verdicts = sentinel.recent_verdicts || [];
if (verdicts.length) { if (verdicts.length) {
var vTitle = document.createElement('div'); var vToggle = document.createElement('div');
vTitle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:8px;text-transform:uppercase;letter-spacing:2px;color:#d946ef;margin:10px 0 6px;opacity:0.6'; vToggle.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:8px;text-transform:uppercase;letter-spacing:1.5px;color:#d946ef;margin:4px 0 0;opacity:0.5;cursor:pointer';
vTitle.textContent = 'Recent AI Verdicts'; vToggle.textContent = '' + verdicts.length + ' recent verdicts';
sentinelCard.appendChild(vTitle); var vList = document.createElement('div');
vList.style.display = 'none';
vToggle.onclick = function(){
if (vList.style.display === 'none') { vList.style.display = 'block'; vToggle.textContent = '' + verdicts.length + ' recent verdicts'; vToggle.style.opacity = '1'; }
else { vList.style.display = 'none'; vToggle.textContent = '' + verdicts.length + ' recent verdicts'; vToggle.style.opacity = '0.5'; }
};
sentinelCard.appendChild(vToggle);
verdicts.slice(0,8).forEach(function(v){ verdicts.slice(0,8).forEach(function(v){
var vLine = document.createElement('div'); var vLine = document.createElement('div');
vLine.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;color:#7a7872;padding:3px 0;border-bottom:1px solid rgba(42,45,53,0.3);display:flex;gap:8px'; vLine.style.cssText = 'font-family:JetBrains Mono,monospace;font-size:10px;color:#7a7872;padding:3px 0;border-bottom:1px solid rgba(42,45,53,0.3);display:flex;gap:8px';
@ -835,8 +811,9 @@ async function loadThreats() {
+ '<span style="min-width:120px">'+esc(v.ip||'?')+'</span>' + '<span style="min-width:120px">'+esc(v.ip||'?')+'</span>'
+ '<span style="color:#c084fc">'+esc(v.attack_type||'?')+'</span>' + '<span style="color:#c084fc">'+esc(v.attack_type||'?')+'</span>'
+ '<span style="flex:1;opacity:0.6">'+esc(v.reason||'')+'</span>'; + '<span style="flex:1;opacity:0.6">'+esc(v.reason||'')+'</span>';
sentinelCard.appendChild(vLine); vList.appendChild(vLine);
}); });
sentinelCard.appendChild(vList);
} }
view.appendChild(sentinelCard); view.appendChild(sentinelCard);