← back to Agent Cabinet
Activity viewer: live search box — filters the feed AND scheduled/continuous jobs by title/detail/type/label/cadence; chip counts track the query; clear button + 'no matches' state; persists across auto-refresh
c791b735527775902b3ccc49a9ef75f7fede101b · 2026-06-16 18:17:05 -0700 · SteveStudio2
Files touched
Diff
commit c791b735527775902b3ccc49a9ef75f7fede101b
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue Jun 16 18:17:05 2026 -0700
Activity viewer: live search box — filters the feed AND scheduled/continuous jobs by title/detail/type/label/cadence; chip counts track the query; clear button + 'no matches' state; persists across auto-refresh
---
activity.html | 31 +++++++++++++++++++++++--------
1 file changed, 23 insertions(+), 8 deletions(-)
diff --git a/activity.html b/activity.html
index f5ff26f..8bb3040 100644
--- a/activity.html
+++ b/activity.html
@@ -16,6 +16,11 @@
.chip.on{color:#fff}
.chip .dot{width:9px;height:9px;border-radius:50%}
.chip .n{font:11px ui-monospace,monospace;opacity:.8}
+ .searchrow{margin-top:11px;display:flex;align-items:center;gap:10px}
+ #search{flex:1;max-width:440px;background:#0a0e16;border:1px solid #2b3650;color:#e7ecf3;border-radius:8px;padding:8px 12px;font-size:13px}
+ #search:focus{outline:none;border-color:#34f4ce}
+ #search-x{color:var(--dim);font-size:12px;cursor:pointer;display:none}
+ #search-x.show{display:inline}
main{max-width:1340px;margin:0 auto;padding:18px 28px 70px}
.cols{display:grid;grid-template-columns:var(--col-w,380px) 14px minmax(0,1fr);align-items:start}
.col-up{position:sticky;top:132px;max-height:calc(100vh - 150px);overflow:auto;padding-right:6px}
@@ -67,6 +72,7 @@
<h1>What it's doing <small>live activity timeline — agents, canaries, watch ticks, commits, spend</small></h1>
<div class="meta"><span class="pulse"></span><span id="meta">loading…</span> · auto-refreshes every 30s · <a href="/pyramid">← cabinet pyramid</a></div>
<div class="chips" id="chips"></div>
+ <div class="searchrow"><input id="search" type="search" placeholder="🔎 filter events & jobs — title, vendor, repo, skill…" autocomplete="off"><span id="search-x" onclick="clearSearch()">✕ clear</span></div>
</header>
<div id="det"><div class="det-head"><span id="det-title">—</span><button class="det-x" onclick="closeDet()">✕</button></div><div id="det-body"></div></div>
<main class="cols">
@@ -83,16 +89,18 @@ const TYPES={
tick:{label:'Watch ticks', col:'#60a5fa'},
};
const VCOL={FAIL:'#ef4444',WARN:'#fbbf24',PASS:'#34d399',NA:'#6b7280'};
-let DATA=[], OFF_FILTER=new Set(); // types hidden
+let DATA=[], OFF_FILTER=new Set(), QUERY=''; // types hidden + search query
+function matchQ(e){ return !QUERY || ((e.title||'')+' '+(e.detail||'')+' '+(e.type||'')).toLowerCase().includes(QUERY); }
function colorFor(e){ if(e.type==='canary') return VCOL[e.verdict]||TYPES.canary.col; return (TYPES[e.type]||{}).col||'#2b3650'; }
function fmtTime(iso){ try{ return new Date(iso).toLocaleTimeString(undefined,{hour:'2-digit',minute:'2-digit'}); }catch(e){ return '--:--'; } }
function fmtDay(iso){ try{ return new Date(iso).toLocaleDateString(undefined,{weekday:'short',month:'short',day:'numeric',year:'numeric'}); }catch(e){ return iso; } }
function dayKey(iso){ try{ return new Date(iso).toISOString().slice(0,10); }catch(e){ return iso.slice(0,10); } }
function renderChips(){
- const counts={}; DATA.forEach(e=>counts[e.type]=(counts[e.type]||0)+1);
+ const set=DATA.filter(matchQ);
+ const counts={}; set.forEach(e=>counts[e.type]=(counts[e.type]||0)+1);
const c=document.getElementById('chips');
- c.innerHTML='<span class="chip '+(OFF_FILTER.size?'':'on')+'" data-t="__all"><b>All</b> <span class="n">'+DATA.length+'</span></span>'+
+ c.innerHTML='<span class="chip '+(OFF_FILTER.size?'':'on')+'" data-t="__all"><b>All</b> <span class="n">'+set.length+'</span></span>'+
Object.keys(TYPES).map(t=>`<span class="chip ${OFF_FILTER.has(t)?'':'on'}" data-t="${t}"><span class="dot" style="background:${TYPES[t].col}"></span>${TYPES[t].label} <span class="n">${counts[t]||0}</span></span>`).join('');
c.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{
const t=ch.dataset.t;
@@ -101,9 +109,9 @@ function renderChips(){
});
}
function renderFeed(){
- const shown=DATA.filter(e=>!OFF_FILTER.has(e.type));
+ const shown=DATA.filter(e=>!OFF_FILTER.has(e.type) && matchQ(e));
const feed=document.getElementById('feed');
- if(!shown.length){ feed.innerHTML='<div class="empty">No events for this filter.</div>'; return; }
+ if(!shown.length){ feed.innerHTML='<div class="empty">'+(QUERY?'No matches for “'+esc(QUERY)+'”.':'No events for this filter.')+'</div>'; return; }
let html='', curDay=null;
shown.forEach(e=>{
const dk=dayKey(e.ts);
@@ -125,10 +133,14 @@ async function load(){
}
function rel(ms){ const d=ms-Date.now(); if(d<=0) return 'now'; const m=Math.round(d/60000); if(m<60) return 'in '+m+'m'; const h=Math.floor(m/60); return 'in '+h+'h '+String(m%60).padStart(2,'0')+'m'; }
function fmtAbs(iso){ try{ return new Date(iso).toLocaleString(undefined,{weekday:'short',hour:'2-digit',minute:'2-digit'}); }catch(e){ return iso; } }
-let SHOW_ALL_UP=false;
+let SHOW_ALL_UP=false, SCHED={upcoming:[],continuous:[]};
async function loadSchedule(){
- let r; try{ r=await(await fetch('/api/schedule')).json(); }catch(e){ return; }
- const up=r.upcoming||[], cont=r.continuous||[];
+ try{ const r=await(await fetch('/api/schedule')).json(); SCHED={upcoming:r.upcoming||[],continuous:r.continuous||[]}; }catch(e){}
+ renderSchedule();
+}
+function renderSchedule(){
+ let up=SCHED.upcoming, cont=SCHED.continuous;
+ if(QUERY){ up=up.filter(e=>((e.label||'')+' '+(e.cadence||'')).toLowerCase().includes(QUERY)); cont=cont.filter(c=>((c.label||'')+' '+(c.cadence||'')).toLowerCase().includes(QUERY)); }
const n=SHOW_ALL_UP?up.length:14;
let h='<div class="up-head">⏭ Upcoming <span class="s">— '+up.length+' scheduled jobs · next fire</span></div>';
h+=up.slice(0,n).map(e=>`<div class="up-row" data-label="${escA(e.label)}"><span class="rel">${rel(Date.parse(e.next_ts))}</span><span class="abs">${fmtAbs(e.next_ts)}</span><span class="lab">${esc(e.label)}</span><span class="cad">${esc(e.cadence)}</span></div>`).join('');
@@ -192,6 +204,9 @@ document.addEventListener('keydown', e=>{ if(e.key==='Escape') closeDet(); });
window.addEventListener('mouseup', ()=>{ if(!dragging) return; dragging=false; sp.classList.remove('drag'); document.body.style.userSelect=''; const w=parseInt(cols.style.getPropertyValue('--col-w'),10); if(w) localStorage.setItem(KEY,w); });
sp.addEventListener('dblclick', ()=>{ cols.style.setProperty('--col-w','380px'); localStorage.setItem(KEY,'380'); });
})();
+const _si=document.getElementById('search');
+_si.addEventListener('input', ()=>{ QUERY=_si.value.trim().toLowerCase(); document.getElementById('search-x').classList.toggle('show', !!QUERY); renderChips(); renderFeed(); renderSchedule(); });
+function clearSearch(){ _si.value=''; QUERY=''; document.getElementById('search-x').classList.remove('show'); _si.focus(); renderChips(); renderFeed(); renderSchedule(); }
load(); loadSchedule(); setInterval(()=>{ load(); loadSchedule(); }, 30000);
</script>
</body></html>
← 9727194 Activity viewer: click any process/event for details — detai
·
back to Agent Cabinet
·
Activity viewer: date-range filter — from/to date pickers + c18a8e8 →