@@ -896,40 +896,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
896896 margin-top: 8px;
897897 font-size: 1.8rem;
898898 }
899- .filter-bar {
900- display: flex;
901- flex-wrap: wrap;
902- gap: 16px;
903- align-items: flex-end;
904- background: var(--surface);
905- border: 1px solid var(--card-border);
906- border-radius: 20px;
907- padding: 16px;
908- box-shadow: 0 18px 40px var(--card-shadow);
909- margin-bottom: 20px;
910- }
911- .filter-bar label {
912- display: flex;
913- flex-direction: column;
914- font-size: 0.9rem;
915- color: var(--muted);
916- min-width: 220px;
917- }
918- select {
919- appearance: none;
920- border-radius: 10px;
921- border: 1px solid var(--card-border);
922- padding: 10px 12px;
923- margin-top: 6px;
924- background: var(--bg);
925- color: var(--text);
926- }
927- .filter-actions {
928- display: flex;
929- gap: 12px;
930- flex-wrap: wrap;
931- align-items: center;
932- }
933899 .btn {
934900 border: none;
935901 border-radius: 999px;
@@ -1046,32 +1012,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
10461012 #toast.show {
10471013 opacity: 1;
10481014 }
1049- .relay-btn {
1050- padding: 4px 8px;
1051- margin: 0 2px;
1052- border: 1px solid var(--card-border);
1053- border-radius: 4px;
1054- background: var(--surface);
1055- color: var(--text);
1056- font-size: 0.75rem;
1057- cursor: pointer;
1058- transition: all 0.2s ease;
1059- }
1060- .relay-btn:hover {
1061- background: var(--accent);
1062- color: var(--accent-contrast);
1063- border-color: var(--accent);
1064- }
1065- .relay-btn.active {
1066- background: var(--accent);
1067- color: var(--accent-contrast);
1068- border-color: var(--accent);
1069- font-weight: bold;
1070- }
1071- .relay-btn:disabled {
1072- opacity: 0.5;
1073- cursor: not-allowed;
1074- }
10751015 </style>
10761016</head>
10771017<body data-theme="light">
@@ -1107,10 +1047,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
11071047 <span>Last Time Sync</span>
11081048 <strong id="lastSync">--</strong>
11091049 </div>
1110- <div class="meta-card">
1111- <span>PIN Status</span>
1112- <strong id="pinStatus">--</strong>
1113- </div>
11141050 <div class="meta-card">
11151051 <span>Last Dashboard Refresh</span>
11161052 <strong id="lastRefresh">--</strong>
@@ -1136,19 +1072,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
11361072 <strong id="statStale">0</strong>
11371073 </div>
11381074 </div>
1139- <section class="filter-bar">
1140- <label>
1141- Site Filter
1142- <select id="siteFilter">
1143- <option value="">All Sites</option>
1144- </select>
1145- </label>
1146- <div class="filter-actions">
1147- <button class="btn" id="refreshSiteBtn">Refresh Selected Site</button>
1148- <button class="btn secondary" id="refreshAllBtn">Refresh All Sites</button>
1149- <span class="badge" id="autoRefreshHint">UI refresh 60 s · Server cadence 6 h</span>
1150- </div>
1151- </section>
11521075 <section class="card">
11531076 <div class="card-head">
11541077 <h2 style="margin:0;">Fleet Telemetry</h2>
@@ -1161,10 +1084,9 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
11611084 <th>Site</th>
11621085 <th>Tank</th>
11631086 <th>Level (in)</th>
1164- <th>% Full</th>
11651087 <th>Status</th>
11661088 <th>Updated</th>
1167- <th>Relays </th>
1089+ <th>Refresh </th>
11681090 </tr>
11691091 </thead>
11701092 <tbody id="tankBody"></tbody>
@@ -1186,11 +1108,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
11861108 nextEmail: document.getElementById('nextEmail'),
11871109 lastSync: document.getElementById('lastSync'),
11881110 lastRefresh: document.getElementById('lastRefresh'),
1189- pinStatus: document.getElementById('pinStatus'),
1190- autoRefreshHint: document.getElementById('autoRefreshHint'),
1191- siteFilter: document.getElementById('siteFilter'),
1192- refreshSiteBtn: document.getElementById('refreshSiteBtn'),
1193- refreshAllBtn: document.getElementById('refreshAllBtn'),
11941111 tankBody: document.getElementById('tankBody'),
11951112 statClients: document.getElementById('statClients'),
11961113 statTanks: document.getElementById('statTanks'),
@@ -1202,7 +1119,6 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
12021119 const state = {
12031120 clients: [],
12041121 tanks: [],
1205- selected: '',
12061122 refreshing: false,
12071123 timer: null,
12081124 uiRefreshSeconds: DEFAULT_REFRESH_SECONDS
@@ -1283,40 +1199,10 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
12831199 return rows;
12841200 }
12851201
1286- function populateSiteFilter(preferredUid) {
1287- const select = els.siteFilter;
1288- const map = new Map();
1289- state.clients.forEach(client => {
1290- if (!client.client) return;
1291- const suffix = client.client.length > 6 ? client.client.slice(-6) : client.client;
1292- const label = client.site ? `${client.site} (${suffix})` : `Client ${suffix}`;
1293- map.set(client.client, label);
1294- });
1295- const previous = select.value;
1296- select.innerHTML = '<option value="">All Sites</option>';
1297- map.forEach((label, uid) => {
1298- const option = document.createElement('option');
1299- option.value = uid;
1300- option.textContent = label;
1301- select.appendChild(option);
1302- });
1303- const desired = preferredUid || previous;
1304- if (desired && map.has(desired)) {
1305- select.value = desired;
1306- state.selected = desired;
1307- } else if (!map.has(state.selected)) {
1308- state.selected = '';
1309- select.value = '';
1310- } else {
1311- select.value = state.selected || '';
1312- }
1313- updateButtonState();
1314- }
1315-
13161202 function renderTankRows() {
13171203 const tbody = els.tankBody;
13181204 tbody.innerHTML = '';
1319- const rows = state.selected ? state.tanks.filter(t => t.client === state.selected) : state. tanks;
1205+ const rows = state.tanks;
13201206 if (!rows.length) {
13211207 const tr = document.createElement('tr');
13221208 tr.innerHTML = '<td colspan="7">No telemetry available</td>';
@@ -1331,10 +1217,9 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
13311217 <td>${row.site || '--'}</td>
13321218 <td>${row.label || 'Tank'} #${row.tank || '?'}</td>
13331219 <td>${formatNumber(row.levelInches)}</td>
1334- <td>${formatNumber(row.percent)}</td>
13351220 <td>${statusBadge(row)}</td>
13361221 <td>${formatEpoch(row.lastUpdate)}</td>
1337- <td>${relayButtons (row)}</td>`;
1222+ <td>${refreshButton (row)}</td>`;
13381223 tbody.appendChild(tr);
13391224 });
13401225 }
@@ -1343,8 +1228,13 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
13431228 if (!row.alarm) {
13441229 return '<span class="status-pill ok">Normal</span>';
13451230 }
1346- const label = row.alarmType ? row.alarmType : 'Alarm';
1347- return `<span class="status-pill alarm">${label}</span>`;
1231+ return '<span class="status-pill alarm">ALARM</span>';
1232+ }
1233+
1234+ function refreshButton(row) {
1235+ if (!row.client || row.client === '--') return '--';
1236+ const escapedClient = escapeHtml(row.client);
1237+ return `<button class="icon-button" onclick="refreshTank('${escapedClient}')" title="Refresh Tank" style="width:32px;height:32px;font-size:1rem;">🔄</button>`;
13481238 }
13491239
13501240 function escapeHtml(unsafe) {
@@ -1357,45 +1247,29 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
13571247 .replace(/'/g, ''');
13581248 }
13591249
1360- function relayButtons(row) {
1361- if (!row.client || row.client === '--') return '--';
1362- const MAX_RELAYS = 4;
1363- const relays = Array.from({length: MAX_RELAYS}, (_, i) => i + 1);
1364- const escapedClient = escapeHtml(row.client);
1365- return relays.map(num =>
1366- `<button class="relay-btn" onclick="toggleRelay('${escapedClient}', ${num}, event)" title="Toggle Relay ${num}">R${num}</button>`
1367- ).join(' ');
1368- }
1369-
1370- async function toggleRelay(clientUid, relayNum, event) {
1371- const btn = event.target;
1372- const wasActive = btn.classList.contains('active');
1373- const newState = !wasActive;
1374-
1375- btn.disabled = true;
1250+ async function refreshTank(clientUid) {
1251+ if (state.refreshing) return;
1252+ state.refreshing = true;
13761253 try {
1377- const res = await fetch('/api/relay ', {
1254+ const res = await fetch('/api/refresh ', {
13781255 method: 'POST',
13791256 headers: { 'Content-Type': 'application/json' },
1380- body: JSON.stringify({
1381- clientUid: clientUid,
1382- relay: relayNum,
1383- state: newState
1384- })
1257+ body: JSON.stringify({ client: clientUid })
13851258 });
13861259 if (!res.ok) {
13871260 const text = await res.text();
1388- throw new Error(text || 'Relay command failed');
1261+ throw new Error(text || 'Refresh failed');
13891262 }
1390- btn.classList.toggle('active', newState);
1391- showToast(`Relay ${relayNum} ${newState ? 'ON' : 'OFF'} command sent`);
1263+ const data = await res.json();
1264+ applyServerData(data);
1265+ showToast('Tank refreshed');
13921266 } catch (err) {
1393- showToast(err.message || 'Relay control failed', true);
1267+ showToast(err.message || 'Refresh failed', true);
13941268 } finally {
1395- btn.disabled = false;
1269+ state.refreshing = false;
13961270 }
13971271 }
1398- window.toggleRelay = toggleRelay ;
1272+ window.refreshTank = refreshTank ;
13991273
14001274 function updateStats() {
14011275 const clientIds = new Set();
@@ -1412,101 +1286,44 @@ static const char DASHBOARD_HTML[] PROGMEM = R"HTML(
14121286 els.statStale.textContent = stale;
14131287 }
14141288
1415- function updateButtonState() {
1416- els.refreshAllBtn.disabled = state.refreshing;
1417- els.refreshSiteBtn.disabled = state.refreshing || !state.selected;
1418- }
1419-
14201289 function scheduleUiRefresh() {
14211290 if (state.timer) {
14221291 clearInterval(state.timer);
14231292 }
14241293 state.timer = setInterval(() => {
1425- refreshData(state.selected );
1294+ refreshData();
14261295 }, state.uiRefreshSeconds * 1000);
14271296 }
14281297
1429- function updateRefreshHint(serverInfo) {
1430- const cadence = describeCadence(serverInfo && serverInfo.webRefreshSeconds);
1431- els.autoRefreshHint.textContent = `UI refresh ${state.uiRefreshSeconds} s · Server cadence ${cadence}`;
1432- }
1433-
1434- function applyServerData(data, preferredUid) {
1298+ function applyServerData(data) {
14351299 state.clients = data.clients || [];
14361300 state.tanks = flattenTanks(state.clients);
1437- if (preferredUid) {
1438- state.selected = preferredUid;
1439- }
14401301 const serverInfo = data.server || {};
14411302 els.serverName.textContent = serverInfo.name || 'Tank Alarm Server';
14421303 els.serverUid.textContent = data.serverUid || '--';
14431304 els.fleetName.textContent = serverInfo.clientFleet || 'tankalarm-clients';
14441305 els.nextEmail.textContent = formatEpoch(data.nextDailyEmailEpoch);
14451306 els.lastSync.textContent = formatEpoch(data.lastSyncEpoch);
1446- els.pinStatus.textContent = serverInfo.pinConfigured ? 'Configured' : 'Not Set';
14471307 els.lastRefresh.textContent = new Date().toLocaleString();
14481308 state.uiRefreshSeconds = DEFAULT_REFRESH_SECONDS;
1449- updateRefreshHint(serverInfo);
1450- populateSiteFilter(preferredUid);
14511309 renderTankRows();
14521310 updateStats();
14531311 scheduleUiRefresh();
14541312 }
14551313
1456- async function refreshData(preferredUid ) {
1314+ async function refreshData() {
14571315 try {
14581316 const res = await fetch('/api/clients');
14591317 if (!res.ok) {
14601318 throw new Error('Failed to fetch fleet data');
14611319 }
14621320 const data = await res.json();
1463- applyServerData(data, preferredUid || state.selected );
1321+ applyServerData(data);
14641322 } catch (err) {
14651323 showToast(err.message || 'Fleet refresh failed', true);
14661324 }
14671325 }
14681326
1469- async function triggerManualRefresh(targetUid) {
1470- const payload = targetUid ? { client: targetUid } : {};
1471- state.refreshing = true;
1472- updateButtonState();
1473- try {
1474- const res = await fetch('/api/refresh', {
1475- method: 'POST',
1476- headers: { 'Content-Type': 'application/json' },
1477- body: JSON.stringify(payload)
1478- });
1479- if (!res.ok) {
1480- const text = await res.text();
1481- throw new Error(text || 'Refresh failed');
1482- }
1483- const data = await res.json();
1484- applyServerData(data, targetUid || state.selected);
1485- showToast(targetUid ? 'Selected site refreshed' : 'Fleet refresh queued');
1486- } catch (err) {
1487- showToast(err.message || 'Refresh failed', true);
1488- } finally {
1489- state.refreshing = false;
1490- updateButtonState();
1491- }
1492- }
1493-
1494- els.siteFilter.addEventListener('change', event => {
1495- state.selected = event.target.value;
1496- renderTankRows();
1497- updateButtonState();
1498- });
1499- els.refreshSiteBtn.addEventListener('click', () => {
1500- if (!state.selected) {
1501- showToast('Pick a site first.', true);
1502- return;
1503- }
1504- triggerManualRefresh(state.selected);
1505- });
1506- els.refreshAllBtn.addEventListener('click', () => {
1507- triggerManualRefresh(null);
1508- });
1509-
15101327 refreshData();
15111328 })();
15121329 </script>
0 commit comments