initial commit
This commit is contained in:
210
hameter/web/templates/setup.html
Normal file
210
hameter/web/templates/setup.html
Normal file
@@ -0,0 +1,210 @@
|
||||
<!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 — I'll use Discovery later</a>
|
||||
<button type="button" class="btn btn-primary" onclick="saveMeterAndFinish()">Save & Start</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="setup-card hidden" id="step-4">
|
||||
<div class="setup-success">✓</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>
|
||||
Reference in New Issue
Block a user