Add total count badges beside page titles in admin

- KB Facts: shows total fact count (sum across all categories)
- Network Devices: shows online/total count in title
- Home Assistant Entities: shows entity count in title
- Proxmox VMs: shows running/total VM count in title
- KB Intents already had this; no change needed
This commit is contained in:
2026-06-01 14:22:36 +00:00
parent 0caff5db57
commit 78c95a508d
+11 -5
View File
@@ -718,7 +718,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
<!-- NETWORK --> <!-- NETWORK -->
<div class="tab" id="tab-network"> <div class="tab" id="tab-network">
<div class="page-title">NETWORK DEVICES <div class="page-title">NETWORK DEVICES <span id="net-title-count" style="font-size:0.6rem;color:var(--dim);letter-spacing:1px;font-weight:400"></span>
<div class="actions"> <div class="actions">
<button class="btn btn-sm btn-green" onclick="netModal()">+ ADD DEVICE</button> <button class="btn btn-sm btn-green" onclick="netModal()">+ ADD DEVICE</button>
<button class="btn btn-sm btn-yellow" id="scanBtn" onclick="scanNow()">SCAN NOW</button> <button class="btn btn-sm btn-yellow" id="scanBtn" onclick="scanNow()">SCAN NOW</button>
@@ -757,7 +757,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
<!-- KB FACTS --> <!-- KB FACTS -->
<div class="tab" id="tab-facts"> <div class="tab" id="tab-facts">
<div class="page-title">KB FACTS <div class="page-title">KB FACTS <span id="facts-count" style="font-size:0.6rem;color:var(--dim);letter-spacing:1px;font-weight:400"></span>
<div class="actions"> <div class="actions">
<button class="btn btn-sm btn-green" onclick="factModal()">+ ADD FACT</button> <button class="btn btn-sm btn-green" onclick="factModal()">+ ADD FACT</button>
<button class="btn btn-sm" onclick="loadFacts()">REFRESH</button> <button class="btn btn-sm" onclick="loadFacts()">REFRESH</button>
@@ -774,7 +774,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
<!-- KB INTENTS --> <!-- KB INTENTS -->
<div class="tab" id="tab-intents"> <div class="tab" id="tab-intents">
<div class="page-title">KB INTENTS <div class="page-title">KB INTENTS <span id="intents-count" style="font-size:0.6rem;color:var(--dim);letter-spacing:1px;font-weight:400"></span>
<div class="actions"> <div class="actions">
<button class="btn btn-sm btn-green" onclick="intentModal()">+ ADD INTENT</button> <button class="btn btn-sm btn-green" onclick="intentModal()">+ ADD INTENT</button>
<button class="btn btn-sm" onclick="loadIntents()">REFRESH</button> <button class="btn btn-sm" onclick="loadIntents()">REFRESH</button>
@@ -785,7 +785,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
<!-- HOME ASSISTANT --> <!-- HOME ASSISTANT -->
<div class="tab" id="tab-ha"> <div class="tab" id="tab-ha">
<div class="page-title">HOME ASSISTANT ENTITIES <div class="page-title">HOME ASSISTANT ENTITIES <span id="ha-title-count" style="font-size:0.6rem;color:var(--dim);letter-spacing:1px;font-weight:400"></span>
<div class="actions"><button class="btn btn-sm" onclick="loadHA()">REFRESH</button></div> <div class="actions"><button class="btn btn-sm" onclick="loadHA()">REFRESH</button></div>
</div> </div>
<div class="filters"> <div class="filters">
@@ -821,7 +821,7 @@ select.filter-sel:focus{border-color:var(--cyan)}
<!-- PROXMOX VMs --> <!-- PROXMOX VMs -->
<div class="tab" id="tab-vms"> <div class="tab" id="tab-vms">
<div class="page-title">PROXMOX VMs <div class="page-title">PROXMOX VMs <span id="vms-count" style="font-size:0.6rem;color:var(--dim);letter-spacing:1px;font-weight:400"></span>
<div class="actions"><button class="btn btn-sm" onclick="loadVMs()">REFRESH</button></div> <div class="actions"><button class="btn btn-sm" onclick="loadVMs()">REFRESH</button></div>
</div> </div>
<div class="tbl-wrap" id="vms-tbl"><div class="loading">SCANNING...</div></div> <div class="tbl-wrap" id="vms-tbl"><div class="loading">SCANNING...</div></div>
@@ -1230,6 +1230,7 @@ function renderNetwork() {
if (_netFilter === 'named') devs = devs.filter(d => d.alias); if (_netFilter === 'named') devs = devs.filter(d => d.alias);
const onlineCount = _allDevices.filter(d=>d.status==='online').length; const onlineCount = _allDevices.filter(d=>d.status==='online').length;
document.getElementById('net-count').textContent = `${onlineCount}/${_allDevices.length} ONLINE`; document.getElementById('net-count').textContent = `${onlineCount}/${_allDevices.length} ONLINE`;
const _ntEl=document.getElementById('net-title-count'); if(_ntEl) _ntEl.textContent=`${onlineCount}/${_allDevices.length} ONLINE`;
if (!devs.length) { document.getElementById('network-tbl').innerHTML='<div class="empty">NO DEVICES MATCH FILTER</div>'; return; } if (!devs.length) { document.getElementById('network-tbl').innerHTML='<div class="empty">NO DEVICES MATCH FILTER</div>'; return; }
// Re-build shell (filter changed) // Re-build shell (filter changed)
document.getElementById('network-tbl').innerHTML = `<table> document.getElementById('network-tbl').innerHTML = `<table>
@@ -1354,6 +1355,8 @@ async function loadFactCategories() {
const sel = document.getElementById('factCat'); const sel = document.getElementById('factCat');
sel.innerHTML = '<option value="__all__">ALL CATEGORIES</option>' + sel.innerHTML = '<option value="__all__">ALL CATEGORIES</option>' +
cats.map(c=>`<option value="${esc(c.category)}">${esc(c.category)} (${c.cnt})</option>`).join(''); cats.map(c=>`<option value="${esc(c.category)}">${esc(c.category)} (${c.cnt})</option>`).join('');
const _factTotal = cats.reduce((s,c)=>s+parseInt(c.cnt||0),0);
const _factCntEl = document.getElementById('facts-count'); if(_factCntEl) _factCntEl.textContent=_factTotal.toLocaleString()+' FACTS';
} }
async function loadFacts() { async function loadFacts() {
@@ -1391,6 +1394,7 @@ function factModal(id=0, category='', key='', value='') {
async function loadIntents() { async function loadIntents() {
scanShell('intents-tbl', ['NAME','PATTERN','RESPONSE','TYPE','PRI','STATUS','ACTIONS'], null, null); scanShell('intents-tbl', ['NAME','PATTERN','RESPONSE','TYPE','PRI','STATUS','ACTIONS'], null, null);
const intents = await api('intents_list'); const intents = await api('intents_list');
const cntEl=document.getElementById('intents-count'); if(cntEl) cntEl.textContent=intents.length.toLocaleString()+' INTENTS';
if (!intents.length) { document.getElementById('intents-tbl').innerHTML='<div class="empty">NO INTENTS</div>'; return; } if (!intents.length) { document.getElementById('intents-tbl').innerHTML='<div class="empty">NO INTENTS</div>'; return; }
document.getElementById('intents-tbl').innerHTML = `<table> document.getElementById('intents-tbl').innerHTML = `<table>
<thead><tr><th>NAME</th><th>PATTERN</th><th>RESPONSE</th><th>TYPE</th><th style="text-align:center">PRI</th><th>STATUS</th><th>ACTIONS</th></tr></thead> <thead><tr><th>NAME</th><th>PATTERN</th><th>RESPONSE</th><th>TYPE</th><th style="text-align:center">PRI</th><th>STATUS</th><th>ACTIONS</th></tr></thead>
@@ -1509,6 +1513,7 @@ async function loadHA() {
if (cur) sel.value = cur; if (cur) sel.value = cur;
const age = data.ts ? Math.floor((Date.now()/1000)-data.ts) : null; const age = data.ts ? Math.floor((Date.now()/1000)-data.ts) : null;
document.getElementById('ha-count').textContent = `${_haEntities.length} ENTITIES${age!=null?' · CACHE '+age+'s AGO':''}`; document.getElementById('ha-count').textContent = `${_haEntities.length} ENTITIES${age!=null?' · CACHE '+age+'s AGO':''}`;
const _haTitleEl=document.getElementById('ha-title-count'); if(_haTitleEl) _haTitleEl.textContent=_haEntities.length.toLocaleString()+' ENTITIES';
renderHATable(_haEntities); renderHATable(_haEntities);
} }
@@ -1635,6 +1640,7 @@ async function loadVMs() {
document.getElementById('vms-tbl').innerHTML='<div class="loading">SCANNING...</div>'; document.getElementById('vms-tbl').innerHTML='<div class="loading">SCANNING...</div>';
const data = await api('vms_list'); const data = await api('vms_list');
const vms = [...(data.vms||[]), ...(data.containers||[])]; const vms = [...(data.vms||[]), ...(data.containers||[])];
const _vmsCntEl=document.getElementById('vms-count'); if(_vmsCntEl){const _vmRun=vms.filter(v=>v.status==='running').length;_vmsCntEl.textContent=`${_vmRun}/${vms.length} RUNNING`;}
if (!vms.length) { document.getElementById('vms-tbl').innerHTML='<div class="empty">NO VM DATA — Proxmox cache empty, refreshes every 5 min</div>'; return; } if (!vms.length) { document.getElementById('vms-tbl').innerHTML='<div class="empty">NO VM DATA — Proxmox cache empty, refreshes every 5 min</div>'; return; }
const ni = data.node_info||{}; const ni = data.node_info||{};