Auf dem Handy war die Seite bisher nur mit der Desktopversion des Browsers
brauchbar. Drei Ursachen, alle nachgemessen bei 375 px:
Die Diagramme standen im Hochformat, 301 x 400 px - die Zeitachse lag auf
der kurzen Seite. Die Hoehe kam nicht aus dem Container: .chart-container
hatte inline height:100% in einem Elternelement ohne Hoehe, und die Canvas
trugen height="400px;", was kein gueltiger Zahlenwert ist. Die Hoehe steht
jetzt im Stylesheet, 400 px am Rechner und 250 px unter 768 px Breite, die
Beschriftung dort 8 statt 10 px.
Das Meteogramm hat sich nicht angepasst, sondern gequetscht. meteoblue legt
sein Bild einmal beim Laden fest und zeichnet es bei einer Breitenaenderung
nicht neu; unter etwa 600 px ueberlagern sich Wochentage und Stundenachse.
Es wird jetzt in mindestens 640 px Breite geladen - auf breiten Karten
gleich in Kartenbreite, dort aendert sich nichts - und, wenn die Karte
schmaler ist, als Ganzes verkleinert. Zuschnitt und Skalierung setzt das
Skript direkt am Element, nicht ueber das Stylesheet: sonst haengt die
Darstellung an einer womoeglich veralteten solar.css im Browser-Cache, und
der breit geladene Rahmen ragt ungeschnitten aus der Karte.
Passend dazu bekommt solar.css eine Versionskennung aus filemtime(), wie sie
die Skripte in footer.php und adminlte.min.css laengst tragen. Ohne sie kam
auf schon einmal besuchten Geraeten die alte Datei aus dem Cache.
Die Statistik-Kacheln lagen in nackten .col ohne Breitenangabe; Bootstrap
laesst sie dann nach Textlaenge umbrechen, mal zwei, mal drei je Zeile und
unterschiedlich breit. Die Breite kommt jetzt aus row-cols-2/row-cols-md-4,
und die Karte fuellt die Zeilenhoehe, damit die Werte trotz ein- oder
zweizeiliger Titel auf einer Linie stehen.
Zuletzt sind die Abstaende unter 768 px enger gefasst - auf .app-content
beschraenkt, damit Seitenleiste, Kopfzeile und Modals unberuehrt bleiben:
Spaltenabstand 24 -> 8 px, Kartenabstand 24 -> 12 px, Polsterung 16 -> 8 px.
Die nutzbare Breite steigt damit von 301 auf 317 px.
Geprueft bei 375 und 1200 px auf Solar, Historie, Heizung, Home und Wetter:
kein Querlauf, am Rechner unveraenderte Werte.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Ventilsteuerung veroeffentlicht neuerdings je Steuerung zwei weitere
Topics: WateringToday mit der heute schon gelaufenen Zeit je Ventil und
LastWateringDay mit derselben Summe fuer den letzten Tag, an dem ueberhaupt
bewaessert wurde. Beide tauchen als zweite Zeile in der Zonenkarte auf:
"Heute 35m - Vortag 30m".
WateringToday wird gegen das heutige Datum geprueft. Die Nachricht ist
retained; nach einem Tageswechsel ohne Bewaesserung stuende sonst die Summe
von gestern als heutige da. Passt der Tag nicht, zeigt die Karte "--" und
nennt den Stand im Titel. Die Spalte heisst "Vortag" und nicht "Gestern",
weil LastWateringDay einen eigenen wateringDay mitfuehrt - es ist der letzte
Tag mit Bewaesserung, nicht zwangslaeufig der gestrige; das Datum steht im
Titel.
Geprueft: leeres Topic, Nutzlast mit 0 Sekunden, gefuellte Werte, veraltete
Nutzlast, fehlendes Feld, kaputtes JSON, Datumsformat - zehn Faelle, keine
Abweichung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Modal ist 500 px breit (modal-dialog ohne Groessenklasse). Die Tabelle
mit fuenf Spalten samt Zahlenfeld war darin schon eng; jede weitere Angabe
haette sie gesprengt. Statt der Tabelle bekommt jede Zone eine eigene Karte
mit Zustandsschild, Steuerzeile und den Zeiten darunter, die Automatik eine
eigene Karte darueber - dort sitzt auch die kombinierte Automatik, die zwei
Zonen bedient.
Die Automatik-Karte zeigt zusaetzlich, was auto_watering.py je Zone vorhat,
und begruendet es: "heute geplant - nur 0,2 mm Regen in 2 Tagen". Die Zahlen
stammen aus dem RainStatus-Topic, das die Seite ohnehin schon abonniert hat,
bisher aber nur fuer das Uhr-Symbol im SVG benutzte. Die genaue Regel steht
im Titel der Zeile, weil sich die Schwellen je Zone deutlich unterscheiden.
Die letzte Bewaesserung bleibt in der Zonenkarte, sie ist eine Eigenschaft
der Zone; die Automatik nennt sie nur als Begruendung.
Nebenbei sind die drei getrennten Zuordnungstabellen (wateringZones,
wateringIndicators, wateringHistoryRows) zu einer verschmolzen, die Ventil,
Automatik-Zone, Beschriftung und die SVG-Anzeige-Ids traegt. Damit wird aus
updateWateringStatus eine Schleife statt dreier, und die doppelt vorhandene
Knopflogik faellt weg. Alle 18 Zuordnungen und die Historienzeilen sind
gegen den vorherigen Stand geprueft, ohne Abweichung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Regel gegen Punkt-Pfade stand ausschliesslich in IfModule-Bloecken fuer
mod_rewrite und mod_alias. Ist keines der beiden geladen, entfaellt sie
ersatzlos und ohne Fehlermeldung, und .git waere wieder abrufbar - genau das
Fail-Open, das derselbe Commit-Satz bei restricted/.htaccess beseitigen
sollte.
Ergaenzt um eine Notbremse aus Kern-Direktiven, die immer verfuegbar sind:
FilesMatch auf die Einstiegsdateien eines Git-Verzeichnisses (HEAD, config,
index, packed-refs und weitere) sowie pack und idx in der Typenliste. Ohne
diese Dateien laesst sich das Verzeichnis nicht aufrollen, weil die
Objektdateien hexadezimale Namen tragen.
FilesMatch passt nur auf Dateinamen, nicht auf Pfadbestandteile, und kann
.git deshalb nicht vollstaendig sperren. Der Kommentar in der Datei sagt das
und benennt die belastbare Loesung: .git und restricted/ ausserhalb des
Dokumentenbaums legen.
Gegengeprueft: von den 40 Adressen, die die Seiten laden, faellt keine unter
die neuen Regeln; in .git greifen sie zusaetzlich fuer 13 Dateien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Funktion soll laut eigener Beschreibung den Zwischenspeicher verwerfen,
falls das SVG neu gezeichnet wird, leerte aber nur svgElements. letzterWert
blieb gefuellt, sodass setText, setAttr und setDisplay jeden Schreibzugriff
danach als "unveraendert" ueberspringen: die Knoten werden zwar neu
nachgeschlagen, aber nie beschrieben. Das frische SVG behaelt dann seine
Platzhalter, bis sich der jeweilige Messwert zufaellig aendert - bei den
Boegen mit auf volle Grad gerundeten Winkeln kann das dauern.
Nachgestellt: nach dem Verwerfen nur der Knoten bleibt die Anzeige auf "--",
mit geleertem letzterWert wird wieder geschrieben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Funktion bildete die uebergebene Breite mit Math.abs() ab. Die Aufrufer
rechnen 55*(1-exp(x)); kehrt sich das Vorzeichen der Messgroesse um, wird der
Ausdruck negativ. Frueher setzte das eine negative stroke-width und die Linie
blieb leer. Mit Math.abs() erschien stattdessen eine Animation voller Staerke
in unveraenderter Laufrichtung - das Diagramm zeigte einen Fluss, den es nicht
gab.
Beispiel: speist die EG-Etage mit 900 W zurueck, ergibt 55*(1-exp(900/2000))
den Wert -31,3. Math.abs machte daraus eine 31,3 breite laufende Linie, jetzt
sind es 0 und die Linie ruht.
Positive Breiten und die Schwelle von 0,5 bleiben unveraendert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Laenge wurde mit intval() aus dem CIDR-String uebernommen. Bei einem
Tippfehler in LOCAL_NETWORKS lief die Funktion damit fail-open: "/-5" ergibt
intdiv(-5,8)=0 ganze Bytes und chr((0xFF << 13) & 0xFF)=Nullbyte, womit beide
maskierten Bytes gleich sind und jede Adresse in jedem Netz liegt. isLocal()
haette dann jede Anfrage aus dem Internet als lokal gewertet - und damit als
angemeldet, inklusive Freigabe der Passkey-Registrierung.
Die Laenge muss jetzt aus reinen Ziffern bestehen und darf die Adressbreite
nicht ueberschreiten; sonst liefert die Funktion false. "/-5", "/244", "/abc"
und "/" fallen damit durch, gueltige Angaben bleiben unveraendert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
restricted/.htaccess enthielt nur "deny from all". Das ist Apache-2.2-Syntax;
unter 2.4 funktioniert sie ausschliesslich mit geladenem mod_access_compat und
quittiert sonst mit einem Fehler. Der Schutz haengt damit an einem Modul, das
es nur aus Abwaertskompatibilitaet gibt. Jetzt stehen "Require all denied"
(2.4) und "Order allow,deny" (2.2) nebeneinander in IfModule-Bloecken.
Neu dazu ein .htaccess im Web-Root, denn restricted/ war nur ein Verzeichnis
von mehreren:
- Punkt-Pfade werden gesperrt. Das Git-Verzeichnis liegt im Web-Root; ueber
.git liesse sich sonst der komplette Quellcode samt Historie herunterladen.
Betrifft 65 Dateien, darunter .git/config und .claude/settings.local.json.
.well-known bleibt frei fuer Zertifikatsausstellungen.
- Dateitypen gesperrt, die der Browser nie anfordert: ini, py, pyc, json, md,
log, bak, example, sql, sqlite, db, tgz, gz, zip, yml, yaml. Betrifft 24
Dateien, darunter deviceDiscovery/config.ini mit Zugangsdaten,
tahoma_devices*.json und die Python-Skripte.
Gegengeprueft: von den 37 Adressen, die die Seiten laden, faellt keine unter
die neuen Regeln. Das Frontend holt ausschliesslich php, css, js, Bilder und
Schriften; tahoma_devices_classic.json liest PHP serverseitig.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Pruefung zerlegte beide Adressen mit explode(":") und verglich die ersten
vier Felder. Bei IPv4 entsteht dabei nur ein Feld, die uebrigen drei sind
undefiniert und null == null ist wahr - die Bedingung schrumpfte damit auf
REMOTE_ADDR == SERVER_ADDR. Kaeme je ein Reverse Proxy auf denselben Host,
haette das jeden Zugriff aus dem Internet als lokal gelten lassen.
Neu:
- LOCAL_NETWORKS als sichtbare Liste, derzeit 192.168.179.0/24.
- ipInNetwork() vergleicht binaer ueber inet_pton, also unabhaengig von der
Schreibweise. "2001:0db8:a:b::9" gilt jetzt zurecht als dasselbe /64 wie
"2001:db8:a:b::9" - vorher fiel dieser Client durch.
- Das IPv6-/64 wird weiterhin aus SERVER_ADDR abgeleitet, da der Provider das
Praefix vergibt.
- REMOTE_ADDR wird mit filter_var geprueft; "192.168.179.44 evil" galt vorher
wegen str_starts_with als lokal.
- Auf IPv6 abgebildete IPv4-Adressen (::ffff:192.168.179.44) werden
normalisiert. Ein Dual-Stack-Socket meldet LAN-Clients so; bisher fielen
sie durch.
- $_SESSION["local"] wird in beiden Zweigen gesetzt, nicht nur im positiven.
Verhaltensaenderung: Anfragen, deren Absender gleich SERVER_ADDR ist - etwa
127.0.0.1 - gelten nicht mehr automatisch als lokal. Im Repo ruft nichts die
Seite ueber HTTP vom Server selbst auf. Wird das doch gebraucht, gehoert das
betreffende Netz in LOCAL_NETWORKS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
actorDetails.php, sensorDetails.php, fillActorDD.php und fillSensorDD.php
fragten die homeMesh-Datenbank ab und gaben Geraetenamen samt Steuer-URLs
(command_url, command_parameters.url) aus - ohne jede Zugriffspruefung.
Alle vier pruefen jetzt checkLogin(), bevor sie etwas ausgeben. Aufgerufen
werden sie ausschliesslich aus js/solar/autoActionFuncs.js auf der Home-Seite,
also aus einer angemeldeten Sitzung; der Regeleditor funktioniert unveraendert.
Die SQL war bereits sauber (intval), es fehlte nur die Autorisierung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Datei hatte keinerlei Zugriffspruefung. Damit liessen sich von beliebiger
Stelle die Somfy-Geraete auflisten (?action=devlist) sowie Rollladen abfragen
und verfahren (?action=move&device=...&pos=...&angle=...). Das Tahoma-Token
liegt serverseitig, ein Angreifer brauchte es also nicht.
checkLogin() ist jetzt vorgeschaltet, dafuer wird helper.php eingebunden. Im
Frontend gibt es derzeit keinen Aufrufer der Datei, ein bestehender Ablauf
kann also nicht brechen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
$_POST["ftt"] wurde nur auf "nicht leer" geprueft und dann roh in die
exec()-Kommandozeile gehaengt. Ein Wert wie "12:00 2>&1; befehl #" fuehrte
damit beliebige Shell-Befehle als Web-Benutzer aus.
Das Formularfeld ist ein <input type="time">, liefert also HH:MM. Der Wert
wird jetzt gegen genau dieses Format geprueft; alles andere gilt als "keine
Ladeplanung" und nimmt den vorhandenen else-Zweig. Zusaetzlich gehen ftt und
setTime durch escapeshellarg().
fte und evAmp waren durch die Vergleichs-Klemmung bereits faktisch numerisch,
laufen jetzt aber ueber intval() und eigene Variablen statt ueber $_POST.
Fuer gueltige Eingaben ist die erzeugte Kommandozeile unveraendert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getCreateArgs und processCreate waren ohne jede Pruefung erreichbar. Der
Einmalschluessel aus addUser.php steuerte nur, ob index.php die
Registrierungsseite anzeigt - authServer.php liess sich direkt aufrufen und
umging das. Da userId und userName fest hinterlegt sind, konnte damit jeder
mit zwei Requests einen eigenen Passkey fuer das vorhandene Konto anlegen.
checkAdduser() vermerkt eine erfolgreiche Schluesselpruefung jetzt als
$_SESSION["mayRegister"]. authServer.php laesst getCreateArgs, processCreate
und clearRegistrations nur noch durch, wenn dieser Vermerk hoechstens zehn
Minuten alt ist oder der Aufruf aus dem lokalen Netz kommt.
Anmeldung und Metadatenabfrage (getGetArgs, processGet,
queryFidoMetaDataService) bleiben unveraendert offen.
authServer.php bindet dafuer helper.php ein statt nur restricted/mysql.php;
das eigene session_start() entfaellt, helper.php erledigt es.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
authServer.php und ajax/getForecastData.php hatten Server, Benutzer, Passwort
und Datenbank noch einmal fest eingetragen und damit die Werte ueberschrieben,
die restricted/mysql.php ohnehin liefert. Beide Dateien binden die Konfiguration
bereits ein; die Doppelung ist entfallen.
getForecastData.php benutzt jetzt $mysql_solarUser/-Pass/-DB wie die uebrigen
Endpunkte, statt dieselben Werte unter den generischen Namen zu setzen.
restricted/mysql.php steht jetzt in .gitignore, restricted/mysql.php.example
dient als Vorlage fuer neue Arbeitskopien.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zusammenlegung
- Die sechs Endpunkte getConsData_/getProdData_{month,year,decade}.php sind
ajax/energyHistory.php?series=&range= gewichen, der gemeinsame Aufbau der
Chart-Daten steht in ajax/chartData.php. Die erzeugte SQL wurde gegen alle
sechs Originale abgeglichen.
- Die 13 Raeume der Home-Ansicht stehen nur noch in restricted/rooms.php.
home.php rendert die SVG-Kacheln daraus, homeMQTT.js bekommt dieselbe
Tabelle als JSON (346 -> 222 bzw. 428 -> 197 Zeilen).
- Gemeinsame JS-Helfer in js/solar/common.js; geteilte Bausteine fuer
Wetterkacheln, Chart-Skripte und das Formular der Ladesteuerung.
- Seitenvorlagen sind jetzt .php und werden eingebunden, statt per
file_get_contents zusammengesetzt zu werden.
Fehlerbehebungen
- ?action=weather fuehrte auf eine leere Seite, es gibt jetzt weather.php.
- homeMQTT.js griff auf ein #meteogram zu, das home.php nie hatte, und brach
damit den readystatechange-Handler ab.
- Die Heizungsseite abonnierte weatherStation/#, zeigte die Werte aber nie an.
Ladelast
- Chart.js, MQTT und Meteogramm werden nur noch auf den Seiten geladen, die
sie brauchen; die Seitentabelle dafuer steht in index.php.
- mqtt.js durch die minifizierte Fassung ersetzt (859 -> 359 KB). MQTT.js
5.14.1, byte-identisch mit dem signierten npm-Tarball.
- Rund 3,1 MB ungenutzte Vendor-Reste und AdminLTE-Demobilder entfernt.
- Solar 1796 -> 1240 KB, Home 1796 -> 873 KB, Historie 1796 -> 843 KB.
Speicher im Browser
- Chart.defaults.devicePixelRatio auf 1.5 gedeckelt. Die Zeichenpuffer der
Diagramme waren mit 20-45 MB der groesste Posten der Seite.
- Das Meteogramm-iframe laedt erst beim Hinscrollen.
Realtime-SVG
- Filterbereiche auf das noetige Mass verkleinert und den wirkungslosen
feOffset-Durchgang entfernt: 60 % weniger gerasterte Filterflaeche bei
pixelgleichem Ergebnis.
- Die Fluss-Animation laeuft ueber transform statt stroke-dashoffset, mit
Punkten statt Strichmuster; sie ruht, wo kein Fluss anliegt.
- Der Wertupdate schreibt nur noch bei echter Aenderung und benutzt
textContent statt innerHTML: 9 statt 86 Elemente pro Update, davon keines
mit Filter. describeArc rundet auf volle Grad.
- waterInfo auf scale(0.90) wie die uebrigen Info-Gruppen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Neues stündliches Python-Script (restricted/gartenbewaesserung/auto_watering.py): holt Niederschlags-
und Sonnenuntergangsdaten von Open-Meteo, wertet je Zone (Hochbeete/Tröge/Garten vorn) individuelle
Regen-Schwellenwerte und Mindest-Bewässerungsdauern aus und startet bei Bedarf per MQTT den passenden
Automatik-Modus der Ventilsteuerung im Zeitraum vor Sonnenuntergang; veröffentlicht den Regen-Status
zusätzlich stündlich als retained MQTT-Nachricht je Zone.
- Web-UI: neue Bewässerungs-Steuerung/-Anzeige (ajax/watering.php, js/solar/solarMQTT.js,
assets/img/realtime.svg) inkl. Live-Status-Icons (aus/geplant/pausiert/aktiv per Farbe und Symbol).
- AutoAction-Regeleditor (ajax/actorDetails.php, sensorDetails.php, fillActorDD.php, fillSensorDD.php,
tahoma.php, AutoAction.php) liest Actor-/Sensor-Parameter jetzt live aus der homeMesh-Datenbank statt
aus stark vereinfachten Altfeldern, inkl. zugehöriger Anpassungen am Device-Discovery-Tooling
(restricted/deviceDiscovery/*, neues logic_module.py).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>