Enerface — Architektur

Dashboard Administration

Diese Seite beschreibt, wie das Enerface-Dashboard gebaut ist, welche Funktionen es hat, wie es konfiguriert wird, wie das Docker-Image betrieben wird und wie man es lokal startet und ein erstes Konto anlegt. Sie ist öffentlich — sie enthält keine Geheimnisse.

1. Überblick

Enerface ist eine private, mandantenfähige Weboberfläche für Photovoltaikanlagen. Jedes Konto besitzt genau eine Anlage, identifiziert über die Basisnummer (z. B. 58). Es gibt keine öffentliche Registrierung: Konten legt nur ein Administrator unter /administration an.

Der Browser spricht nie direkt mit der Datenschicht. Ein einziges Go-Binary (cmd/enerface) liefert die statische Oberfläche, die JSON-API, den SQLite-Cache und die Hintergrundbefüllung. Die Oberfläche ist HTML/CSS/JS plus lokal eingebettetes Apache ECharts — kein CDN, kein Framework, kein Build-Schritt für das Frontend.

Die Zeitzone ist global (Europe/Zurich), die Währung fest CHF. Beides ist Absicht: die Oberfläche ist schweizerisch (Ortschaftssuche über GeoAdmin, Kantonszugehörigkeit), und eine zweite Zeitzone würde die Tagesaggregate im Cache nach Zeitzone schlüsseln müssen.

2. Warum es ein Backend gibt

Die Zeitreihen leben ausschliesslich auf der Datenschicht 1.grafana.enerface.app (Grafana vor InfluxDB, Bucket testing). Die Portal-API api.enerface.app kennt nur Momentanwerte, keine Historie.

Gemessen 2026-08-11: die Datenschicht sendet keine CORS-Header. Ein Preflight OPTIONS /api/ds/query antwortet 404, normale Antworten tragen kein Access-Control-Allow-Origin. Ein Browser darf sie deshalb nicht direkt abfragen.

Das Backend spricht server-to-server mit Grafana und arbeitet als Cache-through-Speicher:

Der anonyme Zugriff auf die Datenschicht ist eine Fehlkonfiguration des Betreibers. Wenn er geschlossen wird, bleibt alles bereits Geholte nutzbar; nur neue Daten fehlen. Ein reiner Durchreich-Proxy ohne Cache würde in dem Moment wertlos.

Anfrageweg: der Browser erreicht die Datenschicht nur über den Go-Prozess.

3. Aufbau

Browser

/ Dashboard (ECharts)
/administration Kontenverwaltung
Session-Cookies, kein JWT

Go-Prozess

cmd/enerface
eingebettetes UI, JSON-API, Prefetch-Schleife, argon2id

Datenschicht

Grafana 12 → InfluxDB
Flux, Topic enerbase/<base>/data
anonym, ohne CORS

↓

SQLite

data/cache.sqlite (WAL)
Samples, Coverage, Konten, Sessions, Einstellungen, Logos

Open-Meteo

Zwei-Tage-Tagesprognose 08–19 Uhr, 15-Minuten-Speicher, Schlüssel Koordinaten

GeoAdmin

Schweizer Ortschaftssuche und Kanton zu einem Punkt (api3.geo.admin.ch)

Ein Prozess, drei Wege nach aussen: Datenschicht, Wetter, Ortschaftssuche. Alles andere bleibt lokal.

Pakete

PfadAufgabe
cmd/enerfaceFlags, Listen, Prefetch, -healthcheck, -hash-password
internal/configTOML der Datenschicht, Admin-Geheimnisse aus der Umgebung, .env
internal/serverHTTP, Sessions, Admin-API, statische Dateien, Sicherheitsheader
internal/cacheSQLite: Zeitreihen nach Basisnummer, Konten, Coverage
internal/energyAnlage, Cache-through, Tiers, Kennzahlen, Live, Prefetch
internal/upstreamGrafana/Flux-Client, Open-Meteo, GeoAdmin
ui/Dashboard, Administration, diese Seite; go:embed ins Binary

Die statischen Dateien liegen im Binary. Mit -static ui (so starten dev.sh / dev.ps1) werden HTML/JS/CSS von der Platte gelesen, damit Frontend-Änderungen keinen Rebuild brauchen.

Antworten ab 1000 Byte werden gzip-komprimiert, wenn der Client das anbietet. /api/* ist Cache-Control: no-store; Vendor-Dateien unter /static/vendor/ sind ein Jahr unveränderlich cachebar.

4. Konten und Anlagen

Die Anlage ist eine Eigenschaft des Kontos, nicht des Prozesses. Der Prozess hält keine globale Anlage mehr.

Zwei Konten dürfen dieselbe Basisnummer teilen — dann teilen sie den Cache, was korrekt ist, weil es dieselbe physische Anlage ist. Der Cache ist nach Basisnummer geschlüsselt, nicht nach Konto: Samples von Basis A sind für Basis B unsichtbar.

Die Anlage hängt am Konto. Zwei Konten mit derselben Basisnummer teilen den Cache — das ist dieselbe physische Anlage.
Die Basisnummer ist ein unauthentifizierter Schlüssel. Grafana antwortet anonym. Eine vertippte Basisnummer erzeugt keinen Fehler — sie zeigt stillschweigend die Anlage eines Fremden und speichert sie unter dieser Nummer. Die Admin-Oberfläche verlangt deshalb eine Bestätigung, bevor eine Basis geändert wird.

5. Datenpfad

Rohdaten kommen als MQTT-Nachrichten, eine pro Minute, Measurement mqtt_consumer, Topic enerbase/<basis>/data. Energiekanäle sind Wh pro Minute: Summe über den Zeitraum ÷ 1000 = kWh. Die Skalierungsfaktoren der Enerface-Dashboards (0.012, 0.2) sind fensterabhängig und werden bewusst nicht übernommen.

Auflösungsstufen (Tiers)

TierFensterAuto-WahlLimit (erzwungen)Prefetch
1moKalendermonat, lokalSpanne > 400 Tage—ja
1dKalendertag> 21 Tage—ja
1hStunde> 3 Tage—ja
15m15 Minuten> 36 Stunden180 Tageja
1mMinutesonst14 Tagenein, nur on demand

auto wählt die Stufe nach der Länge des angefragten Zeitraums, damit ein Jahr nicht als Minutenwerte ankommt. Flux muss option location = timezone.location(name: "Europe/Zurich") setzen, sonst landen Tagesgrenzen auf UTC. In Flux ist 1m eine Minute und ein Monat 1mo.

Cache-through

Cache-through: abgeschlossene Lücken nachladen und speichern; das noch offene Fenster nie.

Die Tabelle covered unterscheidet «keine Daten in diesem Fenster» von «noch nie geholt». Ohne sie würde eine Lücke bei jedem Reload erneut die Datenschicht belasten, und ein leeres Fenster würde fälschlich als «erledigt» gelten.

Minutenwerte werden in 10-Tage-Blöcken abgefragt. Die Grafana-Antwort kommt entweder breit (ein Frame, viele Spalten) oder als ein Frame pro Kanal; der Client versteht beides. Fehler der Datenschicht stehen oft im Body bei HTTP 200 — ausgewertet wird results.A.error.

Hintergrundbefüllung

Fünf Sekunden nach Start, danach alle -prefetch (Standard 1 Stunde), wärmt eine Goroutine die Tiers 1mo / 1d / 1h / 15m für jedes Konto, das in den letzten 7 Tagen angemeldet war. Zwischen den Konten liegen 2 Sekunden, damit ein Aufwachen nicht als Burst bei Grafana ankommt. Ein fehlgeschlagenes Konto bricht den Durchlauf nicht ab. /api/admin/health zeigt das Ergebnis des letzten Laufs.

Live-Werte

GET /api/live benutzt dieselbe Pipeline wie die Zeitreihe, mit den letzten zehn Minuten auf 1m. Wh/min × 60 ergibt kW. Älter als 10 Minuten setzt stale: true; das Frontend zeigt dann ein Banner. Es gibt keinen zweiten Upstream-Aufruf und keine Extra-Credentials.

6. Kanäle und Kennzahlen

Die Kanalbelegung ist die validierte Enerbase-Verdrahtung. Sie wird pro Konto gespeichert, damit eine Anlage später abweichen kann, ist aber heute nicht editierbar — ein anderes Messkonzept braucht Code.

KanalBedeutungRohAggregation
ch1PV-ProduktionWh/minSumme
ch25GesamtverbrauchWh/minSumme
ch26EigenverbrauchWh/minSumme
ch2NetzeinspeisungWh/minSumme
ch3NetzbezugWh/minSumme
ch10BatterieladungWh/minSumme
ch9BatterieentladungWh/minSumme
ch7Batterie-SOC%Mittel
ch8BatterieleistungWMittel; positiv = laden
ch6Boiler-Einschaltdauer0/1Mittel = Duty Cycle

Farben sind geprüft, nicht geraten: ein Kanal behält seinen Farbton in jedem Chart, Paare auf einem Chart sind für Farbfehlsichtigkeit in Hell und Dunkel validiert. Netzbezug ist Violett statt Magenta, weil Magenta neben dem Aqua des Eigenverbrauchs unter Deuteranopie zusammenfiel.

Kennzahlen über den gewählten Zeitraum:

Die Identität Verbrauch = Eigenverbrauch + Netzbezug hält bis auf Rundung der ganzzahligen Rohwerte (Grössenordnung 0.1 kWh/Tag). Das Gegenstück PV = Eigenverbrauch + Einspeisung gilt nicht: die Batterie puffert über Tagesgrenzen, plus Wandlungsverluste. Die Oberfläche behauptet diese Identität deshalb nicht.

Wohin die Produktion geht und woher der Verbrauch kommt. Die Batterie puffert über die Tagesgrenze — daher gilt PV ≠ Eigenverbrauch + Einspeisung.

Ein Konto kann den ersten Betriebstag als «unvollständig» markieren (Messung PV schon da, Verbrauchszähler noch nicht). Der Tag wird in Tabelle und Fussnote gekennzeichnet und verzerrt Summen und Quoten.

7. Funktionen

Dashboard (/)

Administration (/administration)

Ohne ADMIN_USER / ADMIN_PASSWORD_HASH antwortet die Admin-Anmeldung 503. Ohne Konten kann sich niemand am Dashboard anmelden.

8. HTTP-Schnittstelle

Alles unter /api/ ausser /api/login, /api/admin/login und /api/healthz braucht eine Session. Kontobezogene Antworten stammen immer vom Aufrufer — nie von einem anderen Konto.

MethodePfadZweck
POST/api/login{"email","password"} → Cookie enerface_session
POST/api/logoutSession beenden
POST/api/password{"current","new"} Selbständerung
GET/api/metaStammdaten, Kanäle, Cache-Stand der eigenen Basis
GET/api/seriesstart/stop als lokale Tage YYYY-MM-DD, tier=auto|1mo|1d|1h|15m|1m
GET/api/liveNeueste Minute
GET/api/weatherPrognose der konfigurierten Ortschaft
GET/PUT/api/settingsEigene Dashboard-Einstellungen
GET/PUT/DELETE/api/settings/{city,name}-logoHochgeladene Wappen/Logos
GET/api/geo/cities, /kantonOrtschafts- und Kantonssuche
GET/api/healthzLiveness, ohne Session, ohne Geheimnisse

Die Admin-API sitzt hinter enerface_admin. Ein Dashboard-Cookie reicht für keinen dieser Pfade.

MethodePfadZweck
POST/api/admin/login{"username","password"}
POST/api/admin/logout
GET/api/admin/healthDatenschicht, DB-Zahlen, Prefetch
GET/POST/api/admin/accountsListe / Anlegen (Passwort einmal im Body)
GET/PUT/api/admin/accounts/{id}Detail / E-Mail, Basis, Teiltag
PUT/api/admin/accounts/{id}/settingsBenutzer-Einstellungen stellvertretend
GET/PUT/DELETE/api/admin/accounts/{id}/{city,name}-logo
POST/api/admin/accounts/{id}/reset-passwordNeues Passwort einmal; Sessions weg
POST/api/admin/accounts/{id}/disabled{"disabled":true}; Sessions weg

Öffentliche HTML-Seiten ohne Session: /, /administration, /architecture.html. Die APIs dahinter sind getrennt geschützt.

9. Konfiguration

Drei Orte, sie überlappen sich nicht:

OrtInhaltWarum dort
config.tomlZeitzone, Grafana-URL, Datasource-UID, Bucket, HTTP-TimeoutCommitbar: nichts Geheimes, nichts Pro-Kunde
Umgebung / .envADMIN_USER, ADMIN_PASSWORD_HASHGeheim, und der Administrator hat keine Kontenzeile
SQLite über /administrationKonten und alles Pro-Anlage: Basis, kWp, Batterie, Preise, Ortschaft, Logos, data_startLaufzeit, genau der Sinn der Admin-Seite

config.toml

timezone = "Europe/Zurich"

[grafana]
url = "https://1.grafana.enerface.app"
ds_uid = "P951FEA4DE68E13C5"
bucket = "testing"
http_timeout = "5m"

Vorlage: config.example.toml. Fehlt die Datei, kopieren dev.sh und dev.ps1 sie. Im Image liegt dieselbe Vorlage unter /etc/enerface.toml — abweichende Endpunkte per Bind-Mount überschreiben.

Umgebung

ADMIN_USER=admin
ADMIN_PASSWORD_HASH=argon2id$v=19$m=65536,t=3,p=4$...$...

Hash erzeugen:

go run ./cmd/enerface -hash-password
# Passwort tippen, dann Ctrl-D (Unix) bzw. Ctrl-Z Enter (Windows)

Unter Docker Compose jedes $ im Hash als $$ schreiben, sonst interpoliert Compose $v / $m. Bestehende Umgebungsvariablen gewinnen gegen .env (set-if-absent).

In der Entwicklung ist nichts davon nötig: dev.sh und dev.ps1 setzen admin / admin selbst und zeigen es beim Start an (§12).

Prozessflags

FlagStandardBedeutung
-listen127.0.0.1:8099Bind-Adresse. Im Container :8099
-dbdata/cache.sqliteSQLite-Pfad. Im Container /data/cache.sqlite
-configconfig.tomlUpstream-TOML. Im Container /etc/enerface.toml
-static(leer = Embed)UI von der Platte, für die Entwicklung
-secure-cookiefalseCookie-Flag Secure — sobald TLS davor sitzt, einschalten
-session-ttl720hBenutzer-Session
-admin-session-ttl8hAdmin-Session (nur im Speicher; Neustart loggt den Admin aus)
-prefetch1hHintergrundbefüllung; Werte unter einer Minute werden auf eine Minute angehoben
-healthcheck—Fragt /api/healthz und beendet sich; Healthcheck des Images
-hash-password—argon2id-Hash von stdin nach stdout
data/cache.sqlite hält den Cache und die Konten. Löschen kostet nicht nur einen Refill — jedes Konto ist weg. Die Zeitreihen bauen sich neu; die Konten nicht.

10. Docker-Image hosten

Was das Image ist

Mehrstufig: Build in golang:1.26-bookworm (CGO_ENABLED=0, Tests, -trimpath -ldflags="-s -w"), Laufzeit gcr.io/distroless/static-debian12. Distroless hat keine Shell — deshalb Exec-Form für ENTRYPOINT, CMD und HEALTHCHECK. Zeitzonendaten sind ins Binary kompiliert (time/tzdata). SQLite ist reines Go (modernc.org/sqlite), kein CGO, kein glibc-Bedarf zur Laufzeit.

ENTRYPOINT ["/enerface"]
CMD ["-listen", ":8099", "-db", "/data/cache.sqlite", "-config", "/etc/enerface.toml"]

Image beziehen

Das veröffentlichte Image liegt in der Forgejo-Registry derselben Instanz, nicht auf Docker Hub:

source.suntsu.ch/suntsu/enerfacedemo

Der Workflow Publish Docker image läuft nur von Hand (Repository → Actions → Run workflow). Er taggt immer den kurzen Commit-SHA, auf Wunsch latest und ein frei wählbares Extra-Tag. Anmeldung mit dem Repository-Secret REGISTRY_TOKEN (Forgejo-Token mit write:package). Provenance-Attestations sind abgeschaltet, damit in der Paketansicht kein unknown/unknown erscheint.

docker login source.suntsu.ch
docker pull source.suntsu.ch/suntsu/enerfacedemo:latest

Lokal bauen

./dockerbuild.sh                          # taggt enerface:local
ENERFACE_IMAGE=enerface:dev ./dockerbuild.sh

Direkt starten

docker login source.suntsu.ch
docker run -d --name enerface \
  --restart unless-stopped \
  -p 8099:8099 \
  -v enerface-data:/data \
  -v "$PWD/config.toml:/etc/enerface.toml:ro" \
  -e ADMIN_USER='admin' \
  -e ADMIN_PASSWORD_HASH='argon2id$v=19$m=65536,t=3,p=4$...' \
  source.suntsu.ch/suntsu/enerfacedemo:latest \
  -listen :8099 -db /data/cache.sqlite -config /etc/enerface.toml -secure-cookie

-secure-cookie gehört dazu, sobald TLS (Reverse-Proxy) davor sitzt. Ohne Volume ist der Cache — und jedes Konto — nach einem Recreate weg.

Docker Compose / Podman

docker-compose.yml mappt den Host-Port 8081 auf Container-Port 8099, nimmt Admin-Daten aus .env, mountet ./config.toml und ./data:

# einmal, die Registry ist privat
docker login source.suntsu.ch
# oder: podman login source.suntsu.ch

mkdir -p data          # bevor compose das Verzeichnis als root anlegt
cp config.example.toml config.toml
cp .env.example .env   # Hash einsetzen; $ als $$ escapen

docker compose up -d
# veröffentlichtes Image statt lokalem Build:
# ENERFACE_IMAGE=source.suntsu.ch/suntsu/enerfacedemo:latest docker compose up -d

Das Image läuft als uid 10001 und kann ein von Ihnen besessenes Host-Verzeichnis nicht beschreiben. Compose setzt deshalb user: "${UID:-1000}:${GID:-1000}". Legen Sie ./data vorher an — sonst erzeugt Compose es als root.

Compose-Command (weicht vom Image-CMD ab): -listen=:8099 -db=/data/cache.sqlite -config=/etc/enerface.toml -secure-cookie=false. Hinter TLS diese letzte Zeile auf true stellen oder das Flag ohne Wert übergeben.

Reverse-Proxy und TLS

Aktualisieren, Backup, Diagnose

docker compose pull
docker compose up -d

# Backup: Datei kopieren, während WAL läuft lieber den Container kurz stoppen
docker compose stop
cp data/cache.sqlite backup/cache-$(date +%F).sqlite
docker compose start

# Health
curl -fsS http://127.0.0.1:8081/api/healthz
docker inspect --format='{{.State.Health.Status}}' enerface

Ein Image-Update ersetzt nur das Binary. /data und die gemountete config.toml bleiben. Es gibt keinen Migrationspfad für vor-Mandanten-Datenbanken: eine alte sample-Tabelle ohne Spalte base lässt den Prozess mit einer klaren Fehlermeldung nicht starten — Datei löschen und Konten neu anlegen.

11. Sicherheit

12. Entwicklung: starten

Für die Entwicklung braucht es kein Docker — eine Go-Toolchain genügt. Die beiden Startskripte dev.sh (Linux, macOS) und dev.ps1 (Windows) tun dasselbe: fehlende Konfiguration anlegen, einen Administrator einrichten, den Prozess mit -static ui starten. Dadurch werden Änderungen an HTML, CSS und JS ohne Rebuild sichtbar — ein Reload im Browser reicht.

Voraussetzungen

Linux und macOS

./dev.sh                      # http://127.0.0.1:8099, UI aus ./ui
PORT=9000 ./dev.sh            # anderer Port
HOST=0.0.0.0 ./dev.sh         # im LAN erreichbar, ohne TLS — mit Bedacht

Ist das Skript nicht ausführbar: chmod +x dev.sh, oder einmalig sh dev.sh. Beenden mit Ctrl-C.

Windows

.\dev.ps1                                   # http://127.0.0.1:8099, UI aus .\ui
$env:PORT = '9000'; .\dev.ps1               # anderer Port
$env:HOST = '0.0.0.0'; .\dev.ps1            # im LAN erreichbar, ohne TLS — mit Bedacht

Blockiert die Ausführungsrichtlinie das Skript, genügt ein einzelner Lauf ohne sie: pwsh -ExecutionPolicy Bypass -File .\dev.ps1. Beenden mit Ctrl-C. $env:PORT gilt für die ganze Shell-Sitzung — für einen einmaligen Port entweder eine neue Shell öffnen oder hinterher Remove-Item Env:PORT.

Dieselbe Aufgabe, beide Plattformen

AufgabeLinux / macOSWindows (PowerShell 7)
Starten./dev.sh.\dev.ps1
Anderer PortPORT=9000 ./dev.sh$env:PORT = '9000'; .\dev.ps1
Im LANHOST=0.0.0.0 ./dev.sh$env:HOST = '0.0.0.0'; .\dev.ps1
Eigener AdministratorADMIN_USER=ich ADMIN_PASSWORD_HASH='argon2id$…' ./dev.sh$env:ADMIN_USER = 'ich'; $env:ADMIN_PASSWORD_HASH = 'argon2id$…'; .\dev.ps1
Hash erzeugenprintf 'geheim' | go run ./cmd/enerface -hash-password'geheim' | go run ./cmd/enerface -hash-password
Testsgo test ./..., einzeln z. B. go test ./internal/server -run TestAdminLogin
Binary bauengo build ./cmd/enerface
BeendenCtrl-C

Administrator-Zugang in der Entwicklung

Beide Skripte richten einen festen lokalen Administrator ein und geben ihn beim Start auf der Konsole aus — es muss nichts nachgeschlagen und nichts in eine Datei geschrieben werden:

==> hashing the development admin password
==> http://127.0.0.1:8099
==> administration: http://127.0.0.1:8099/administration
    user: admin   password: admin
Wert
Benutzernameadmin
Passwortadmin
Adressehttp://127.0.0.1:8099/administration
admin / admin ist ausschliesslich für die lokale Entwicklung. Im Betrieb kommen ADMIN_USER und ADMIN_PASSWORD_HASH aus der Umgebung des Hosts (§9) — die Startskripte werden dort nicht benutzt.

Was die Skripte sonst noch tun

13. Konto und Anlage anlegen

Konten entstehen nur in der Oberfläche unter /administration, nie über eine Registrierung und nie von Hand in der Datenbank. Der Ablauf ist in der Entwicklung und im Betrieb identisch; es unterscheidet sich nur die Adresse: http://127.0.0.1:8099/administration in der Entwicklung, bei Docker Compose der Host-Port 8081, dahinter die Adresse des Reverse-Proxys.

Vom Administrator zum angemeldeten Benutzer. Das Passwort ist der einzige Schritt, der nicht wiederholbar ist.
  1. Administration öffnen und anmelden. In der Entwicklung admin / admin (§12). Die Admin-Anmeldung hat ein eigenes Cookie und läuft nach 8 Stunden ab; ein Neustart des Prozesses meldet den Administrator ebenfalls ab.
  2. «Konto anlegen» klicken. Das Formular hat nur drei Eingaben:
    • E-Mail-Adresse — reiner Identifier für die Anmeldung. Es gibt keine Mail-Infrastruktur, es wird nichts versendet.
    • Basisnummer — welche Anlage dieses Konto sieht, z. B. 58. Das ist die Anlage: eine eigene Anlagenverwaltung gibt es nicht.
    • Erster Tag unvollständig (optional) — wenn am ersten Betriebstag die PV-Messung schon lief, der Verbrauchszähler aber noch nicht. Der Tag wird dann in Tabelle und Fussnote gekennzeichnet.
  3. Passwort sofort notieren. Es wird erzeugt (vier Vierergruppen, ohne leicht verwechselbare Zeichen) und genau einmal angezeigt — mit einem Kopieren-Knopf. Es gibt keinen Weg, es später zu lesen; verloren heisst «Passwort zurücksetzen», und das verwirft alle Sessions des Kontos.
  4. data_start nicht tippen. Der Beginn der Historie wird beim Anlegen automatisch mit einer Flux-first()-Abfrage gegen die Basisnummer ermittelt. Antwortet die Datenschicht gerade nicht, bleibt das Feld leer und lässt sich später im Detail nachtragen.
  5. Anlage konfigurieren. Konto in der Liste öffnen, Abschnitt Dashboard-Einstellungen: Ortschaft (mit GeoAdmin-Vorschlägen, der Kanton folgt automatisch), Anzeigename, Anlagenleistung in kWp, Batteriekapazität in kWh, «Daten ab», Bezugspreis und Einspeisetarif in CHF/kWh, dazu Wappen- und Namenslogo. Das Detail hat zwei getrennte Formulare mit je eigenem «Speichern»: oben Konto (E-Mail, Basis, Teiltag), unten die Einstellungen.
  6. Gegenprobe. Abmelden, das Dashboard auf / öffnen und sich mit E-Mail und dem notierten Passwort anmelden. Die Kennzahlen zu Geld erscheinen erst, wenn der jeweilige Tarif gesetzt ist; der spezifische Ertrag erst mit kWp.

Ohne kWp, Tarife oder Ortschaft funktioniert das Dashboard vollständig — die betroffenen Kacheln und die Wetterleiste erscheinen dann schlicht nicht. Nachträglich änderbar ist alles ausser dem einmalig angezeigten Passwort.

Eine vertippte Basisnummer erzeugt keinen Fehler: Grafana antwortet anonym, das Konto zeigt still die Anlage eines Fremden und der Cache füllt sich unter der falschen Nummer. Deshalb fragt die Oberfläche beim Ändern einer bestehenden Basis nach einer Bestätigung — beim Anlegen prüft sie niemand. Zahl vor dem Speichern gegenlesen.

Ein Konto wird nie hart gelöscht, nur deaktiviert («Konto deaktivieren»); dabei verfallen sofort alle seine Sessions. Zwei Konten dürfen dieselbe Basisnummer tragen — sie teilen sich dann denselben Cache, weil es dieselbe physische Anlage ist (§4).