Skip to content

Commit a05eacb

Browse files
authored
Merge pull request #108 from SenaxInc/copilot/remove-voltage-from-viewer
Remove voltage column and refresh section from 112025 viewer
2 parents 8d639ee + 6b93648 commit a05eacb

File tree

1 file changed

+13
-153
lines changed

1 file changed

+13
-153
lines changed

TankAlarm-112025-Viewer-BluesOpta/TankAlarm-112025-Viewer-BluesOpta.ino

Lines changed: 13 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@
8080
#define WEB_REFRESH_SECONDS 21600
8181
#endif
8282

83+
#ifndef WEB_REFRESH_MINUTES
84+
#define WEB_REFRESH_MINUTES (WEB_REFRESH_SECONDS / 60)
85+
#endif
86+
8387
#ifndef SUMMARY_FETCH_INTERVAL_SECONDS
8488
#define SUMMARY_FETCH_INTERVAL_SECONDS 21600UL
8589
#endif
@@ -158,7 +162,6 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
158162
--header-bg: #ffffff;
159163
--meta-color: #475569;
160164
--card-bg: #ffffff;
161-
--filter-bg: #ffffff;
162165
--table-border: rgba(15,23,42,0.08);
163166
}
164167
body[data-theme="dark"] {
@@ -167,7 +170,6 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
167170
--header-bg: #1e293b;
168171
--meta-color: #94a3b8;
169172
--card-bg: #1e293b;
170-
--filter-bg: #1e293b;
171173
--table-border: rgba(255,255,255,0.08);
172174
}
173175
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); transition: background 0.3s, color 0.3s; }
@@ -179,13 +181,6 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
179181
.icon-button { width: 40px; height: 40px; border-radius: 50%; border: 1px solid rgba(148,163,184,0.4); background: var(--card-bg); color: var(--text); font-size: 1.1rem; cursor: pointer; }
180182
main { padding: 24px; max-width: 1400px; margin: 0 auto; }
181183
.card { background: var(--card-bg); border-radius: 16px; padding: 20px; box-shadow: 0 25px 60px rgba(15,23,42,0.15); border: 1px solid rgba(15,23,42,0.08); }
182-
.filter-bar { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end; margin-bottom: 20px; background: var(--filter-bg); padding: 12px 16px; border-radius: 12px; box-shadow: 0 10px 30px rgba(15,23,42,0.15); border: 1px solid rgba(15,23,42,0.08); }
183-
.filter-bar label { display: flex; flex-direction: column; font-size: 0.9rem; color: var(--meta-color); }
184-
.filter-bar select { margin-top: 6px; padding: 8px 10px; border-radius: 6px; border: 1px solid rgba(148,163,184,0.4); background: var(--bg); color: var(--text); min-width: 220px; }
185-
.filter-actions { display: flex; gap: 12px; flex-wrap: wrap; }
186-
.btn { padding: 10px 16px; border-radius: 999px; border: none; background: linear-gradient(135deg,#22d3ee,#818cf8); color: #0f172a; font-weight: 600; cursor: pointer; box-shadow: 0 15px 30px rgba(14,165,233,0.25); transition: transform 0.15s ease, box-shadow 0.15s ease; }
187-
.btn:disabled { opacity: 0.55; cursor: not-allowed; box-shadow: none; }
188-
.btn:not(:disabled):hover { transform: translateY(-1px); box-shadow: 0 20px 40px rgba(14,165,233,0.45); }
189184
table { width: 100%; border-collapse: collapse; margin-top: 12px; }
190185
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--table-border); }
191186
th { text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.75rem; color: var(--meta-color); }
@@ -219,30 +214,17 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
219214
</div>
220215
</header>
221216
<main>
222-
<div class="filter-bar">
223-
<label>
224-
Site Filter
225-
<select id="siteFilter">
226-
<option value="">All Sites</option>
227-
</select>
228-
</label>
229-
<div class="filter-actions">
230-
<button class="btn" id="refreshSiteBtn">Refresh Selected Site</button>
231-
<button class="btn" id="refreshAllBtn">Refresh All Sites</button>
232-
</div>
233-
</div>
234217
<section class="card">
235218
<div style="display:flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap;">
236219
<h2 style="margin:0; font-size:1.2rem;">Fleet Snapshot</h2>
237-
<span class="timestamp">Dashboard auto-refresh: )HTML" STR(WEB_REFRESH_SECONDS) R"HTML( s</span>
220+
<span class="timestamp">Dashboard auto-refresh: )HTML" STR(WEB_REFRESH_MINUTES) R"HTML( min</span>
238221
</div>
239222
<table>
240223
<thead>
241224
<tr>
242225
<th>Site</th>
243226
<th>Tank</th>
244227
<th>Level (ft/in)</th>
245-
<th>VIN Voltage</th>
246228
<th>24hr Change</th>
247229
<th>Updated</th>
248230
</tr>
@@ -281,19 +263,14 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
281263
lastFetch: document.getElementById('lastFetch'),
282264
nextFetch: document.getElementById('nextFetch'),
283265
refreshHint: document.getElementById('refreshHint'),
284-
tankBody: document.getElementById('tankBody'),
285-
siteFilter: document.getElementById('siteFilter'),
286-
refreshSiteBtn: document.getElementById('refreshSiteBtn'),
287-
refreshAllBtn: document.getElementById('refreshAllBtn')
266+
tankBody: document.getElementById('tankBody')
288267
};
289268
290269
const state = {
291-
tanks: [],
292-
selected: '',
293-
refreshing: false
270+
tanks: []
294271
};
295272
296-
function applyTankData(data, preferredUid) {
273+
function applyTankData(data) {
297274
els.viewerName.textContent = data.viewerName || 'Tank Alarm Viewer';
298275
els.viewerUid.textContent = data.viewerUid || '--';
299276
els.sourceServer.textContent = data.sourceServerName || 'Server';
@@ -303,81 +280,27 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
303280
els.nextFetch.textContent = formatEpoch(data.nextFetchEpoch);
304281
els.refreshHint.textContent = describeCadence(data.refreshSeconds, data.baseHour);
305282
state.tanks = data.tanks || [];
306-
const desired = preferredUid || state.selected;
307-
populateSiteFilter(desired);
308283
renderTankRows();
309284
}
310285
311-
async function fetchTanks(preferredUid) {
286+
async function fetchTanks() {
312287
try {
313288
const res = await fetch('/api/tanks');
314289
if (!res.ok) throw new Error('HTTP ' + res.status);
315290
const data = await res.json();
316-
applyTankData(data, preferredUid);
291+
applyTankData(data);
317292
} catch (err) {
318293
console.error('Viewer refresh failed', err);
319294
}
320295
}
321296
322-
async function triggerManualRefresh(targetUid) {
323-
const payload = targetUid ? { client: targetUid } : {};
324-
setRefreshBusy(true);
325-
try {
326-
const res = await fetch('/api/refresh', {
327-
method: 'POST',
328-
headers: { 'Content-Type': 'application/json' },
329-
body: JSON.stringify(payload)
330-
});
331-
if (!res.ok) {
332-
const text = await res.text();
333-
throw new Error(text || 'Refresh failed');
334-
}
335-
const data = await res.json();
336-
applyTankData(data, targetUid || state.selected);
337-
} catch (err) {
338-
console.error('Manual refresh failed', err);
339-
} finally {
340-
setRefreshBusy(false);
341-
}
342-
}
343-
344-
function populateSiteFilter(preferredUid) {
345-
const uniqueClients = new Map();
346-
state.tanks.forEach(tank => {
347-
if (!tank.client) return;
348-
if (!uniqueClients.has(tank.client)) {
349-
const label = tank.site || `Client ${tank.client.slice(-4)}`;
350-
uniqueClients.set(tank.client, label);
351-
}
352-
});
353-
const select = els.siteFilter;
354-
select.innerHTML = '<option value="">All Sites</option>';
355-
uniqueClients.forEach((label, uid) => {
356-
const option = document.createElement('option');
357-
const suffix = uid.length > 6 ? uid.slice(-6) : uid;
358-
option.value = uid;
359-
option.textContent = `${label} (${suffix})`;
360-
select.appendChild(option);
361-
});
362-
if (preferredUid && uniqueClients.has(preferredUid)) {
363-
select.value = preferredUid;
364-
state.selected = preferredUid;
365-
} else if (!uniqueClients.has(state.selected)) {
366-
select.value = '';
367-
state.selected = '';
368-
} else {
369-
select.value = state.selected;
370-
}
371-
updateButtonState();
372-
}
373-
374297
function renderTankRows() {
375298
const tbody = els.tankBody;
376299
tbody.innerHTML = '';
377-
const rows = state.selected ? state.tanks.filter(t => t.client === state.selected) : state.tanks;
300+
const rows = state.tanks;
378301
if (!rows.length) {
379302
const tr = document.createElement('tr');
380-
tr.innerHTML = '<td colspan="6">No tank data available</td>';
303+
tr.innerHTML = '<td colspan="5">No tank data available</td>';
381304
tbody.appendChild(tr);
382305
return;
383306
}
@@ -392,7 +315,6 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
392315
<td>${escapeHtml(tank.site, '--')}</td>
393316
<td>${escapeHtml(tank.label || 'Tank')} #${escapeHtml((tank.tank ?? '?'))}</td>
394317
<td>${formatFeetInches(tank.levelInches)}</td>
395-
<td>${formatVoltage(tank.vinVoltage)}</td>
396318
<td>--</td>
397319
<td>${formatEpoch(tank.lastUpdate)}${staleWarning}</td>`;
398320
if (isStale) {
@@ -403,13 +325,6 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
403325
});
404326
}
405327
406-
function formatVoltage(voltage) {
407-
if (typeof voltage !== 'number' || !isFinite(voltage) || voltage <= 0) {
408-
return '--';
409-
}
410-
return voltage.toFixed(2) + ' V';
411-
}
412-
413328
function statusBadge(tank) {
414329
if (!tank.alarm) {
415330
return '<span class="status-pill ok">Normal</span>';
@@ -452,36 +367,8 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
452367
return String(value).replace(/[&<>"']/g, c => entityMap[c] || c);
453368
}
454369
455-
function setRefreshBusy(busy) {
456-
state.refreshing = busy;
457-
updateButtonState();
458-
}
459-
460-
function updateButtonState() {
461-
els.refreshAllBtn.disabled = state.refreshing;
462-
els.refreshSiteBtn.disabled = state.refreshing || !state.selected;
463-
}
464-
465-
els.siteFilter.addEventListener('change', event => {
466-
state.selected = event.target.value;
467-
renderTankRows();
468-
updateButtonState();
469-
});
470-
471-
els.refreshSiteBtn.addEventListener('click', () => {
472-
if (!state.selected) {
473-
alert('Select a site to refresh.');
474-
return;
475-
}
476-
triggerManualRefresh(state.selected);
477-
});
478-
479-
els.refreshAllBtn.addEventListener('click', () => {
480-
triggerManualRefresh(null);
481-
});
482-
483370
fetchTanks();
484-
setInterval(() => fetchTanks(state.selected), REFRESH_SECONDS * 1000);
371+
setInterval(() => fetchTanks(), REFRESH_SECONDS * 1000);
485372
})();
486373
</script>
487374
</body>
@@ -503,7 +390,6 @@ static double computeNextAlignedEpoch(double epoch, uint8_t baseHour, uint32_t i
503390
static void scheduleNextSummaryFetch();
504391
static void fetchViewerSummary();
505392
static void handleViewerSummary(JsonDocument &doc, double epoch);
506-
static void handleRefreshPost(EthernetClient &client, const String &body);
507393

508394
void setup() {
509395
Serial.begin(115200);
@@ -688,8 +574,6 @@ static void handleWebRequests() {
688574
sendDashboard(client);
689575
} else if (method == "GET" && path == "/api/tanks") {
690576
sendTankJson(client);
691-
} else if (method == "POST" && path == "/api/refresh") {
692-
handleRefreshPost(client, body);
693577
} else {
694578
respondStatus(client, 404, "Not Found");
695579
}
@@ -948,27 +832,3 @@ static void handleViewerSummary(JsonDocument &doc, double epoch) {
948832
Serial.print(gTankRecordCount);
949833
Serial.println(F(" tanks)"));
950834
}
951-
952-
static void handleRefreshPost(EthernetClient &client, const String &body) {
953-
char clientUid[48] = {0};
954-
if (body.length() > 0) {
955-
DynamicJsonDocument doc(128);
956-
if (deserializeJson(doc, body) == DeserializationError::Ok) {
957-
const char *uid = doc["client"] | "";
958-
if (uid && *uid) {
959-
strlcpy(clientUid, uid, sizeof(clientUid));
960-
}
961-
}
962-
}
963-
964-
if (clientUid[0]) {
965-
Serial.print(F("Viewer manual refresh requested for client " ));
966-
Serial.println(clientUid);
967-
} else {
968-
Serial.println(F("Viewer manual refresh requested for all sites"));
969-
}
970-
971-
fetchViewerSummary();
972-
scheduleNextSummaryFetch();
973-
sendTankJson(client);
974-
}

0 commit comments

Comments
 (0)