base dashboard and login

This commit is contained in:
2026-05-17 08:28:16 +00:00
parent 64f4f3c5d4
commit 7066415af5
39 changed files with 3327 additions and 72 deletions
+48
View File
@@ -0,0 +1,48 @@
(function () {
'use strict';
var pollInterval = (window._pollInterval || 15) * 1000;
var cliAvailable = !!window._cliAvailable;
var elDecisions = document.getElementById('stat-decisions');
var elAlerts = document.getElementById('stat-alerts');
var elBouncers = document.getElementById('stat-bouncers');
var elMachines = document.getElementById('stat-machines');
function animateTo(el, newVal) {
if (!el) return;
var current = parseInt(el.textContent, 10);
if (isNaN(current) || current === newVal) {
el.textContent = newVal;
return;
}
// Brief flash transition
el.style.opacity = '0.4';
setTimeout(function () {
el.textContent = newVal;
el.style.opacity = '1';
}, 150);
}
function fetchStats() {
fetch('/api/v1/stats')
.then(function (r) {
if (!r.ok) throw new Error('bad response');
return r.json();
})
.then(function (d) {
animateTo(elDecisions, d.decisions);
animateTo(elAlerts, d.alerts);
if (cliAvailable) {
animateTo(elBouncers, d.bouncers);
animateTo(elMachines, d.machines);
}
})
.catch(function () {
// silently fail — health badge in base.html covers connectivity state
});
}
fetchStats();
setInterval(fetchStats, pollInterval);
})();
+65
View File
@@ -0,0 +1,65 @@
(function () {
'use strict';
// Client-side live filter for tables with data-filter="true" attribute.
// Filters rows by matching input value against all cell text.
function initLiveFilter(inputId, tableId) {
var input = document.getElementById(inputId);
var table = document.getElementById(tableId);
if (!input || !table) return;
input.addEventListener('input', function () {
var query = input.value.toLowerCase().trim();
var rows = table.querySelectorAll('tbody tr');
rows.forEach(function (row) {
var text = row.textContent.toLowerCase();
row.style.display = (!query || text.includes(query)) ? '' : 'none';
});
});
}
// Client-side column sort for a table.
function initSort(tableId) {
var table = document.getElementById(tableId);
if (!table) return;
var headers = table.querySelectorAll('thead th[data-sort]');
headers.forEach(function (th, colIdx) {
th.style.cursor = 'pointer';
th.addEventListener('click', function () {
sortTable(table, colIdx, th);
});
});
}
function sortTable(table, colIdx, th) {
var asc = th.dataset.sortDir !== 'asc';
th.dataset.sortDir = asc ? 'asc' : 'desc';
var tbody = table.querySelector('tbody');
var rows = Array.from(tbody.querySelectorAll('tr'));
rows.sort(function (a, b) {
var aText = cellText(a, colIdx);
var bText = cellText(b, colIdx);
var aNum = parseFloat(aText);
var bNum = parseFloat(bText);
if (!isNaN(aNum) && !isNaN(bNum)) {
return asc ? aNum - bNum : bNum - aNum;
}
return asc
? aText.localeCompare(bText)
: bText.localeCompare(aText);
});
rows.forEach(function (r) { tbody.appendChild(r); });
}
function cellText(row, idx) {
var cells = row.querySelectorAll('td');
return cells[idx] ? cells[idx].textContent.trim().toLowerCase() : '';
}
// Expose helpers for inline use in page scripts.
window.TableHelpers = { initLiveFilter: initLiveFilter, initSort: initSort };
})();