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

211 lines
8.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HAMeter Setup</title>
<link rel="stylesheet" href="{{ url_for('main.static', filename='style.css') }}">
<link rel="icon" type="image/svg+xml" href="{{ url_for('main.static', filename='favicon.svg') }}">
</head>
<body class="setup-body">
<div class="setup-container">
<div class="setup-card" id="step-1">
<div class="setup-logo">HAMeter</div>
<h2>Welcome</h2>
<p>HAMeter reads utility meters via SDR and publishes readings to Home Assistant over MQTT.</p>
<p>Let's configure your connection.</p>
<button class="btn btn-primary btn-lg" onclick="showStep(2)">Get Started</button>
</div>
<div class="setup-card hidden" id="step-2">
<h2>MQTT Broker</h2>
<p>Enter your MQTT broker connection details.</p>
<form id="mqtt-form" onsubmit="return false;">
<div class="form-group">
<label for="mqtt-host">Host <span class="required">*</span></label>
<input type="text" id="mqtt-host" placeholder="192.168.1.74" required>
</div>
<div class="form-group">
<label for="mqtt-port">Port</label>
<input type="number" id="mqtt-port" value="1883">
</div>
<div class="form-group">
<label for="mqtt-user">Username</label>
<input type="text" id="mqtt-user" placeholder="Optional">
</div>
<div class="form-group">
<label for="mqtt-password">Password</label>
<input type="password" id="mqtt-password" placeholder="Optional">
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="testMqtt()">Test Connection</button>
<button type="button" class="btn btn-primary" onclick="mqttNext()">Next</button>
</div>
<div class="test-result hidden" id="mqtt-test-result"></div>
</form>
</div>
<div class="setup-card hidden" id="step-3">
<h2>Add a Meter</h2>
<p>If you know your meter's radio ID and protocol, enter them below. Otherwise, you can use Discovery after setup.</p>
<form id="meter-form" onsubmit="return false;">
<div class="form-group">
<label for="meter-id">Meter ID (ERT Serial Number)</label>
<input type="number" id="meter-id" placeholder="e.g. 23040293">
</div>
<div class="form-group">
<label for="meter-protocol">Protocol</label>
<select id="meter-protocol">
{% for p in protocols %}
<option value="{{ p }}">{{ p }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="meter-name">Name</label>
<input type="text" id="meter-name" placeholder="e.g. Electric Meter">
</div>
<div class="form-group">
<label for="meter-device-class">Type</label>
<select id="meter-device-class" onchange="updateMeterDefaults()">
<option value="">-- Select --</option>
<option value="energy">Energy (Electric)</option>
<option value="gas">Gas</option>
<option value="water">Water</option>
</select>
</div>
<div class="form-actions">
<a href="#" class="btn btn-link" onclick="skipMeter()">Skip &mdash; I'll use Discovery later</a>
<button type="button" class="btn btn-primary" onclick="saveMeterAndFinish()">Save &amp; Start</button>
</div>
</form>
</div>
<div class="setup-card hidden" id="step-4">
<div class="setup-success">&#10003;</div>
<h2>Setup Complete</h2>
<p>HAMeter is starting up. Redirecting to dashboard...</p>
<div class="spinner"></div>
</div>
<div class="setup-error hidden" id="setup-error">
<p class="error-text"></p>
</div>
</div>
<script>
function showStep(n) {
document.querySelectorAll('.setup-card').forEach(el => el.classList.add('hidden'));
document.getElementById('step-' + n).classList.remove('hidden');
}
async function testMqtt() {
const result = document.getElementById('mqtt-test-result');
result.classList.remove('hidden');
result.textContent = 'Testing...';
result.className = 'test-result';
try {
const resp = await fetch('/api/config/mqtt/test', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
host: document.getElementById('mqtt-host').value,
port: parseInt(document.getElementById('mqtt-port').value),
user: document.getElementById('mqtt-user').value,
password: document.getElementById('mqtt-password').value,
}),
});
const data = await resp.json();
result.textContent = data.message;
result.className = 'test-result ' + (data.ok ? 'test-success' : 'test-error');
} catch (e) {
result.textContent = 'Request failed: ' + e.message;
result.className = 'test-result test-error';
}
}
function mqttNext() {
const host = document.getElementById('mqtt-host').value.trim();
if (!host) {
showError('MQTT host is required');
return;
}
showStep(3);
}
function updateMeterDefaults() {
const dc = document.getElementById('meter-device-class').value;
if (!dc) return;
fetch('/api/meter_defaults/' + dc)
.then(r => r.json())
.then(data => {
// Auto-fill name if empty
const nameEl = document.getElementById('meter-name');
if (!nameEl.value) {
const names = {energy: 'Electric Meter', gas: 'Gas Meter', water: 'Water Meter'};
nameEl.value = names[dc] || '';
}
});
}
async function saveMeterAndFinish() {
await doSetup(true);
}
async function skipMeter() {
await doSetup(false);
}
async function doSetup(includeMeter) {
const payload = {
mqtt: {
host: document.getElementById('mqtt-host').value.trim(),
port: parseInt(document.getElementById('mqtt-port').value),
user: document.getElementById('mqtt-user').value,
password: document.getElementById('mqtt-password').value,
},
};
if (includeMeter) {
const meterId = document.getElementById('meter-id').value;
if (!meterId) {
showError('Meter ID is required');
return;
}
payload.meter = {
id: parseInt(meterId),
protocol: document.getElementById('meter-protocol').value,
name: document.getElementById('meter-name').value || 'Meter 1',
device_class: document.getElementById('meter-device-class').value,
};
}
try {
const resp = await fetch('/api/setup', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
});
const data = await resp.json();
if (data.ok) {
showStep(4);
setTimeout(() => window.location.href = '/dashboard', 3000);
} else {
showError(data.error || 'Setup failed');
}
} catch (e) {
showError('Request failed: ' + e.message);
}
}
function showError(msg) {
const el = document.getElementById('setup-error');
el.classList.remove('hidden');
el.querySelector('.error-text').textContent = msg;
setTimeout(() => el.classList.add('hidden'), 5000);
}
</script>
</body>
</html>