Files
HAMeter/hameter/web/templates/discovery.html
2026-03-06 12:25:27 -05:00

120 lines
4.6 KiB
HTML

{% extends "base.html" %}
{% block title %}Discovery - HAMeter{% endblock %}
{% block page_title %}Discovery{% endblock %}
{% block content %}
<div class="discovery-page">
<div class="discovery-info">
<p>Discovery mode listens for all nearby meter transmissions. This will <strong>temporarily stop</strong> meter monitoring while active.</p>
</div>
<div class="discovery-controls">
<div class="form-group form-inline">
<label for="duration">Duration (seconds)</label>
<input type="number" id="duration" value="120" min="10" max="600">
</div>
<div class="btn-group">
<button class="btn btn-primary" id="btn-start" onclick="startDiscovery()">Start Discovery</button>
<button class="btn btn-danger hidden" id="btn-stop" onclick="stopDiscovery()">Stop</button>
</div>
<div class="discovery-status hidden" id="discovery-status">
<span class="spinner-sm"></span>
<span id="discovery-timer">Listening...</span>
</div>
</div>
<div class="table-container">
<table class="data-table" id="discovery-table">
<thead>
<tr>
<th>Meter ID</th>
<th>Protocol</th>
<th>Count</th>
<th>Last Reading</th>
<th>Action</th>
</tr>
</thead>
<tbody id="discovery-tbody">
<tr class="empty-row"><td colspan="5">No meters found yet. Start discovery to scan.</td></tr>
</tbody>
</table>
</div>
</div>
<script>
let discoveryTimer = null;
let discoveryStart = null;
let discoveryDuration = 120;
const configuredIds = new Set({{ configured_ids|tojson }});
function startDiscovery() {
discoveryDuration = parseInt(document.getElementById('duration').value) || 120;
fetch('/api/discovery/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({duration: discoveryDuration}),
}).then(r => r.json()).then(data => {
if (data.ok) {
discoveryStart = Date.now();
document.getElementById('btn-start').classList.add('hidden');
document.getElementById('btn-stop').classList.remove('hidden');
document.getElementById('discovery-status').classList.remove('hidden');
document.getElementById('discovery-tbody').innerHTML =
'<tr class="empty-row"><td colspan="5">Listening for meters...</td></tr>';
discoveryTimer = setInterval(updateTimer, 1000);
}
});
}
function stopDiscovery() {
fetch('/api/discovery/stop', {method: 'POST'});
endDiscovery();
}
function endDiscovery() {
clearInterval(discoveryTimer);
document.getElementById('btn-start').classList.remove('hidden');
document.getElementById('btn-stop').classList.add('hidden');
document.getElementById('discovery-status').classList.add('hidden');
}
function updateTimer() {
const elapsed = Math.floor((Date.now() - discoveryStart) / 1000);
const remaining = discoveryDuration - elapsed;
if (remaining <= 0) {
endDiscovery();
return;
}
document.getElementById('discovery-timer').textContent =
'Listening... ' + remaining + 's remaining';
}
// SSE handler updates discovery results
if (typeof window._onDiscoveryUpdate === 'undefined') {
window._onDiscoveryUpdate = function(results) {
const tbody = document.getElementById('discovery-tbody');
if (!tbody) return;
if (!results || Object.keys(results).length === 0) return;
let html = '';
// Sort by count descending
const entries = Object.entries(results).sort((a, b) => (b[1].count || 0) - (a[1].count || 0));
for (const [mid, info] of entries) {
const meterId = parseInt(mid);
const configured = configuredIds.has(meterId);
html += '<tr>' +
'<td><code>' + mid + '</code></td>' +
'<td><span class="badge badge-protocol">' + (info.protocol || '').toUpperCase() + '</span></td>' +
'<td>' + (info.count || 0) + '</td>' +
'<td>' + (info.last_consumption || '--') + '</td>' +
'<td>' + (configured
? '<span class="text-muted">Configured</span>'
: '<a href="/config/meters/add?id=' + mid + '&protocol=' + (info.protocol || '') + '" class="btn btn-sm btn-primary">Add</a>'
) + '</td></tr>';
}
tbody.innerHTML = html;
};
}
</script>
{% endblock %}