f8b877d555
PWA laeuft: Manifest, Symbole in allen noetigen Groessen und die iOS-Angaben, die Apple statt des Manifests auswertet. Das Startsymbol ist bewusst neutral - die Vier Ringe auf einem Homescreen waeren nach aussen sichtbar und fielen nicht mehr unter die private Nutzung. Vorbereitet, aber nicht ausgefuehrt: Capacitor-Konfiguration, ein Ablage-Adapter fuer Schluesselbund und Keystore (greift defensiv auf die Laufzeit zu, damit die App ohne das Plugin unveraendert weiterlaeuft), die Pfad-Freigabeliste fuer den Reverse Proxy und das Geruest der FMM003-Zuordnungstabelle. Die QR-Seite fuer die Einrichtung war zunaechst ein Fehlgriff: Ich hatte den QR-Erzeuger selbst geschrieben. Der Vergleich gegen eine erprobte Implementierung zeigte 1239 abweichende Module von 3249 - der Code waere unlesbar gewesen. Jetzt liegt eine bewaehrte Bibliothek (MIT, 57 KB) neben der Seite im eigenen Home Assistant. Das erfuellt die eigentliche Anforderung genauso: kein Netzzugriff, der Token verlaesst das eigene Netz nicht. Geprueft ueber einen Umlauf - 233 Zeichen hinein, identisch wieder heraus.
116 lines
4.6 KiB
HTML
116 lines
4.6 KiB
HTML
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>DataMetric360 — Gerät verbinden</title>
|
|
<!--
|
|
Erzeugt den Einrichtungs-QR-Code für die App, vollständig auf dem Gerät.
|
|
|
|
Der Punkt dabei: Ein QR-Generator im Internet bekäme den Zugangstoken zu
|
|
sehen. Diese Seite lädt ihre Bibliothek aus dem eigenen Home Assistant
|
|
(dm360-qrcode-lib.js daneben) und stellt keine einzige Netzanfrage — der
|
|
Token verlässt das eigene Netz nicht.
|
|
|
|
Ablegen unter /config/www/, aufrufbar als /local/dm360-qr.html.
|
|
Gegenstück in der App: UMSETZUNGSPLAN.md Phase 10 Stufe 2.
|
|
-->
|
|
<script src="./dm360-qrcode-lib.js"></script>
|
|
<style>
|
|
:root { color-scheme: dark; }
|
|
body {
|
|
margin: 0; min-height: 100vh; display: grid; place-items: center;
|
|
background: #161b23; color: #fff;
|
|
font: 400 15px/1.55 Helvetica, Arial, sans-serif;
|
|
}
|
|
main { width: min(420px, 92vw); padding: 24px; }
|
|
h1 { font-size: 22px; font-weight: 300; margin: 0 0 6px; }
|
|
p { color: #9aa1ad; font-size: 13.5px; margin: 0 0 18px; text-wrap: pretty; }
|
|
label { display: block; font-size: 10px; letter-spacing: .2em;
|
|
text-transform: uppercase; color: #8a94a3; margin: 14px 0 6px; }
|
|
input {
|
|
width: 100%; box-sizing: border-box; padding: 11px 12px; font-size: 16px;
|
|
background: #1f2733; color: #fff; border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 8px; outline: none;
|
|
}
|
|
input:focus-visible { outline: 2px solid #F50537; outline-offset: 2px; }
|
|
button {
|
|
margin-top: 18px; width: 100%; padding: 13px; font-size: 15px;
|
|
background: #fff; color: #000; border: 0; border-radius: 999px; cursor: pointer;
|
|
}
|
|
#ausgabe { margin-top: 22px; display: none; text-align: center; }
|
|
#ausgabe.sichtbar { display: block; }
|
|
#ausgabe img { width: 100%; max-width: 320px; height: auto;
|
|
image-rendering: pixelated; background: #fff;
|
|
padding: 12px; border-radius: 12px; box-sizing: border-box; }
|
|
.warnung { color: #ffaa00; font-size: 12px; margin-top: 14px; }
|
|
.fehler { color: #fd2c4e; font-size: 13px; margin-top: 14px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<h1>Gerät verbinden</h1>
|
|
<p>
|
|
Trag die von außen erreichbare Adresse und einen langlebigen Zugriffstoken ein.
|
|
Der Code entsteht hier auf dem Gerät; nichts davon wird irgendwohin gesendet.
|
|
</p>
|
|
|
|
<label for="adresse">Server-Adresse</label>
|
|
<input id="adresse" type="url" placeholder="https://api.datametric360.app" />
|
|
|
|
<label for="token">Zugriffstoken</label>
|
|
<input id="token" type="password" placeholder="in Home Assistant unter Profil → Sicherheit" />
|
|
|
|
<button id="erzeugen" type="button">Code anzeigen</button>
|
|
|
|
<div id="ausgabe"></div>
|
|
<p id="meldung" class="fehler" hidden></p>
|
|
</main>
|
|
|
|
<script>
|
|
const ausgabe = document.getElementById("ausgabe")
|
|
const meldung = document.getElementById("meldung")
|
|
|
|
function melde(text) {
|
|
meldung.textContent = text
|
|
meldung.hidden = !text
|
|
}
|
|
|
|
document.getElementById("erzeugen").addEventListener("click", () => {
|
|
melde("")
|
|
ausgabe.classList.remove("sichtbar")
|
|
|
|
const url = document.getElementById("adresse").value.trim().replace(/\/+$/, "")
|
|
const token = document.getElementById("token").value.trim()
|
|
if (!url || !token) {
|
|
melde("Bitte Adresse und Token eintragen.")
|
|
return
|
|
}
|
|
|
|
const inhalt = JSON.stringify({ url, token })
|
|
try {
|
|
// Version 0 heißt: kleinstmögliche Version selbst wählen.
|
|
// Fehlerkorrektur L, weil der Code direkt vom Bildschirm abgelesen
|
|
// wird — es gibt keinen zerkratzten Aufkleber zu retten.
|
|
const qr = qrcode(0, "L")
|
|
qr.addData(inhalt)
|
|
qr.make()
|
|
ausgabe.innerHTML = qr.createImgTag(6, 0, "Einrichtungs-Code")
|
|
ausgabe.insertAdjacentHTML(
|
|
"beforeend",
|
|
'<p class="warnung">Wer diesen Code abfotografiert, hat vollen Zugriff auf die ' +
|
|
"Schnittstelle. Nach dem Einrichten die Seite schließen.</p>",
|
|
)
|
|
ausgabe.classList.add("sichtbar")
|
|
} catch (fehler) {
|
|
melde(
|
|
"Der Code konnte nicht erzeugt werden: " +
|
|
String((fehler && fehler.message) || fehler) +
|
|
". Ist der Token vollständig eingefügt?",
|
|
)
|
|
}
|
|
})
|
|
</script>
|
|
</body>
|
|
</html>
|