Dashboard: Doppelungen zusammengelegt, Lade- und Renderlast gesenkt

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>
This commit is contained in:
2026-08-26 22:54:28 +02:00
co-authored by Claude Opus 5
parent 29f752eafe
commit 6b8556567b
68 changed files with 1555 additions and 60085 deletions
+50 -44
View File
@@ -1,21 +1,30 @@
<style>
.stream{
animation: stream 2s steps(40) infinite;
/* Fluss-Animation als transform statt stroke-dashoffset.
stroke-dashoffset ist nicht kompositierbar - jede Stufe kostete einen
Neuzeichenvorgang auf dem Hauptthread. Ein transform kann der Compositor
dagegen selbst verschieben.
Die Punkte laufen eine Periode (111 Einheiten) weit, danach steht der
naechste an derselben Stelle - der Zyklus schliesst nahtlos.
--dx/--dy stehen pro Linie im style-Attribut, --w setzt setFlowWidth().
steps(40) = 20 Bewegungen pro Sekunde, wie in der urspruenglichen
Fassung. Das deckelt die Neuzeichnungen fuer den Fall, dass der Browser
das transform doch nicht kompositiert; auf dem Compositor kostet die
Stufung nichts. Fuer volle Fluessigkeit: linear statt steps(40). */
.stream, .stream-rev {
animation: flow 2s steps(40) infinite;
}
@keyframes stream {
100%{
stroke-dashoffset: -110;
.stream-rev {
animation-direction: reverse;
}
}
.stream-rev{
animation: stream-rev 2s steps(40) infinite;
@keyframes flow {
to { transform: translate(var(--dx), var(--dy)); }
}
@keyframes stream-rev {
100%{
stroke-dashoffset: 110;
.stream circle, .stream-rev circle {
r: calc(var(--w, 0px) / 2);
fill: gray;
}
}
.powerTextClass {
.powerTextClass {
font-size: 27px;
font-family: sans-serif;
text-anchor: middle;
@@ -63,6 +72,16 @@
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<clipPath id="clip_consOGani"><rect x="-30" y="-30" width="404.4" height="60" transform="translate(770.0,380.0) rotate(-64.1790)"/></clipPath>
<clipPath id="clip_consAni"><rect x="-30" y="-30" width="400.0" height="60" transform="translate(430.0,380.0) rotate(0.0000)"/></clipPath>
<clipPath id="clip_consEGani"><rect x="-30" y="-30" width="386.5" height="60" transform="translate(770.0,380.0) rotate(62.6501)"/></clipPath>
<clipPath id="clip_consUGani"><rect x="-30" y="-30" width="396.2" height="60" transform="translate(770.0,380.0) rotate(120.3791)"/></clipPath>
<clipPath id="clip_consHeatAni"><rect x="-30" y="-30" width="410.0" height="60" transform="translate(770.0,380.0) rotate(0.0000)"/></clipPath>
<clipPath id="clip_consEVani"><rect x="-30" y="-30" width="360.0" height="60" transform="translate(920.0,670.0) rotate(0.0000)"/></clipPath>
<clipPath id="clip_consEVOGani"><rect x="-30" y="-30" width="360.0" height="60" transform="translate(920.0,70.0) rotate(0.0000)"/></clipPath>
<clipPath id="clip_consGridani"><rect x="-30" y="-30" width="390.0" height="60" transform="translate(100.0,380.0) rotate(0.0000)"/></clipPath>
<clipPath id="clip_pvani"><rect x="-30" y="-30" width="413.6" height="60" transform="translate(180.0,130.0) rotate(45.0000)"/></clipPath>
<clipPath id="clip_battani"><rect x="-30" y="-30" width="413.6" height="60" transform="translate(180.0,630.0) rotate(-45.0000)"/></clipPath>
</defs>
<g opacity="0">
<animate begin="lastInitAni.end" attributetype="CSS" attributeName="opacity" from="0.0" to="1.0"
@@ -81,44 +100,34 @@
+500 -300 (center +90 in x und y)
-->
<line x1="770" y1="380" x2="920" y2="70" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consOGani" class="stream" x1="770" y1="380" x2="920" y2="70" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consOGani)"><g id="consOGani" class="stream" style="--dx:48.347px;--dy:-99.918px"><circle cx="721.7" cy="479.9"/><circle cx="770.0" cy="380.0"/><circle cx="818.3" cy="280.1"/><circle cx="866.7" cy="180.2"/><circle cx="915.0" cy="80.2"/><circle cx="963.4" cy="-19.7"/></g></g>
<line x1="430" y1="380" x2="770" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consAni" class="stream" x1="430" y1="380" x2="770" y2="380" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consAni)"><g id="consAni" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="319.0" cy="380.0"/><circle cx="430.0" cy="380.0"/><circle cx="541.0" cy="380.0"/><circle cx="652.0" cy="380.0"/><circle cx="763.0" cy="380.0"/><circle cx="874.0" cy="380.0"/></g></g>
<line x1="770" y1="380" x2="920" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consEGani" class="stream" x1="770" y1="380" x2="920" y2="670" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consEGani)"><g id="consEGani" class="stream" style="--dx:50.996px;--dy:98.592px"><circle cx="719.0" cy="281.4"/><circle cx="770.0" cy="380.0"/><circle cx="821.0" cy="478.6"/><circle cx="872.0" cy="577.2"/><circle cx="923.0" cy="675.8"/></g></g>
<line x1="770" y1="380" x2="600" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consUGani" class="stream" x1="770" y1="380" x2="600" y2="670" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consUGani)"><g id="consUGani" class="stream" style="--dx:-56.135px;--dy:95.759px"><circle cx="826.1" cy="284.2"/><circle cx="770.0" cy="380.0"/><circle cx="713.9" cy="475.8"/><circle cx="657.7" cy="571.5"/><circle cx="601.6" cy="667.3"/><circle cx="545.5" cy="763.0"/></g></g>
<line x1="770" y1="380" x2="1120" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consHeatAni" class="stream" x1="770" y1="380" x2="1120" y2="380" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consHeatAni)"><g id="consHeatAni" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="659.0" cy="380.0"/><circle cx="770.0" cy="380.0"/><circle cx="881.0" cy="380.0"/><circle cx="992.0" cy="380.0"/><circle cx="1103.0" cy="380.0"/><circle cx="1214.0" cy="380.0"/></g></g>
<line x1="920" y1="670" x2="1220" y2="670" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consEVani" class="stream" x1="920" y1="670" x2="1220" y2="670" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consEVani)"><g id="consEVani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="809.0" cy="670.0"/><circle cx="920.0" cy="670.0"/><circle cx="1031.0" cy="670.0"/><circle cx="1142.0" cy="670.0"/><circle cx="1253.0" cy="670.0"/></g></g>
<line x1="920" y1="70" x2="1220" y2="70" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consEVOGani" class="stream" x1="920" y1="70" x2="1220" y2="70" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consEVOGani)"><g id="consEVOGani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="809.0" cy="70.0"/><circle cx="920.0" cy="70.0"/><circle cx="1031.0" cy="70.0"/><circle cx="1142.0" cy="70.0"/><circle cx="1253.0" cy="70.0"/></g></g>
<line x1="100" y1="380" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="consGridani" class="stream" x1="100" y1="380" x2="430" y2="380" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_consGridani)"><g id="consGridani" class="stream" style="--dx:111.000px;--dy:0.000px"><circle cx="-11.0" cy="380.0"/><circle cx="100.0" cy="380.0"/><circle cx="211.0" cy="380.0"/><circle cx="322.0" cy="380.0"/><circle cx="433.0" cy="380.0"/></g></g>
<line x1="180" y1="130" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="pvani" class="stream" x1="180" y1="130" x2="430" y2="380" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_pvani)"><g id="pvani" class="stream" style="--dx:78.489px;--dy:78.489px"><circle cx="101.5" cy="51.5"/><circle cx="180.0" cy="130.0"/><circle cx="258.5" cy="208.5"/><circle cx="337.0" cy="287.0"/><circle cx="415.5" cy="365.5"/><circle cx="494.0" cy="444.0"/></g></g>
<line x1="180" y1="630" x2="430" y2="380" style="stroke:#777;stroke-width:2;stroke-dasharray:1,6;" />
<line id="battani" class="stream" x1="180" y1="630" x2="430" y2="380" stroke-linecap="round"
stroke-width="0" stroke="gray" stroke-dasharray="1,110" />
<g clip-path="url(#clip_battani)"><g id="battani" class="stream" style="--dx:78.489px;--dy:-78.489px"><circle cx="101.5" cy="708.5"/><circle cx="180.0" cy="630.0"/><circle cx="258.5" cy="551.5"/><circle cx="337.0" cy="473.0"/><circle cx="415.5" cy="394.5"/><circle cx="494.0" cy="316.0"/></g></g>
</g>
</svg>
@@ -127,21 +136,18 @@
style="position:absolute;top:0;"
viewBox="-100 -100 1600 950" width="100%" height="100%" id="realtimeSvg">
<defs>
<filter id="shadow" x="-0.5" y="-0.5" width="200%" height="200%">
<feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
<feMorphology in="offOut" result="offOut" operator="dilate" radius="0.7" />
<filter id="shadow" x="-0.3" y="-0.3" width="160%" height="160%">
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="0.7" />
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="0.5" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
<filter id="shadowMiddle" x="-1.0" y="-1.0" width="300%" height="300%">
<feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
<feMorphology in="offOut" result="offOut" operator="dilate" radius="3" />
<filter id="shadowMiddle" x="-0.4" y="-0.4" width="180%" height="180%">
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="1" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
<filter id="shadowBig" x="-1" y="-1" width="300%" height="300%">
<feOffset result="offOut" in="SourceAlpha" dx="0" dy="0" />
<feMorphology in="offOut" result="offOut" operator="dilate" radius="3" />
<filter id="shadowBig" x="-0.45" y="-0.45" width="190%" height="190%">
<feMorphology in="SourceAlpha" result="offOut" operator="dilate" radius="3" />
<feGaussianBlur result="offOut" in="offOut" stdDeviation="2" />
<feBlend in="SourceGraphic" in2="offOut" mode="normal" />
</filter>
@@ -581,7 +587,7 @@
style='display:none;mix-blend-mode: difference;fill:#70CCBB;' href="#heater" />
</g>
<g transform="translate(330,280), scale(0.70,0.70)" id="waterInfo" class="clickable" onclick="openModal('watering')">
<g transform="translate(330,280), scale(0.90,0.90)" id="waterInfo" class="clickable" onclick="openModal('watering')">
<animateMotion begin="0.5s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
keySplines=".1,0,.27,1" path="M0 0 L340 0" fill="freeze" />
<animateMotion begin="1.2s" dur=".7s" repeatCount="1" calcMode="spline" keyTimes="0;1"
@@ -591,7 +597,7 @@
d="M 99.863863 22.00012 A 78 78 0 1 0 100 22" />
<use href="#water" transform="translate(60,55), scale(0.20,0.20)" fill="#3A6292" />
<!-- Wasserstand und Temperatur werden von der Bewässerungssteuerung nicht unterstützt.
<text id="waterText" filter="url(#shadowMiddle)" class="powerTextClass" text-anchor="middle" x="100" y="55">--.- cm</text>
<text id="waterText" filter="url(#shadowMiddle)" class="powerTextClass" text-anchor="middle" x="100" y="55">-.- cm</text>
<text id="waterTemp" filter="url(#shadowMiddle)" class="powerTextClass" text-anchor="middle" x="100" y="88">-.- °C</text>
-->
<use id="wateringFrontIcon" href="#gardenPlantIcon" transform="translate(152,162),rotate(310),scale(1.3)" fill="#6c757d" color="#6c757d">