Add Pi Zero headless serial bridge with AP portal and daily RTC-based logs
This commit is contained in:
119
templates/index.html
Normal file
119
templates/index.html
Normal file
@@ -0,0 +1,119 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Serial Portal</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>WiFi Setup</h1>
|
||||
<p>Fallback-AP: <strong>serial</strong> / Passwort: <strong>serialserial</strong></p>
|
||||
|
||||
<section class="card">
|
||||
<h2>Netzwerkstatus</h2>
|
||||
<div id="status">Lade Status...</div>
|
||||
<div id="error" class="error"></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Mit WLAN verbinden</h2>
|
||||
<button id="scanBtn">Scan</button>
|
||||
<label for="ssidSelect">SSID</label>
|
||||
<select id="ssidSelect">
|
||||
<option value="">Bitte zuerst scannen</option>
|
||||
</select>
|
||||
|
||||
<label for="pw">Passwort</label>
|
||||
<input id="pw" type="password" autocomplete="off" placeholder="WLAN Passwort">
|
||||
|
||||
<button id="connectBtn">Verbinden</button>
|
||||
<div id="connectMsg"></div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Serial Monitor</h2>
|
||||
<a href="/serial">Zur Live-Serial-Seite</a>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
async function refreshStatus() {
|
||||
try {
|
||||
const resp = await fetch('/api/status');
|
||||
const data = await resp.json();
|
||||
document.getElementById('status').textContent =
|
||||
`AP: ${data.ap_mode ? 'an' : 'aus'} | WLAN: ${data.wifi_connected ? 'verbunden' : 'getrennt'} | Internet: ${data.internet_available ? 'ok' : 'nein'} | Status: ${data.status_message}`;
|
||||
document.getElementById('error').textContent = data.last_error || '';
|
||||
} catch (e) {
|
||||
document.getElementById('status').textContent = 'Status nicht erreichbar';
|
||||
}
|
||||
}
|
||||
|
||||
async function scan() {
|
||||
const msg = document.getElementById('connectMsg');
|
||||
msg.textContent = 'Scanne WLANs...';
|
||||
try {
|
||||
const resp = await fetch('/api/scan', {method: 'POST'});
|
||||
const data = await resp.json();
|
||||
const select = document.getElementById('ssidSelect');
|
||||
select.innerHTML = '';
|
||||
if (data.ssids && data.ssids.length) {
|
||||
data.ssids.forEach(ssid => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = ssid;
|
||||
opt.textContent = ssid;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
msg.textContent = `${data.ssids.length} Netzwerke gefunden.`;
|
||||
} else {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = 'Keine Netzwerke gefunden';
|
||||
select.appendChild(opt);
|
||||
msg.textContent = 'Keine Netzwerke gefunden.';
|
||||
}
|
||||
} catch (e) {
|
||||
msg.textContent = 'Scan fehlgeschlagen.';
|
||||
}
|
||||
refreshStatus();
|
||||
}
|
||||
|
||||
async function connectWifi() {
|
||||
const ssid = document.getElementById('ssidSelect').value;
|
||||
const password = document.getElementById('pw').value;
|
||||
const msg = document.getElementById('connectMsg');
|
||||
|
||||
if (!ssid) {
|
||||
msg.textContent = 'Bitte SSID wählen.';
|
||||
return;
|
||||
}
|
||||
|
||||
msg.textContent = `Verbinde mit ${ssid}...`;
|
||||
try {
|
||||
const resp = await fetch('/api/connect', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ssid, password})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (resp.ok && data.ok) {
|
||||
msg.textContent = 'Verbindung erfolgreich. AP wird beendet.';
|
||||
} else {
|
||||
msg.textContent = `Fehler: ${data.message || 'Connect fehlgeschlagen'}`;
|
||||
}
|
||||
} catch (e) {
|
||||
msg.textContent = 'Verbindungsversuch fehlgeschlagen.';
|
||||
}
|
||||
setTimeout(refreshStatus, 1000);
|
||||
}
|
||||
|
||||
document.getElementById('scanBtn').addEventListener('click', scan);
|
||||
document.getElementById('connectBtn').addEventListener('click', connectWifi);
|
||||
|
||||
refreshStatus();
|
||||
setInterval(refreshStatus, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
45
templates/serial.html
Normal file
45
templates/serial.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Serial Stream</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>ESP32 Serial Live</h1>
|
||||
<p><a href="/">Zurück zum WLAN-Portal</a></p>
|
||||
<pre id="terminal" class="terminal"></pre>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const term = document.getElementById('terminal');
|
||||
const maxLines = 500;
|
||||
const lines = [];
|
||||
|
||||
function appendLine(text) {
|
||||
lines.push(text);
|
||||
if (lines.length > maxLines) {
|
||||
lines.shift();
|
||||
}
|
||||
term.textContent = lines.join('\n');
|
||||
term.scrollTop = term.scrollHeight;
|
||||
}
|
||||
|
||||
const events = new EventSource('/events/serial');
|
||||
events.onmessage = (evt) => {
|
||||
try {
|
||||
const payload = JSON.parse(evt.data);
|
||||
appendLine(payload.line || '');
|
||||
} catch (e) {
|
||||
appendLine(evt.data || '');
|
||||
}
|
||||
};
|
||||
|
||||
events.onerror = () => {
|
||||
appendLine('[sse] reconnecting...');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user