← back to Vendor Agents Viewer
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
c19c33c606bc9b2068560cca348fcc55c7e3ea18 · 2026-08-12 08:53:19 -0700 · Steve
Files touched
Diff
commit c19c33c606bc9b2068560cca348fcc55c7e3ea18
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 08:53:19 2026 -0700
viewer standard: add Grid/List toggle + per-field on/off menu to internal record-grid viewer
---
public/index.html | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 74 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index 6ae9e76..3077e0a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -15,7 +15,35 @@
.controls label{color:var(--mut);font-size:12px;display:flex;gap:6px;align-items:center}
select,input[type=search]{background:#12141a;color:var(--txt);border:1px solid var(--line);border-radius:7px;padding:6px 9px;font-size:13px}
input[type=range]{accent-color:var(--accent)}
+ /* view toggle */
+ .vav-vseg{display:flex;border:1px solid var(--line);border-radius:7px;overflow:hidden}
+ .vav-vseg button{padding:5px 11px;background:#12141a;border:none;font-size:12px;color:var(--mut);cursor:pointer;font-family:inherit}
+ .vav-vseg button.on{background:var(--accent);color:#0f1115}
+ /* fields menu */
+ .vav-fieldmenu{position:relative}
+ .vav-fbtn{background:#12141a;color:var(--mut);border:1px solid var(--line);border-radius:7px;padding:6px 10px;font-size:12px;cursor:pointer}
+ .vav-fbtn:hover{border-color:var(--accent);color:var(--txt)}
+ .vav-fpop{position:absolute;top:calc(100% + 6px);right:0;background:#12141a;border:1px solid var(--line);border-radius:9px;box-shadow:0 10px 28px rgba(0,0,0,.6);padding:8px 4px;z-index:30;min-width:175px}
+ .vav-fpop[hidden]{display:none}
+ .vav-fpop label{display:flex;align-items:center;gap:8px;padding:6px 12px;font-size:12px;color:var(--mut);cursor:pointer;white-space:nowrap}
+ .vav-fpop label:hover{color:var(--txt)}
+ .vav-fpop input{accent-color:var(--accent);width:13px;height:13px;margin:0;padding:0}
+ /* grid view */
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px;padding:18px 22px}
+ /* list view */
+ .grid.list{display:flex;flex-direction:column;gap:6px;padding:14px 22px}
+ .grid.list .card{flex-direction:row;align-items:flex-start;border-radius:8px;padding:10px 14px;gap:14px;flex-wrap:wrap}
+ .grid.list .card .row1{flex:1;min-width:180px}
+ .grid.list .card .desc{flex:2;min-width:160px;min-height:0}
+ .grid.list .card .kv{flex:2;min-width:180px}
+ .grid.list .card .btns{flex:1;min-width:120px;margin-top:0}
+ /* field visibility */
+ body.vav-hf-desc [data-f="vav-desc"]{display:none!important}
+ body.vav-hf-kv [data-f="vav-kv"]{display:none!important}
+ body.vav-hf-when [data-f="vav-when"]{display:none!important}
+ body.vav-hf-scraper [data-f="vav-scraper"]{display:none!important}
+ body.vav-hf-btns [data-f="vav-btns"]{display:none!important}
+ body.vav-hf-badge [data-f="vav-badge"]{display:none!important}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:14px;display:flex;flex-direction:column;gap:8px}
.card.on{border-left:3px solid var(--on)} .card.off{border-left:3px solid var(--off)} .card.conflict{border-left:3px solid var(--conf)}
.row1{display:flex;justify-content:space-between;align-items:flex-start;gap:8px}
@@ -52,6 +80,14 @@
</label>
<label>Show <select id="filter"><option value="all">All</option><option value="online">Online</option><option value="offline">Offline</option><option value="conflict">Port Conflict</option></select></label>
<label>Density <input type="range" id="dens" min="2" max="7" value="4"></label>
+ <div class="vav-vseg">
+ <button id="vavVGrid" class="on" title="Grid view">⊞ Grid</button>
+ <button id="vavVList" title="List view">☰ List</button>
+ </div>
+ <div class="vav-fieldmenu">
+ <button class="vav-fbtn" id="vavFieldsBtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+ <div class="vav-fpop" id="vavFpop" hidden role="menu"></div>
+ </div>
<button class="act" onclick="load()">↻ Refresh</button>
</div>
</header>
@@ -96,19 +132,19 @@ function render(){
<div class="card ${x.status}">
<div class="row1">
<div><div class="nm">${x.agent_name||'(unnamed)'}</div><div class="ven">${x.vendor_name?drill('vendor',x.vendor_name,x.vendor_name):''}${x.agent_port?' · :'+x.agent_port:''}</div></div>
- ${drill('status',x.status,x.status,'badge '+x.status)}
+ <span data-f="vav-badge">${drill('status',x.status,x.status,'badge '+x.status)}</span>
</div>
- <div class="desc">${x.description||''}</div>
- <div class="kv">
+ <div class="desc" data-f="vav-desc">${x.description||''}</div>
+ <div class="kv" data-f="vav-kv">
<span>Catalog <b>${(x.catalog_count||0).toLocaleString()}</b></span>
<span>Shopify <b>${(x.shopify_count||0).toLocaleString()}</b></span>
<span>Images <b>${(x.catalog_with_images||0).toLocaleString()}</b></span>
${x.website_score!=null?`<span>Site <b>${x.website_score}</b></span>`:''}
</div>
- ${x.conflict_owner?`<div class="when" style="color:var(--conf)">\u26a0 port :${x.agent_port} reused by <b>${x.conflict_owner}</b> \u2014 agent not running</div>`:""}
- <div class="when" title="${x.updated_at||''}">🕓 updated ${fmtDate(x.updated_at)}</div>
- <div class="kv"><span>Scraper <b style="color:${x.launchable?'var(--on)':'var(--off)'}">${x.scraper_id||(x.skip_catalog_scrape?(x.is_private_label?'n/a — private label':'n/a — no source'):'none built')}</b></span></div>
- <div class="btns">
+ ${x.conflict_owner?`<div class="when" data-f="vav-when" style="color:var(--conf)">\u26a0 port :${x.agent_port} reused by <b>${x.conflict_owner}</b> \u2014 agent not running</div>`:""}
+ <div class="when" data-f="vav-when" title="${x.updated_at||''}">🕓 updated ${fmtDate(x.updated_at)}</div>
+ <div class="kv" data-f="vav-scraper"><span>Scraper <b style="color:${x.launchable?'var(--on)':'var(--off)'}">${x.scraper_id||(x.skip_catalog_scrape?(x.is_private_label?'n/a — private label':'n/a — no source'):'none built')}</b></span></div>
+ <div class="btns" data-f="vav-btns">
${x.url?`<a class="act go" href="${x.url}" target="_blank">Open</a>`:''}
${x.status==='online'
? `<button class="act" onclick='act("restart",{vendor_code:${JSON.stringify(x.vendor_code)}},"restart ${x.agent_name}")'>Restart</button>
@@ -124,6 +160,37 @@ async function load(){
$('#stats').innerHTML=`<span><b>${s.total}</b> agents</span><span><b style="color:var(--on)">${s.online}</b> online</span><span><b>${s.offline}</b> offline</span><span><b style="color:#f59e0b">${s.conflict||0}</b> port-conflict</span><span><b>${s.totalCatalog.toLocaleString()}</b> catalog SKUs</span><span><b>${s.totalShopify.toLocaleString()}</b> on Shopify</span>`;
render();
}
+// ── Grid / List toggle + Fields menu ──
+const VAV_FIELDS=[['desc','Description'],['kv','Counts (Catalog/Shopify)'],['when','Updated Date'],['scraper','Scraper ID'],['btns','Action Buttons'],['badge','Status Badge']];
+const VAV_FIELDS_DEF=Object.fromEntries(VAV_FIELDS.map(([k])=>[k,true]));
+let vavFieldVis=Object.assign({},VAV_FIELDS_DEF,JSON.parse(localStorage.getItem('vav_fields')||'{}'));
+function applyVavFields(){
+ VAV_FIELDS.forEach(([k])=>document.body.classList.toggle('vav-hf-'+k,!vavFieldVis[k]));
+ document.querySelectorAll('#vavFpop input[data-field]').forEach(cb=>cb.checked=!!vavFieldVis[cb.dataset.field]);
+ localStorage.setItem('vav_fields',JSON.stringify(vavFieldVis));
+}
+function buildVavFieldMenu(){
+ const fp=document.getElementById('vavFpop');
+ fp.innerHTML=VAV_FIELDS.map(([k,lbl])=>`<label><input type="checkbox" data-field="${k}"${vavFieldVis[k]?' checked':''}> ${lbl}</label>`).join('');
+ fp.querySelectorAll('input').forEach(cb=>cb.addEventListener('change',()=>{vavFieldVis[cb.dataset.field]=cb.checked;localStorage.setItem('vav_fields',JSON.stringify(vavFieldVis));applyVavFields();}));
+}
+function setVavView(v){
+ document.getElementById('grid').classList.toggle('list',v==='list');
+ document.getElementById('vavVGrid').classList.toggle('on',v==='grid');
+ document.getElementById('vavVList').classList.toggle('on',v==='list');
+ localStorage.setItem('vav_view',v);
+}
+document.getElementById('vavVGrid').onclick=()=>setVavView('grid');
+document.getElementById('vavVList').onclick=()=>setVavView('list');
+setVavView(localStorage.getItem('vav_view')||'grid');
+buildVavFieldMenu();
+applyVavFields();
+(function(){
+ const btn=document.getElementById('vavFieldsBtn'), fp=document.getElementById('vavFpop');
+ btn.addEventListener('click',e=>{e.stopPropagation();const h=fp.hidden;fp.hidden=!h;btn.setAttribute('aria-expanded',String(h));});
+ document.addEventListener('click',()=>{if(!fp.hidden){fp.hidden=true;btn.setAttribute('aria-expanded','false');}});
+ fp.addEventListener('click',e=>e.stopPropagation());
+})();
// persist controls
['sort','filter','dens'].forEach(id=>{const el=$('#'+id);const k='vav_'+id;if(localStorage[k])el.value=localStorage[k];el.addEventListener('input',()=>{localStorage[k]=el.value;if(id==='dens')document.documentElement.style.setProperty('--cols',el.value);else render();});});
document.documentElement.style.setProperty('--cols',$('#dens').value);
← c4e0072 vendor-agents-viewer: href-drill — data points now filter-li
·
back to Vendor Agents Viewer
·
TK-11438: migrate postgres pool to unix socket on darwin f19f192 →