Honor dashboard UI config flags
Build and Deploy / build-and-deploy (push) Successful in 27s

This commit is contained in:
Ketan Patel committed 2026-06-20 10:52:12 -04:00
1 parent 94f7ed8411
commit acb6b25657
5 files changed
+71 -2

No files matched your search

+1
View File
@@ -24,6 +24,7 @@
} }
.card:hover { border-color: var(--border-hi); box-shadow: var(--shadow-md); } .card:hover { border-color: var(--border-hi); box-shadow: var(--shadow-md); }
.card.resizing { outline: 2px dashed var(--accent); } .card.resizing { outline: 2px dashed var(--accent); }
#ups-cards.resize-disabled .card { resize: none; }
.card-header { .card-header {
display: flex; display: flex;
+26 -1
View File
@@ -136,6 +136,7 @@ try {
} catch(_) {} } catch(_) {}
// Set defaults // Set defaults
uiCfg.allow_resize = uiCfg.allow_resize !== false; uiCfg.allow_resize = uiCfg.allow_resize !== false;
if (uiCfgEl) uiCfgEl.classList.toggle('resize-disabled', uiCfg.allow_resize === false);
// Human-friendly metric labels for dropdown & headers // Human-friendly metric labels for dropdown & headers
const METRIC_LABELS = { const METRIC_LABELS = {
'LOADPCT': 'UPS Load %', 'LOADPCT': 'UPS Load %',
@@ -155,8 +156,12 @@ const lastUpdateTs = {};
const UPS_STALE_SECONDS = 120; // mark offline after 2 minutes inactivity const UPS_STALE_SECONDS = 120; // mark offline after 2 minutes inactivity
// Freeform tile layout enhancements // Freeform tile layout enhancements
const SNAP_GRID = 20; // pixel grid for snapping const SNAP_GRID = 20; // pixel grid for snapping
const SNAP_ORIGIN = 10; // default tile layout starts at 10px
let selectedTiles = new Set(); let selectedTiles = new Set();
function snap(v) { return Math.round(v / SNAP_GRID) * SNAP_GRID; } function snap(v) {
if (v <= SNAP_ORIGIN) return SNAP_ORIGIN;
return SNAP_ORIGIN + Math.round((v - SNAP_ORIGIN) / SNAP_GRID) * SNAP_GRID;
}
function clearSelection() { selectedTiles.forEach(t => t.classList.remove('selected')); selectedTiles.clear(); } function clearSelection() { selectedTiles.forEach(t => t.classList.remove('selected')); selectedTiles.clear(); }
function toggleTileSelection(tile, additive) { function toggleTileSelection(tile, additive) {
if (!additive) clearSelection(); if (!additive) clearSelection();
@@ -164,6 +169,20 @@ function toggleTileSelection(tile, additive) {
else { tile.classList.add('selected'); selectedTiles.add(tile); } else { tile.classList.add('selected'); selectedTiles.add(tile); }
} }
function applyCardFeatureVisibility(card) {
const runtimeBadge = card.querySelector('[data-badge="runtime"]');
const wattsBadge = card.querySelector('[data-badge="watts"]');
const headroomBadge = card.querySelector('[data-badge="headroom"]');
if (runtimeBadge) runtimeBadge.hidden = uiCfg.show_runtime === false;
if (wattsBadge) wattsBadge.hidden = uiCfg.show_watts === false;
if (headroomBadge) headroomBadge.hidden = uiCfg.show_headroom === false;
}
function clearEventFooter(name) {
const footer = getCard(name)?.querySelector('[data-ups-footer]');
if (footer) footer.textContent = '';
}
// --- Redis-backed tile layout persistence --- // --- Redis-backed tile layout persistence ---
async function loadServerTileConfig(name) { async function loadServerTileConfig(name) {
try { try {
@@ -418,6 +437,7 @@ function ensureUpsCard(name) {
if (!card.style.width && savedCardSizes[name]) { if (!card.style.width && savedCardSizes[name]) {
restoreCardSize(name); restoreCardSize(name);
} }
applyCardFeatureVisibility(card);
return card; return card;
} }
const container = document.getElementById('ups-cards'); const container = document.getElementById('ups-cards');
@@ -459,6 +479,7 @@ function ensureUpsCard(name) {
attachResizeBehavior(div, handle); attachResizeBehavior(div, handle);
} }
container.appendChild(div); container.appendChild(div);
applyCardFeatureVisibility(div);
// Observe card for resize events to update footer position // Observe card for resize events to update footer position
if (typeof ro !== 'undefined') { if (typeof ro !== 'undefined') {
@@ -1029,7 +1050,11 @@ evtSource.onmessage = (e) => {
const conn = card.querySelector('.ups-connection'); const conn = card.querySelector('.ups-connection');
if (conn) conn.textContent = `${meta.host}:${meta.port}`; if (conn) conn.textContent = `${meta.host}:${meta.port}`;
// Update event footer asynchronously (lightweight) // Update event footer asynchronously (lightweight)
if (uiCfg.show_events === false) {
clearEventFooter(meta.name);
} else {
updateEventFooter(meta.name); updateEventFooter(meta.name);
}
}); });
Object.entries(snapshots).forEach(([name, snap]) => { Object.entries(snapshots).forEach(([name, snap]) => {
+4
View File
@@ -42,6 +42,10 @@
show_events: f.show_events.checked, show_events: f.show_events.checked,
show_energy: f.show_energy.checked, show_energy: f.show_energy.checked,
color_badges: f.color_badges.checked, color_badges: f.color_badges.checked,
show_runtime: f.show_runtime.checked,
show_watts: f.show_watts.checked,
show_headroom: f.show_headroom.checked,
allow_resize: f.allow_resize.checked,
enable_transfer_burst_alert: f.enable_transfer_burst_alert.checked, enable_transfer_burst_alert: f.enable_transfer_burst_alert.checked,
enable_voltage_deviation_alert: f.enable_voltage_deviation_alert.checked, enable_voltage_deviation_alert: f.enable_voltage_deviation_alert.checked,
energy_cost_per_kwh: Number.parseFloat(normalizeEnergyRate(f.energy_cost_per_kwh.value)), energy_cost_per_kwh: Number.parseFloat(normalizeEnergyRate(f.energy_cost_per_kwh.value)),
+28
View File
@@ -30,6 +30,34 @@
</div> </div>
<label class="switch"><input type="checkbox" name="color_badges"><span class="slider"></span></label> <label class="switch"><input type="checkbox" name="color_badges"><span class="slider"></span></label>
</div> </div>
<div class="toggle-row">
<div class="toggle-label">
<span class="toggle-title">Show runtime badge</span>
<span class="toggle-help">Display estimated runtime on each UPS card</span>
</div>
<label class="switch"><input type="checkbox" name="show_runtime"><span class="slider"></span></label>
</div>
<div class="toggle-row">
<div class="toggle-label">
<span class="toggle-title">Show watts badge</span>
<span class="toggle-help">Display current load in watts on each UPS card</span>
</div>
<label class="switch"><input type="checkbox" name="show_watts"><span class="slider"></span></label>
</div>
<div class="toggle-row">
<div class="toggle-label">
<span class="toggle-title">Show headroom badge</span>
<span class="toggle-help">Display remaining load capacity on each UPS card</span>
</div>
<label class="switch"><input type="checkbox" name="show_headroom"><span class="slider"></span></label>
</div>
<div class="toggle-row">
<div class="toggle-label">
<span class="toggle-title">Allow resizing</span>
<span class="toggle-help">Enable card and tile resize handles on the dashboard</span>
</div>
<label class="switch"><input type="checkbox" name="allow_resize"><span class="slider"></span></label>
</div>
<div class="toggle-row"> <div class="toggle-row">
<div class="toggle-label"> <div class="toggle-label">
<span class="toggle-title">Transfer-burst alerts</span> <span class="toggle-title">Transfer-burst alerts</span>
+12 -1
View File
@@ -127,12 +127,23 @@ def test_ui_config_default(authed_client):
def test_ui_config_update(authed_client): def test_ui_config_update(authed_client):
r = authed_client.put( r = authed_client.put(
"/api/config/ui", "/api/config/ui",
json={"show_energy": True, "energy_cost_per_kwh": 0.15}, json={
"show_energy": True,
"energy_cost_per_kwh": 0.15,
"show_runtime": False,
"show_watts": False,
"show_headroom": False,
"allow_resize": False,
},
) )
assert r.status_code == 200 assert r.status_code == 200
body = r.json() body = r.json()
assert body["ui"]["show_energy"] is True assert body["ui"]["show_energy"] is True
assert body["ui"]["energy_cost_per_kwh"] == 0.15 assert body["ui"]["energy_cost_per_kwh"] == 0.15
assert body["ui"]["show_runtime"] is False
assert body["ui"]["show_watts"] is False
assert body["ui"]["show_headroom"] is False
assert body["ui"]["allow_resize"] is False
def test_ui_config_energy_cost_keeps_four_decimal_rate(authed_client): def test_ui_config_energy_cost_keeps_four_decimal_rate(authed_client):