Technische Dokumentation
Diese Dokumentation beschreibt den vollständigen technischen Aufbau der Webseite bastianherold.ch. Sie richtet sich an dich als technisch Verantwortlichen, damit du die Webseite pflegen und bei Bedarf weiterentwickeln kannst – auch ohne Programmierkenntnisse. Jeder Abschnitt erklärt zuerst was etwas tut, dann warum es so gemacht wurde, und schliesslich wo du es findest.
Inhaltsverzeichnis
- Überblick – Was ist diese Webseite?
- Azure – Die Cloud dahinter
- Projektstruktur – Wo liegt was?
- Frontend – Was der Besucher sieht
- API – Die Schnittstellen im Hintergrund
- Authentifizierung – Wer darf was?
- CMS – Inhalte verwalten
- Bilder & Medien – Upload und Speicherung
- Analytics – Besucherstatistiken
- LiftAir – Produktseite & Shop
- Sicherheit – Schutz der Webseite
- SEO & Performance
- Umgebungsvariablen – Konfiguration
- Wartung & Weiterentwicklung
1. Überblick – Was ist diese Webseite?
bastianherold.ch ist eine persönliche Webseite für Bastian Herold, einen Gleitschirmpiloten aus Bayern. Die Webseite wurde am 1. März 2026 an Bastian übergeben und zeigt seine Projekte, sein selbstgebautes Variometer und Eindrücke vom Gleitschirmfliegen.
Die Webseite besteht aus folgenden Bereichen
- Öffentliche Hauptseite – Vier Sektionen: Über mich, Variometer, Gleitschirm, Kontakt
- LiftAir Produktseite – Eigener Bereich für das LiftAir Variometer mit Landingpage, Produktseite, Shop und Konfigurations-App
- Members Bereich – Geschützter Bereich für eingeladene Benutzer mit exklusiven Inhalten
- Admin Bereich – Dashboard mit Login-Logs und Content-Changelog
- Admin Content Editor (CMS) – Zum Bearbeiten aller Texte, Bilder und Galerien
- Admin Analytics – Besucherstatistiken und Nutzungsdaten
- Datenschutzseite – Rechtlich notwendige Datenschutzerklärung (DSG/DSGVO)
Technologie-Stack
Einfach erklärt: Die Webseite ist eine «Statische Webseite» – das bedeutet, es gibt keinen klassischen Server der permanent läuft. Stattdessen liegen die HTML-, CSS- und JavaScript-Dateien einfach in der Cloud (Azure) und werden direkt an den Browser des Besuchers ausgeliefert. Die dynamischen Funktionen (Login, Inhalte speichern, E-Mails senden) laufen als «Serverless Functions» – kleine Code-Stücke, die nur dann ausgeführt werden, wenn sie gebraucht werden.
| Komponente | Technologie | Erklärung |
|---|---|---|
| Hosting | Azure Static Web Apps | Liefert die Webseite aus der Cloud aus |
| API (Backend) | Azure Functions (Node.js) | Kleine Programme für Login, Inhalte, E-Mails |
| Dateispeicher | Azure Blob Storage | Speichert Bilder, Inhalte und Login-Logs |
| Login | Microsoft Entra ID (Azure AD) | Login über ein Microsoft-Konto |
| Azure Communication Services | Versendet Kontaktformular-E-Mails | |
| Analytics | Application Insights | Erfasst Besucherdaten (ohne Cookies!) |
| Schutzschild | Azure Front Door + WAF | Rate-Limiting und Schutz vor Missbrauch der API |
| Frontend | Vanilla HTML/CSS/JS | Kein Framework – schnell und unkompliziert |
| Schriftart | Inter Variable | Selbst gehostet, eine Datei für alle Schriftstärken |
2. Azure – Die Cloud dahinter
«Azure» ist Microsofts Cloud-Plattform – vergleichbar mit einem riesigen Rechenzentrum, das man mieten kann. Für diese Webseite nutzen wir mehrere Azure-Dienste, die zusammenarbeiten. Hier eine Übersicht, was jeder Dienst tut und warum wir ihn brauchen.
2.1 Azure Static Web Apps (SWA)
Das ist der Hauptdienst – er hostet die gesamte Webseite. Stell dir vor, SWA ist wie ein intelligenter Ordner in der Cloud: Du legst deine HTML/CSS/JS-Dateien hinein, und Azure liefert sie weltweit schnell aus.
Das Besondere an SWA:
- Automatisches Deployment: Jedes Mal, wenn du den Code auf GitHub änderst (push), wird die Webseite automatisch neu veröffentlicht.
- Eingebauter Login: SWA bietet von Haus aus Microsoft-Login an – wir mussten keinen eigenen Login bauen.
- Integrierte API: Die Azure Functions (Backend-Code) sind direkt in SWA eingebettet und unter
/api/...erreichbar. - Routing-Schutz: In der Konfigurationsdatei können wir festlegen, welche Seiten/APIs nur für eingeloggte Benutzer erreichbar sind.
Konfigurationsdatei: staticwebapp.config.json im Hauptverzeichnis steuert alles: Login-Anbieter, geschützte Routen, Sicherheitsheader, URL-Umschreibungen.
2.2 Azure Functions (Serverless API)
Azure Functions sind kleine Programme, die nur laufen, wenn jemand sie aufruft. Statt einen ganzen Server dauerhaft zu betreiben, startet Azure diese Mini-Programme bei Bedarf und berechnet nur die tatsächliche Nutzung.
Unsere Webseite hat 11 Functions im api/ Ordner – sie werden im Abschnitt API detailliert erklärt.
2.3 Azure Blob Storage
«Blob Storage» ist ein Dateispeicher in der Cloud – denk an eine Art Dropbox, aber für die Webseite. Wir nutzen drei «Container» (= Ordner) darin:
| Container | Inhalt | Zugriff |
|---|---|---|
content |
content.json – alle Texte und Galerie-Referenzenmember-logins.json – Login-Protokoll Membersadmin-logins.json – Login-Protokoll Admin
|
Nur über die API (privat) |
images |
Hochgeladene Bilder/Videos für die öffentlichen Sektionen (Variometer, Gleitschirm etc.) | Öffentlich – direkt über URL abrufbar |
members-images |
Bilder/Videos für den Members Bereich | Privat – nur über /api/getMemberImage abrufbar (Login erforderlich) |
Datenschutz: Soft Delete, Versionierung & Lifecycle
Um versehentlich gelöschte oder überschriebene Dateien wiederherstellen zu können, sind im Storage Account folgende Schutzmechanismen aktiviert:
- Soft Delete (Blobs + Container): Gelöschte Dateien werden 7 Tage aufbewahrt und können wiederhergestellt werden.
- Blob Versioning: Bei jedem Überschreiben wird die alte Version automatisch aufbewahrt (z.B. frühere
content.json). - Lifecycle-Regel
CleanupOldVersions: Alte Versionen werden nach 7 Tagen in günstigeren Speicher (Cool Tier) verschoben und nach 30 Tagen endgültig gelöscht.
Wo? Azure Portal → Storage Accounts → Data protection (Soft Delete & Versioning) und Lifecycle management.
2.4 Microsoft Entra ID (Azure AD)
Das ist der Login-Dienst von Microsoft. Benutzer melden sich mit ihrem Microsoft-Konto an (z.B. Outlook, Hotmail oder Firmen-Konto). Die Webseite nutzt den «Multi-Tenant»-Modus – das heisst, jedes Microsoft-Konto kann sich einloggen. Ob der Benutzer dann tatsächlich Zugang bekommt, entscheidet die Allowlist (Erlaubnisliste) in den Umgebungsvariablen.
So funktioniert es: Der Benutzer klickt auf «Login» → wird zu Microsoft weitergeleitet → loggt sich ein → wird zurück zur Webseite geleitet → die API prüft, ob seine E-Mail auf der Erlaubnisliste steht.
2.5 Azure Communication Services (ACS)
Dieser Dienst verschickt die E-Mails, die über das Kontaktformular gesendet werden. ACS ist sozusagen der «Postbote» der Webseite – er nimmt die Nachricht entgegen und liefert sie an die konfigurierte Empfängeradresse ab.
2.6 Application Insights
Application Insights sammelt anonymisierte Besucherdaten: Wie viele Besucher kommen, welche Seiten am meisten angeschaut werden, aus welchen Ländern die Besucher kommen, welche Browser sie nutzen usw.
Wichtig: Die Webseite ist cookiefrei konfiguriert (disableCookiesUsage: true),
d.h. es werden keine Tracking-Cookies gesetzt. Deshalb ist kein Cookie-Banner nötig –
ein bewusster Vorteil für Datenschutz und Benutzererlebnis.
Kostenkontrolle: Daily Cap
Damit keine unerwarteten Kosten entstehen, ist ein Daily Cap von 0.1 GB (100 MB/Tag) konfiguriert.
Bei 80 % wird eine Warnung an kontakt@bastianherold.ch gesendet.
Ergibt ca. 3 GB/Monat → innerhalb des Azure-Gratis-Kontingents von 5 GB (keine Kosten).
Optional kann Data Sampling das Volumen weiter reduzieren.
Wo? Azure Portal → Application Insights → Configure → Usage and estimated costs → Daily cap / Data sampling.
2.7 Azure Front Door + WAF (Web Application Firewall)
Azure Front Door steht als Schutzschild vor der Webseite – alle Anfragen gehen zuerst durch Front Door, bevor sie bei der Static Web App ankommen. In Kombination mit einer WAF Policy schützt es vor Spam, DDoS und übermässigen API-Aufrufen.
Setup
- Typ: Azure Front Door (Standard Tier), Endpoint:
bastianherold-fd - Origin: Static Web App, Custom Domain:
bastianherold.ch - DNS: CNAME auf den Front Door Endpoint
- Die
sendEmail-API prüft überFRONTDOOR_ID, dass Anfragen nicht an Front Door vorbei gehen
WAF-Regeln (Rate Limiting)
| Regel | Typ | Priorität | Limit | Bedingung | Aktion |
|---|---|---|---|---|---|
RateLimitSendEmail |
Rate Limit | 100 | 10 Anfragen / Minute | Request URI contains /api/sendEmail |
Blockieren |
RateLimitAPI |
Rate Limit | 200 | 60 Anfragen / Minute | Request URI beginsWith /api/ |
Blockieren |
Die niedrigere Priorität greift zuerst: sendEmail ist strenger limitiert (10/min) als die restliche API (60/min).
Wo? Azure Portal → Front Door and CDN profiles → bastianherold-fd → Security → WAF Policy wafBastianherold → Custom rules.
2.8 Wie alles zusammenhängt
3. Projektstruktur – Wo liegt was?
Hier siehst du die gesamte Ordnerstruktur des Projekts mit einer Erklärung zu jeder Datei und jedem Ordner. Der Code wird über GitHub verwaltet und automatisch auf Azure veröffentlicht.
Wie jede API-Function aufgebaut ist
Jede Function (z.B. api/checkAccess/) besteht aus zwei Dateien:
function.json– Die Konfiguration: Welche HTTP-Methode (GET/POST)? Welcher URL-Pfad? Wie heisst die Funktion?index.js– Der Code: Was die Funktion tatsächlich tut.
4. Frontend – Was der Besucher sieht
Das «Frontend» ist alles, was im Browser des Besuchers läuft: HTML (Struktur), CSS (Aussehen) und JavaScript (Verhalten). Wir verwenden bewusst kein Framework (kein React, Vue etc.) – alles ist in reinem «Vanilla» HTML/CSS/JS geschrieben. Das macht die Webseite schnell, einfach zu verstehen und wartungsarm.
4.1 HTML-Seiten
| Seite | URL | Zweck | Geschützt? |
|---|---|---|---|
| Hauptseite | / | Öffentliche Landingpage mit 4 Sektionen + Kontaktformular | Nein |
| LiftAir Landingpage | /liftair/ | Einstiegsseite mit Vario/Shop/App-Boxen | Nein |
| LiftAir Vario | /liftair/vario/ | Produktseite mit Features, Galerie, Specs | Nein |
| LiftAir Shop | /liftair/shop/ | Bestellformular für das Variometer | Nein |
| LiftAir App | /liftair/app/ | BLE-Konfigurations-App (eigenes Projekt) | Nein |
| Members | /members/ | Exklusive Inhalte für berechtigte Benutzer | Ja (Member) |
| Admin Dashboard | /admin/ | Login-Logs + Änderungsprotokoll | Ja (Admin) |
| Admin Content | /admin/content/ | CMS-Editor für Texte und Galerien | Ja (Admin) |
| Admin Analytics | /admin/analytics/ | Besucherstatistiken | Ja (Admin) |
| Datenschutz | /datenschutz/ | Datenschutzerklärung (DSG/DSGVO) | Nein |
| Dokumentation | /docs/ | Diese technische Dokumentation | Nein (nicht verlinkt) |
4.2 CSS – Das Aussehen
Es gibt nur eine einzige CSS-Datei: css/styles.css (ca. 530 Zeilen). Sie steuert
das gesamte Erscheinungsbild aller Seiten. Hier die wichtigsten Konzepte:
Design Tokens (CSS Custom Properties)
Farben und Abstände sind als «Variablen» definiert, die man an einer Stelle ändern kann und sie wirken sich überall aus. Zum Beispiel ändert sich die gesamte Farbpalette automatisch, wenn der Benutzer den Dark Mode aktiviert.
| Variable | Light Mode | Dark Mode | Verwendung |
|---|---|---|---|
--color-bg | #ffffff (Weiss) | #141924 (Dunkelblau) | Hintergrund |
--color-text | #111 (Fast Schwarz) | #e6e6e6 (Hellgrau) | Text |
--color-surface | #ebeef3 (Hellgrau) | #1c2333 (Dunkelgrau) | Karten, Header, Footer |
--color-primary | #0a66c2 (Blau) | #5b9dff (Hellblau) | Links, Titel, Buttons |
--color-border | #cbd5e1 (Hellgrau) | #3a4356 (Dunkelgrau) | Rahmen, Trennlinien |
Responsive Design (Anpassung an Bildschirmgrösse)
Die Webseite passt sich an alle Bildschirmgrössen an. Hier die wichtigsten Breakpoints:
- Ab 921px – Desktop-Layout: Navigation nebeneinander, 3-Spalten-Grid
- 921–1080px – Spezialfall wenn Login-Links sichtbar sind (Auth-Expanded): Mobile Hamburger-Menü trotz grossem Bildschirm
- Unter 920px – Mobile: Hamburger-Menü, Sektionen untereinander, Lightbox im Vollbild
- Unter 800px – Grid wird einspaltig, Analytics-Layout angepasst
Wichtige CSS-Bereiche
- Schriftart – Inter Variable (Zeilen 1–12): Selbst gehosted,
font-display: swapfür schnelles Laden - Layout – Container mit max. 1100px Breite, zentriert
- Header – Klebt oben (sticky), halbdurchsichtig mit Blur-Effekt
- Karten – Abgerundete Ecken, Surface-Hintergrund
- Galerie – Horizontal scrollbar mit Snap-Effekt (rastet ein)
- Lightbox – Vollbild-Bildvorschau mit Pfeilen, Tastatur und Touch-Gesten
- Dark Mode – Automatisch via
:root.darkKlasse, alle Farben invertiert - Barrierefreiheit – Fokus-Indikatoren, Screen-Reader-Klassen, automatische Silbentrennung
4.3 JavaScript – Das Verhalten
Die Webseite verwendet 10 JavaScript-Dateien, die jeweils eine spezifische Aufgabe haben. Alle sind als IIFE (Immediately Invoked Function Expression) geschrieben – das bedeutet, sie laufen sofort und stören sich nicht gegenseitig.
main.js – Kernfunktionen (ca. 205 Zeilen)
Läuft auf jeder Seite und stellt die Grundfunktionen bereit:
- Theme Toggle: Wechselt zwischen Light/Dark Mode. Speichert die Wahl im Browser (
localStorage), respektiert die Systemeinstellung des Benutzers. - Hamburger-Menü: Auf kleinen Bildschirmen: öffnet/schliesst die Navigation als Dropdown.
- Galerie-Pfeile: Links/Rechts-Pfeile zum Scrollen durch die Galerie. Blendet Pfeile aus, wenn man am Anfang/Ende ist.
- Lightbox: Klick auf ein Galerie-Bild öffnet es gross. Unterstützt Tastatur (Escape, Pfeiltasten), Touch-Gesten (Wischen) und Videos.
- Easter Egg: Klick auf «Bastian Herold» im Footer zeigt ein verstecktes Bild. 🥚
auth.js – Login-UI (ca. 80 Zeilen)
Läuft nur auf der Hauptseite. Prüft, ob der Besucher eingeloggt ist, und passt die Navigation an:
- Eingeloggt + berechtigt → zeigt «Members» und «Logout» Links
- Eingeloggt aber nicht berechtigt → wird sofort ausgeloggt (Schutz!)
- Nicht eingeloggt → zeigt «Login» Link
content-loader.js – Dynamische Inhalte (ca. 190 Zeilen)
Läuft auf der Hauptseite. Holt die aktuellen Inhalte vom Server und zeigt sie an:
- Ruft
/api/getContentauf (mit 5 Sekunden Timeout) - Rendert Markdown-Text (fett, kursiv, Links) sicher über
marked+DOMPurify - Falls der Server nicht antwortet: zeigt die im HTML eingebetteten Platzhalter-Texte
- Baut Galerien dynamisch auf (Bilder + Videos mit Lazy Loading)
contact.js – Kontaktformular (ca. 21 Zeilen)
Kleine Datei, die das Kontaktformular validiert und abschickt:
- Prüft Pflichtfelder (Name, E-Mail, Nachricht)
- E-Mail-Format-Prüfung
- «Honeypot»-Schutz gegen Spam-Bots (unsichtbares Feld, das nur Bots ausfüllen)
- Sendet Daten an
/api/sendEmail
members-auth.js – Members Bereich (ca. 120 Zeilen)
Läuft auf der Members-Seite:
- Prüft ob der Benutzer eingeloggt ist (wenn nicht → Weiterleitung zum Login)
- Prüft ob der Benutzer berechtigt ist (wenn nicht → «Zugriff verweigert» + automatischer Logout nach 3 Sek.)
- Lädt Members-spezifische Inhalte und Galerien
content-admin.js – CMS Editor (ca. 650 Zeilen)
Die grösste JavaScript-Datei – sie macht den kompletten Content-Editor möglich. Wird im Abschnitt CMS detailliert erklärt.
admin-dashboard.js – Admin Dashboard (ca. 133 Zeilen)
- Zeigt die letzten Login-Versuche (Members + Admin) mit Status (Erfolg/Verweigert)
- Zeigt das Änderungsprotokoll: Wer hat wann welchen Inhalt geändert?
analytics.js – Besucherstatistiken (ca. 300 Zeilen)
Baut das Analytics-Dashboard mit Chart.js-Diagrammen. Wird im Abschnitt Analytics erklärt.
app-insights.js – Telemetrie (11 Zeilen)
Initialisiert Application Insights für anonymisierte Besucherzählung. Cookiefrei konfiguriert.
shop.js – LiftAir Shop (ca. 130 Zeilen)
Läuft auf der Shop-Seite (/liftair/shop/). Steuert das Bestellformular:
- Lädt das Produktbild automatisch aus der Content-API (erstes Variometer-Galeriebild)
- Berechnet den Gesamtpreis basierend auf Menge (wenn Preis festgelegt ist)
- Validiert alle Pflichtfelder inkl. Honeypot-Schutz
- Sendet die Bestellung an
/api/submitOrder - Zeigt bei Erfolg eine Bestätigungsansicht (Formular wird ausgeblendet)
- Platzhalter-Modus: Solange
UNIT_PRICE = nullist, zeigt der Shop «Preis auf Anfrage»
4.4 Externe Bibliotheken
Die Webseite nutzt drei kleine externe Bibliotheken, geladen über CDN (Content Delivery Network):
| Bibliothek | Version | Zweck | Genutzt auf |
|---|---|---|---|
| marked | 12.0.2 | Wandelt Markdown-Text in HTML um | Hauptseite, Members, CMS |
| DOMPurify | 3.3.1 | Bereinigt HTML, um Sicherheitslücken (XSS) zu verhindern | Hauptseite, Members, CMS |
| Chart.js | 4.5.1 | Zeichnet Diagramme für Analytics | Admin Analytics |
5. API – Die Schnittstellen im Hintergrund
«API» steht für Application Programming Interface – das sind Endpunkte (URLs), über die der
Browser des Besuchers mit dem Server kommuniziert. Zum Beispiel: Der Browser ruft /api/getContent
auf und bekommt die aktuellen Webseitentexte zurück.
Alle APIs leben im api/ Ordner. Hier eine Übersicht aller 11 Funktionen:
5.1 checkAccess – Member-Berechtigung prüfen
| URL | /api/checkAccess |
|---|---|
| Methode | GET |
| Zugriff | Nur eingeloggte Benutzer |
Was sie tut:
- Liest die Identität des eingeloggten Benutzers aus dem Azure-Header (
x-ms-client-principal) - Prüft, ob die E-Mail-Adresse in der Umgebungsvariable
ALLOWED_USERSsteht - Protokolliert den Login-Versuch (Erfolg oder Abgelehnt) in
member-logins.json - Antwortet mit
{ authorized: true/false, userEmail: "..." }
5.2 checkAdminAccess – Admin-Berechtigung prüfen
| URL | /api/checkAdminAccess |
|---|---|
| Methode | GET |
| Zugriff | Nur eingeloggte Benutzer |
Was sie tut: Identisch wie checkAccess, prüft aber gegen ALLOWED_ADMINS
und protokolliert in admin-logins.json.
5.3 getContent – Inhalte laden
| URL | /api/getContent |
|---|---|
| Methode | GET |
| Zugriff | Öffentlich (Jeder) |
Was sie tut:
- Liest
content.jsonaus dem Blob Storage - Für normale Besucher: Entfernt interne Metadaten (
_changeLog,lastModifiedBy) und setzt 5 Minuten Cache - Für Admins: Gibt alles zurück inkl. Änderungsprotokoll, kein Cache
- Falls noch kein Inhalt gespeichert wurde: Gibt
{ _empty: true }zurück → die Webseite zeigt dann die Platzhalter-Texte
5.4 saveContent – Inhalte speichern
| URL | /api/saveContent |
|---|---|
| Methode | POST |
| Zugriff | Nur Admins |
Was sie tut:
- Empfängt die bearbeiteten Inhalte vom CMS-Editor
- Validiert alles gründlich: Max. 512 KB, erlaubte Sektionen, Textlängen-Limits, Gallery-Limits
- Erkennt automatisch, was sich geändert hat (z.B. «Variometer › Karte 2 › Text geändert»)
- Speichert die Änderungen mit Zeitstempel und Benutzername in
content.json - Behält die letzten 20 Änderungseinträge als Protokoll
5.5 sendEmail – Kontaktformular-E-Mail
| URL | /api/sendEmail |
|---|---|
| Methode | GET, POST |
| Zugriff | Öffentlich (mit Schutzmassnahmen) |
Was sie tut:
- Empfängt Name, E-Mail und Nachricht vom Kontaktformular
- Mehrere Sicherheitsprüfungen: Front Door ID, Honeypot, Eingabelängen, E-Mail-Format, Header-Injection-Schutz
- Sendet die E-Mail über Azure Communication Services (ACS)
- Maskiert HTML-Zeichen in der E-Mail, um Code-Einschleusung zu verhindern
5.6 submitOrder – LiftAir-Shop-Bestellung
| URL | /api/submitOrder |
|---|---|
| Methode | POST |
| Zugriff | Öffentlich (mit Schutzmassnahmen) |
Was sie tut:
- Empfängt Bestelldaten vom LiftAir-Shop (Produkt, Menge, Name, Adresse, E-Mail)
- Sicherheitsprüfungen: Front Door ID, Honeypot, Eingabelängen (200 Zeichen pro Feld), E-Mail-Format, Menge 1–10
- Vergibt eine eindeutige Bestell-Nr. im Format
LA-{Zeitstempel} - Sendet zwei E-Mails über ACS:
- An den Betreiber: Vollständige Bestelldaten (Produkt, Menge, Preis, Kundendaten)
- An den Kunden: Bestätigung mit Bestellübersicht und Zahlungsinformationen (Bankdaten)
- Alle Eingaben werden mit
escapeHtml()maskiert - Empfängeradresse wird ausschliesslich aus
ORDER_EMAILgelesen (kein Fallback)
5.7 uploadImage – Bilder/Videos hochladen
| URL | /api/uploadImage |
|---|---|
| Methode | POST |
| Zugriff | Nur Admins |
Was sie tut:
- Empfängt ein Bild/Video als Base64-kodierte Daten
- Prüft Dateityp (WebP, JPEG, PNG, AVIF, GIF, MP4) und -grösse (max. 10 MB)
- Magic-Byte-Validierung: Prüft, ob der Dateiinhalt wirklich zum angegebenen Typ passt (verhindert, dass jemand z.B. eine ausführbare Datei als Bild tarnt)
- Speichert in
images(öffentlich) odermembers-images(privat) Container - Vergibt einen zufälligen UUID-Dateinamen (z.B.
a1b2c3d4-e5f6.webp) – sicher und eindeutig
5.8 deleteImage – Bilder löschen
| URL | /api/deleteImage |
|---|---|
| Methode | POST |
| Zugriff | Nur Admins |
Was sie tut:
- Löscht ein Bild/Video aus dem Blob Storage
- Erkennt automatisch, ob es ein öffentliches oder Members-Bild ist (anhand der URL)
- Schutz gegen Pfad-Traversierung (verhindert Zugriff auf andere Ordner)
- Idempotent: Gibt auch «Erfolg» zurück, wenn die Datei bereits gelöscht war
5.9 getMemberImage – Geschützte Members-Bilder
| URL | /api/getMemberImage?file=dateiname.webp |
|---|---|
| Methode | GET |
| Zugriff | Members + Admins |
Was sie tut:
- Fungiert als «Proxy» (Vermittler): lädt das Bild aus dem privaten Container und gibt es an den Browser weiter
- Prüft, ob der Benutzer Member ODER Admin ist
- Setzt korrekten Content-Type und 1 Stunde privaten Cache
Warum ein Proxy? Da Members-Bilder im privaten Container liegen, kann man sie nicht direkt über eine URL aufrufen. Die API fungiert als kontrollierter Durchgangskanal – nur berechtigte Benutzer bekommen das Bild.
5.10 getAnalytics – Besucherdaten
| URL | /api/getAnalytics?timespan=P7D |
|---|---|
| Methode | GET |
| Zugriff | Nur Admins |
Was sie tut:
- Fragt Application Insights über die REST-API ab (8 verschiedene Abfragen)
- Holt sich dafür einen Zugangstoken über OAuth2 (Client Credentials)
- Liefert: Seitenaufrufe, eindeutige Besucher, Sessions, Ladezeiten, Top-Seiten, Länder, Städte, Browser, Betriebssysteme
- Zeitraum wählbar: 7 Tage, 30 Tage oder 90 Tage
5.11 getLoginLog – Login-Protokoll
| URL | /api/getLoginLog |
|---|---|
| Methode | GET |
| Zugriff | Nur Admins |
Was sie tut:
- Liest
member-logins.jsonundadmin-logins.jsonparallel aus dem Blob Storage - Fügt sie zusammen, sortiert nach Datum (neueste zuerst)
- Gibt die letzten 20 Einträge zurück, jeweils mit Typ-Label (Members/Admin)
5.12 Gemeinsamer Code (shared/)
adminCheck.js
Wird von mehreren APIs importiert (saveContent, uploadImage, deleteImage,
getLoginLog). Enthält die Funktion checkAdmin(req), die den eingeloggten Benutzer
identifiziert und prüft, ob er Admin ist.
loginLogger.js
Protokolliert Login-Versuche in den Blob Storage. Besonderheiten:
- Fire-and-Forget: Läuft im Hintergrund und lässt nie den Hauptaufruf fehlschlagen
- Deduplizierung: Derselbe Benutzer mit gleichem Status innerhalb 1 Stunde wird nicht erneut geloggt
- Optimistic Locking: Nutzt ETags, um Schreibkonflikte zu verhindern (wenn zwei Logins gleichzeitig passieren)
- Max. 20 Einträge: Ältere werden automatisch entfernt
6. Authentifizierung – Wer darf was?
Die Webseite hat ein zweistufiges Berechtigungssystem: Erst muss sich der Benutzer einloggen (Authentifizierung), dann wird geprüft, ob er berechtigt ist (Autorisierung).
6.1 Der Login-Ablauf Schritt für Schritt
- Benutzer klickt auf «Login» in der Navigation
- Wird zu Microsoft weitergeleitet (
/.auth/login/aad) - Loggt sich dort mit seinem Microsoft-Konto ein
- Microsoft leitet ihn zurück zur Webseite (mit einem Session-Cookie)
- Die Webseite ruft
/.auth/meauf → bekommt die Benutzer-Identität - Die Webseite ruft
/api/checkAccessauf → die API prüft die E-Mail gegen die Allowlist - Der Login-Versuch wird im Blob gespeichert (Erfolg oder Abgelehnt)
- Die Webseite zeigt entsprechend an: Members-Link oder sofortiger Logout
6.2 Zugriffsmatrix
Wer darf auf welche Bereiche zugreifen?
| Bereich | Ohne Login | Eingeloggt (nicht berechtigt) | Member | Admin |
|---|---|---|---|---|
| Hauptseite | ✅ Lesen | ✅ Lesen | ✅ Lesen | ✅ Lesen |
| Kontaktformular | ✅ Senden | ✅ Senden | ✅ Senden | ✅ Senden |
| LiftAir (Vario/Shop/App) | ✅ Voll | ✅ Voll | ✅ Voll | ✅ Voll |
| Members Bereich | → Login | → Logout | ✅ Voll | ❌ (nur wenn auch in ALLOWED_USERS) |
| Admin Bereich | → Login | → Logout | ❌ | ✅ Voll |
| Inhalte bearbeiten | ❌ | ❌ | ❌ | ✅ |
| Bilder hochladen | ❌ | ❌ | ❌ | ✅ |
| Analytics | ❌ | ❌ | ❌ | ✅ |
| Members-Bilder sehen | ❌ | ❌ | ✅ | ✅ |
6.3 Benutzer verwalten
Die Benutzerlisten werden nicht in einer Datenbank verwaltet, sondern als einfache Umgebungsvariablen in den Azure-Einstellungen:
ALLOWED_USERS– Kommagetrennte Liste der Member-E-Mails, z.B.hans@example.com,anna@example.comALLOWED_ADMINS– Kommagetrennte Liste der Admin-E-Mails
Wichtig: Um einen neuen Benutzer hinzuzufügen oder zu entfernen, musst du die jeweilige Umgebungsvariable in den Azure Static Web Apps Einstellungen ändern. Wo genau steht im Abschnitt Umgebungsvariablen.
6.4 Sicherheitsentscheidung: Sofortiger Logout
Wenn sich ein Benutzer einloggt, der nicht auf der Allowlist steht, wird er sofort wieder
ausgeloggt. Das passiert sowohl auf der Hauptseite (auth.js) als auch im Members-Bereich
(members-auth.js, dort mit 3 Sekunden Verzögerung für die Meldung «Zugriff verweigert»).
So kann niemand, der nicht berechtigt ist, auch nur die geschützten Bereiche sehen.
7. CMS – Inhalte verwalten
Das CMS (Content Management System) ermöglicht es, alle Texte und Bilder der Webseite direkt im Browser
zu bearbeiten – ohne Code anfassen zu müssen. Erreichbar unter /admin/content/.
7.1 Wie die Inhalte gespeichert werden
Alle Inhalte der Webseite sind in einer einzigen Datei gespeichert: content.json
im Blob Storage Container content. Die Datei hat folgende Struktur:
7.2 Die vier editierbaren Sektionen
| Sektion | JSON-Key | Karten | Galerie |
|---|---|---|---|
| Über mich | ueberMich | ✅ (nur Text, kein Titel) | ❌ |
| Variometer | variometer | ✅ (Titel + Text) | ✅ |
| Gleitschirm | gleitschirm | ✅ (Titel + Text) | ✅ |
| Members | members | ✅ (Titel + Text) | ✅ |
7.3 Der CMS-Editor im Detail
Der Editor (js/content-admin.js, ca. 760 Zeilen) bietet folgende Funktionen:
Karten bearbeiten
- Jede Karte ist als aufklappbares Akkordeon dargestellt
- Im geschlossenen Zustand zeigt es eine Vorschau des Textes
- Titel und Text sind editierbar
- Text unterstützt Markdown:
**fett**,*kursiv*,[Link](url) - Über der Textarea gibt es eine Toolbar für Fett/Kursiv/Link
Galerie bearbeiten
- Bilder/Videos werden als visuelles Grid angezeigt
- Drag & Drop: Reihenfolge per Ziehen ändern
- Hinzufügen: Klick auf «+» oder Dateien per Drag & Drop auf den Editor ziehen
- Entfernen: X-Button auf dem Thumbnail → löscht auch die Datei im Blob Storage
- Alt-Text: Jedes Bild kann eine Beschreibung bekommen (für Barrierefreiheit)
- Limit: Maximal 10 Galerie-Einträge pro Sektion
- Erlaubte Formate: WebP, JPEG, PNG, AVIF, GIF, MP4
Speichern
- Sobald etwas geändert wird, erscheint ein schwebender «Speichern»-Button unten rechts
- Der Button zeigt den Status an: «Ungespeicherte Änderungen» → «Wird gespeichert...» → «Gespeichert»
- Wenn du die Seite verlassen willst, ohne zu speichern, kommt eine Warnung
7.4 Der Datenfluss
7.5 Änderungsprotokoll (Changelog)
Die saveContent-API erkennt automatisch, was geändert wurde. Sie vergleicht den alten mit dem
neuen Inhalt und erstellt Einträge wie:
- «Variometer › Karte 2 › Text der Karte geändert.»
- «Gleitschirm › Galerie › 1 Bild entfernt.»
- «Members › Karte 1 › Neuer Titel.»
- «Variometer › Galerie › Reihenfolge geändert.»
Diese Einträge werden mit Datum und Benutzer im Admin-Dashboard angezeigt.
8. Bilder & Medien – Upload und Speicherung
8.1 Upload-Prozess
- Admin wählt eine Datei aus (Klick oder Drag & Drop im CMS-Editor)
- Der Browser liest die Datei und kodiert sie als «Base64» (eine Textdarstellung der Datei)
- Der Browser sendet die Daten an
/api/uploadImage - Die API prüft: Dateityp erlaubt? Grösse unter 10 MB? Magic Bytes korrekt?
- Die API vergibt einen zufälligen Dateinamen (UUID) und speichert die Datei im Blob Storage
- Die API gibt die URL der hochgeladenen Datei zurück
- Das CMS fügt die URL automatisch in die Galerie ein
8.2 Öffentliche vs. private Bilder
| Öffentliche Sektionen | Members Sektion | |
|---|---|---|
| Container | images | members-images |
| Zugriff | Direkt über Blob-URL | Nur über /api/getMemberImage |
| URL-Format | https://account.blob.core.windows.net/images/uuid.webp | /api/getMemberImage?file=uuid.webp |
| Wer kann sehen? | Jeder | Nur Members + Admins |
8.3 Erlaubte Dateitypen
| Format | MIME Type | Magic Bytes |
|---|---|---|
| WebP | image/webp | RIFF....WEBP |
| JPEG | image/jpeg | FF D8 FF |
| PNG | image/png | 89 50 4E 47 |
| AVIF | image/avif | ftyp (Box) |
| GIF | image/gif | GIF87a / GIF89a |
| MP4 | video/mp4 | ftyp (Box) |
Magic Bytes: Jeder Dateityp hat bestimmte Bytes am Anfang der Datei, die ihn identifizieren. Die API prüft diese Bytes, damit niemand z.B. eine Schadsoftware als Bild hochladen kann.
9. Analytics – Besucherstatistiken
Das Analytics-Dashboard (/admin/analytics/) zeigt folgende Daten an:
9.1 KPI-Übersicht (Kennzahlen)
- Seitenaufrufe gesamt
- Eindeutige Besucher
- Sessions (Sitzungen)
- Durchschnittliche Ladezeit
9.2 Diagramme
- Tägliche Aufrufe + Besucher – Liniendiagramm über den gewählten Zeitraum
- Top Seiten – Welche Seiten am meisten besucht werden
- Länder, Bundesländer, Städte – Woher die Besucher kommen
- Browser & Betriebssysteme – Doughnut-Diagramme
9.3 Zeitraum wählen
Drei Buttons: 7 Tage, 30 Tage, 90 Tage. Die Diagramme aktualisieren sich entsprechend.
9.4 Funktionsweise
- Datensammlung: Das
app-insights.js-Script auf jeder öffentlichen Seite sendet anonymisierte Besucherdaten an Application Insights (ohne Cookies). - Datenabfrage: Die
getAnalytics-API fragt Application Insights über die REST-API ab. Dazu authentifiziert sie sich mit einem Service Principal (Client-ID + Secret) und führt 8 KQL-Abfragen parallel aus. - Darstellung:
analytics.jsbaut die Chart.js-Diagramme auf und passt die Farben dynamisch an den aktuellen Theme-Modus an (Light/Dark).
9.5 Kostenkontrolle
Die Kosten sind durch einen Daily Cap und das Azure-Gratis-Kontingent gedeckt. Details und Konfiguration findest du im Abschnitt 2.6.
10. LiftAir – Produktseite & Shop
LiftAir ist ein eigenständiger Bereich der Webseite unter /liftair/. Er umfasst die Produktpräsentation
für das selbstgebaute Variometer, einen Bestellshop und eine BLE-Konfigurations-App. Der Bereich hat eine
eigene Navigation (Logo + Vario/Shop/App) und ist komplett öffentlich zugänglich.
10.1 Aufbau – Die vier Bereiche
| Seite | URL | Zweck |
|---|---|---|
| Landingpage | /liftair/ | Einstiegsseite mit drei Boxen (Vario, Shop, App) und grossem Logo |
| Vario (Produktseite) | /liftair/vario/ | Features, Bildergalerie, technische Daten, CTA zum Shop |
| Shop | /liftair/shop/ | Bestellformular mit Produktbild, Preis und Adresseingabe |
| App | /liftair/app/ | BLE-Konfigurations-App (wird in neuem Tab geöffnet) |
10.2 Eigene Sub-Navigation
Die LiftAir-Seiten verwenden eine eigene Navigation anstelle der Hauptseiten-Navigation.
Statt «Bastian Herold» steht links das LiftAir-Logo mit dem Text «LiftAir», und die Links führen zu
Vario, Shop und App. Die Sub-Navigation wird in CSS über die Klassen .liftair-nav und
.liftair-nav__brand gestylt und passt sich auf mobilen Geräten als Hamburger-Menü an.
Im Dark Mode wird das LiftAir-Logo über filter: invert(1) invertiert, damit es auf dunklem
Hintergrund sichtbar bleibt.
10.3 Produktseite (Vario)
Die Produktseite unter /liftair/vario/ stellt das Variometer vor:
- Branded Title: Kombination aus LiftAir-Logo + «Vario» als Überschrift (CSS-Klasse
.branded-title) - Feature-Karten: Drei Karten (Software, Hardware, Gehäuse) im bekannten Grid-Layout
- Galerie: Nutzt den bestehenden
content-loader.jsund zeigt Bilder aus der Variometer-Galerie (content.json) - Technische Daten: Spezifikationstabelle (Sensor, Auflösung, Display, BLE, Akku, Gehäuse, Gewicht, Abmessungen)
- CTA-Button: Führt zum Shop
Hinweis: Gewicht und Abmessungen enthalten noch Platzhalter (XX g, XX × XX × XX mm).
Diese werden direkt im HTML-Code unter liftair/vario/index.html eingetragen, sobald die finalen Werte feststehen.
10.4 Shop – Bestellformular
Der Shop unter /liftair/shop/ ermöglicht die Bestellung des Variometers per E-Mail. Es gibt
kein Zahlungs-Gateway – der Kunde bestellt, erhält eine Bestätigungs-E-Mail mit
Bankdaten und überweist manuell.
Aufbau der Seite
- Links: Produktbild (automatisch aus der Variometer-Galerie geladen), Logo, Preis, Versandhinweis, Mengenauswahl
- Rechts: Bestellformular mit Name, E-Mail, Adresse, Land (DE/AT/CH), optionaler Nachricht
- Datenschutz-Checkbox: Muss akzeptiert werden (verlinkt auf
/datenschutz/) - Honeypot-Feld: Unsichtbares Feld gegen Spam-Bots
Bestellablauf
Platzhalter-Modus
Der Shop unterstützt einen Platzhalter-Modus: Solange in js/shop.js die Variable
UNIT_PRICE = null gesetzt ist, zeigt die Seite «Preis auf Anfrage» statt eines Betrags. Die Bestätigungs-E-Mail
zeigt dann ebenfalls «Preis auf Anfrage». Sobald der Preis feststeht, muss nur UNIT_PRICE auf den
Betrag geändert werden (z.B. UNIT_PRICE = 149.00).
Bankdaten: In der Bestätigungs-E-Mail stehen aktuell [PLATZHALTER] für IBAN,
BIC, Bank und Kontoinhaber. Diese müssen in api/submitOrder/index.js eingetragen werden, bevor
der Shop produktiv geht.
10.5 BLE-Konfigurations-App
Unter /liftair/app/ wird eine eigenständige Web-App ausgeliefert, mit der das Variometer
über Bluetooth Low Energy (BLE) direkt im Browser konfiguriert werden kann.
Technologie
| Komponente | Technologie |
|---|---|
| Build-Tool | Vite |
| Sprache | TypeScript (Vanilla, kein Framework) |
| BLE | Web Bluetooth API |
| Audio | Web Audio API (Ton-Simulator) |
| Firmware-Check | GitHub API (Release-Versionen) |
Funktionen
- Geräteverbindung: Verbindet sich via BLE mit dem Variometer und liest Konfigurationsparameter
- Konfiguration: Alle Parameter können direkt im Browser bearbeitet und auf das Gerät geschrieben werden
- Ton-Simulator: Visualisiert die Tonkurve auf einem Canvas und spielt die Töne per Web Audio ab
- Firmware-Update-Check: Prüft über die GitHub-API, ob eine neuere Firmware verfügbar ist
- Produkterkennung: Erkennt automatisch das Produktmodell anhand der Seriennummer-Präfixe (
PV= Base,PP= Pro) - PWA-fähig: Kann als App auf dem Smartphone installiert werden
Quellcode
Der Quellcode liegt im Ordner liftair-src/. Dieses Projekt ist separat vom Hauptprojekt –
es wird mit npm run build gebaut und die Ausgabe (Build-Artefakte) wird in den Ordner liftair/app/
kopiert. Die App wird dann zusammen mit der restlichen Webseite automatisch auf Azure veröffentlicht.
10.6 CSP-Anpassungen für LiftAir
Für die LiftAir-App wurden in der Content-Security-Policy (in staticwebapp.config.json) folgende Ergänzungen vorgenommen:
connect-src:https://api.github.comundhttps://raw.githubusercontent.com(für Firmware-Update-Checks)Permissions-Policy:bluetooth=(self)(erlaubt Web Bluetooth nur auf der eigenen Domain)
11. Sicherheit – Schutz der Webseite
Die Webseite implementiert zahlreiche Sicherheitsmassnahmen. Hier eine Übersicht in verständlicher Sprache:
11.1 HTTP-Sicherheitsheader
Diese Header werden bei jeder Antwort der Webseite mitgesendet (konfiguriert in staticwebapp.config.json):
| Header | Was er tut |
|---|---|
Content-Security-Policy | Definiert genau, von welchen Quellen Skripte, Bilder etc. geladen werden dürfen. Verhindert Code-Einschleusung (XSS). Enthält frame-ancestors 'none' als zusätzlichen Clickjacking-Schutz. Bilder und Medien sind auf bastianherold01.blob.core.windows.net beschränkt (kein Wildcard). |
X-Frame-Options: DENY | Verhindert, dass die Webseite in einem Iframe auf einer anderen Seite eingebettet wird (Clickjacking-Schutz). |
X-Content-Type-Options: nosniff | Browser darf den Dateityp nicht «erraten» – muss den angegebenen Typ verwenden. |
Strict-Transport-Security | Erzwingt HTTPS für 1 Jahr – kein unverschlüsselter Zugriff möglich. |
Referrer-Policy | Beschränkt, welche Informationen beim Klicken auf externe Links weitergegeben werden. |
Permissions-Policy | Sperrt Kamera, Mikrofon und Standort komplett – die Webseite braucht das nicht. |
11.2 Eingabevalidierung
- Kontaktformular: Honeypot-Feld, E-Mail-Format-Prüfung, Längenbegrenzungen (Name/E-Mail: 200 Zeichen, Nachricht: 10'000 Zeichen), Header-Injection-Schutz
- LiftAir Shop: Honeypot-Feld, Front Door ID-Prüfung, E-Mail-Format, Feldlängen max. 200 Zeichen, Nachricht max. 5'000 Zeichen, Menge 1–10, Preis-Validierung (NaN/Infinity-Schutz), HTML-Escaping aller Eingaben in E-Mails
- CMS: Erlaubte Sektionen (Whitelist), Titel max. 200 Zeichen, Text max. 5'000 Zeichen, Galerie max. 10 Einträge, Galerie-URLs nur relative Pfade oder
https://(XSS-Schutz), max. 512 KB Gesamtgrösse (Content-Length Pre-Check + JSON-Validierung) - Bild-Upload: MIME-Type-Whitelist, Magic-Byte-Validierung, 10 MB Grössenlimit, UUID-Dateinamen
- Pfad-Schutz: Dateinamen werden URL-dekodiert (
decodeURIComponent) und anschliessend auf/,\,..und Null-Bytes geprüft – verhindert URL-kodierte Path-Traversal-Angriffe (%2f,%2e%2e) - Fehler-Härtung: API-Fehlermeldungen geben keine internen Details an den Client weiter – nur generische, benutzerfreundliche Texte
11.3 XSS-Schutz (Cross-Site Scripting)
Da Benutzer Markdown-Text eingeben können (im CMS), muss sichergestellt werden, dass kein böswilliger HTML/JavaScript-Code eingeschleust wird. Deshalb durchlaufen alle Texte zwei Schritte:
marked.parse()wandelt Markdown in HTML umDOMPurify.sanitize()entfernt alles Gefährliche – nur sichere Tags sind erlaubt:<strong>,<em>,<a>,<br>,<p>,<ul>,<ol>,<li>
11.4 URL-Validierung
Galerie-Bilder werden nur geladen, wenn die URL bestimmte Muster erfüllt:
Relative Pfade (/bilder/...) oder HTTPS-URLs von bastianherold01.blob.core.windows.net
und bastianherold.ch. Alle anderen URLs werden ignoriert.
Serverseitig validiert die saveContent-API zusätzlich, dass Gallery-URLs nur relative Pfade (/...)
oder https://-URLs sind – javascript:- und data:-URIs werden abgelehnt.
11.5 SRI (Subresource Integrity)
Alle externen CDN-Scripts (marked.js, DOMPurify, Chart.js) werden mit
SHA-384 Integrity-Hashes geladen. Das bedeutet: Falls der CDN-Server (jsdelivr.net)
kompromittiert wird und manipierten Code ausliefert, blockiert der Browser das Script automatisch,
weil der Hash nicht mehr übereinstimmt.
11.6 Authentifizierungs-Token-Prüfung
Die API prüft nicht nur, ob ein Benutzer eingeloggt ist, sondern auch, ob der Login über Azure AD (Entra ID)
erfolgte (identityProvider === 'aad'). Das verhindert Manipulation des Auth-Headers.
12. SEO & Performance
12.1 Suchmaschinenoptimierung (SEO)
- Canonical URL:
<link rel="canonical">auf der Hauptseite vorgibt, dasshttps://bastianherold.ch/die «offizielle» URL ist - robots.txt: Erlaubt Suchmaschinen das Crawlen öffentlicher Seiten, blockiert
/admin/,/members/und/.auth/, verweist auf die Sitemap - sitemap.xml: Listet alle öffentlichen Seiten: Startseite, LiftAir (Landingpage, Vario, Shop), Dokumentation und Datenschutz – mit Prioritäten und Aktualisierungshäufigkeit
- Open Graph: Meta-Tags für Social Media Vorschauen (Facebook, LinkedIn etc.)
- Twitter Card: Grosses Vorschaubild für Twitter/X
- Schema.org: Strukturierte Daten im JSON-LD-Format (Person, Name, Beruf, Ort)
- noindex/nofollow: Alle nicht-öffentlichen Seiten (Admin, Members, Datenschutz, Docs) sind für Suchmaschinen gesperrt
- Sprache:
<html lang="de">
12.2 Performance-Optimierungen
- Font Preloading: Die Schriftart wird vorab geladen, damit sie sofort verfügbar ist
- font-display: swap: Text wird sofort mit Systemschrift angezeigt, dann getauscht wenn Inter geladen ist
- Variable Font: Eine einzige Datei (
Inter-Variable.woff2) statt mehrerer Dateien für verschiedene Schriftstärken - Lazy Loading: Galerie-Bilder werden erst geladen, wenn sie sichtbar werden
- Deferred Scripts: Alle JavaScript-Dateien verwenden
defer– sie blockieren nicht das Rendern der Seite - Kein Framework: ~1'900 Zeilen Vanilla JS statt eines 100+ KB Frameworks
- Cache: Öffentliche Inhalte werden 5 Minuten gecacht, Members-Bilder 1 Stunde
- PWA-Manifest: Die Webseite kann als App auf dem Handy installiert werden
13. Umgebungsvariablen – Konfiguration
Umgebungsvariablen sind Einstellungen, die nicht im Code stehen, sondern in der Azure-Konfiguration. Das ist wichtig, weil Passwörter und Zugangsdaten nie im Code erscheinen sollten.
13.1 Wo findest du die Umgebungsvariablen?
- Gehe zu portal.azure.com
- Öffne die Static Web App Ressource (bastianherold.ch)
- Klicke im linken Menü auf «Konfiguration» (oder «Configuration»)
- Dort siehst du alle Einstellungen unter «Anwendungseinstellungen»
13.2 Liste aller Umgebungsvariablen
Authentifizierung
| Variable | Zweck |
|---|---|
AZURE_CLIENT_ID | ID der Azure AD App-Registrierung (für Login) |
AZURE_CLIENT_SECRET | Geheimschlüssel der App-Registrierung |
ALLOWED_USERS | Kommagetrennte E-Mail-Adressen der berechtigten Members |
ALLOWED_ADMINS | Kommagetrennte E-Mail-Adressen der Admins |
Blob Storage
| Variable | Zweck |
|---|---|
BLOB_CONNECTION_STRING | Verbindungsschlüssel zum Azure Blob Storage |
BLOB_ACCOUNT_NAME | Name des Storage-Kontos (für Bild-URLs) |
E-Mail (Azure Communication Services)
| Variable | Zweck |
|---|---|
ACS_CONNECTION_STRING | Verbindungsschlüssel für den E-Mail-Dienst |
SENDER_EMAIL | Absenderadresse für Kontaktformular-E-Mails |
CONTACT_EMAIL | Empfängeradresse (wohin die Kontaktanfragen gehen) |
Hinweis: Die E-Mail-Funktion akzeptiert auch alternative Variablennamen
(COMMUNICATION_SERVICES_CONNECTION_STRING, EMAIL_CONNECTION_STRING etc.),
um flexibel zu sein. Die oben genannten sind die primären.
LiftAir Shop
| Variable | Zweck |
|---|---|
ORDER_EMAIL | Empfängeradresse für Shop-Bestellungen (separater Posteingang). Kein Fallback – wenn nicht gesetzt, schlägt die Bestellung fehl. |
Analytics (Application Insights)
| Variable | Zweck |
|---|---|
APPINSIGHTS_APP_ID | ID der Application Insights Ressource |
ANALYTICS_TENANT_ID | Azure AD Mandanten-ID (für Token-Abfrage) |
ANALYTICS_CLIENT_ID | Service Principal Client-ID (für Analytics-API) |
ANALYTICS_CLIENT_SECRET | Service Principal Secret (für Analytics-API) |
Schutz
| Variable | Zweck |
|---|---|
FRONTDOOR_ID | Azure Front Door ID – die sendEmail-API prüft diesen Header, um sicherzustellen, dass Anfragen über Front Door kommen |
OWNER_EMAIL | E-Mail-Adresse des Besitzers – nur dieser Benutzer sieht den «Logs löschen»-Button im Admin-Dashboard und kann Logs über clearLogs bereinigen. Fallback: freshmurphy@outlook.com |
13.3 Häufige Aufgaben
Einen neuen Member hinzufügen
- Gehe zu Azure Portal → Static Web App → Konfiguration
- Suche die Variable
ALLOWED_USERS - Füge die E-Mail-Adresse am Ende kommagetrennt hinzu, z.B.:
bestehende@mail.ch,neue.person@mail.ch - Klicke auf Speichern
Einen Member entfernen
- Gehe zu Azure Portal → Static Web App → Konfiguration
- Suche die Variable
ALLOWED_USERS - Lösche die E-Mail-Adresse aus der Liste (Komma beachten!)
- Klicke auf Speichern
Die Empfängeradresse des Kontaktformulars ändern
- Ändere die Variable
CONTACT_EMAILauf die neue Adresse
14. Wartung & Weiterentwicklung
14.1 Wie Code-Änderungen live gehen
Die Webseite wird über GitHub verwaltet. Der Ablauf:
- Du öffnest das Projekt in VS Code
- Du machst Änderungen am Code
- Du committest die Änderungen und pushst sie nach GitHub
- GitHub Actions (ein automatischer Prozess) baut die Webseite neu und veröffentlicht sie auf Azure
- Nach wenigen Minuten ist die Änderung live
Tipp: Inhalts-Änderungen (Texte, Bilder) benötigen keinen Code-Push – sie werden direkt
über das CMS unter /admin/content/ gemacht und sind sofort live.
14.2 Was du ohne Code-Kenntnisse ändern kannst
| Aufgabe | Wie |
|---|---|
| Texte und Bilder ändern | CMS unter /admin/content/ |
| Besucher-Statistiken anschauen | Analytics unter /admin/analytics/ |
| Login-Aktivitäten prüfen | Admin Dashboard unter /admin/ |
| Members hinzufügen/entfernen | Azure Portal → Konfiguration → ALLOWED_USERS |
| Admins hinzufügen/entfernen | Azure Portal → Konfiguration → ALLOWED_ADMINS |
| Kontakt-E-Mail-Empfänger ändern | Azure Portal → Konfiguration → CONTACT_EMAIL |
| Shop-Bestell-Empfänger ändern | Azure Portal → Konfiguration → ORDER_EMAIL |
14.3 Was Code-Änderungen erfordert
| Aufgabe | Betroffene Dateien |
|---|---|
| Layout/Design ändern | css/styles.css |
| Neue Sektion hinzufügen | index.html, js/content-loader.js, js/content-admin.js, api/saveContent/ |
| Navigation ändern | Alle HTML-Dateien (Header ist in jeder Datei) |
| Neuen API-Endpunkt erstellen | Neuen Ordner in api/ mit function.json + index.js |
| Sicherheitsheader anpassen | staticwebapp.config.json |
| Routen/Zugriff anpassen | staticwebapp.config.json |
| LiftAir-Preis festlegen | js/shop.js (UNIT_PRICE) |
| LiftAir-Bankdaten eintragen | api/submitOrder/index.js (Platzhalter ersetzen) |
| LiftAir-Spezifikationen ergänzen | liftair/vario/index.html (Gewicht, Abmessungen) |
| Datenschutzerklärung aktualisieren | datenschutz/index.html |
14.4 Tipps für die Weiterentwicklung
Vibe Coding mit Claude
Diese Webseite wurde durch «Vibe Coding» mit Claude Opus 4.6 erstellt – einer Methode, bei der du in natürlicher Sprache beschreibst, was du möchtest, und die KI den Code schreibt. Das kannst du auch für Weiterentwicklungen nutzen:
- Öffne das Projekt in VS Code mit GitHub Copilot (Claude-Modell)
- Beschreibe die gewünschte Änderung in natürlicher Sprache
- Claude kennt die gesamte Codebasis und kann gezielte Änderungen vornehmen
- Teste die Änderung lokal und pushe sie dann nach GitHub
Best Practices
- Kleine Schritte: Lieber viele kleine Änderungen als eine riesige. So kannst du Fehler leichter finden.
- Testen: Nach jeder Änderung die Webseite auf verschiedenen Geräten/Browsern prüfen.
- Git-Commits: Jede Änderung mit einer verständlichen Nachricht committen, z.B. «Neue Sektion Wetter hinzugefügt».
- Backup: GitHub ist dein Backup für den Code. Im Blob Storage ist Versionierung aktiviert – überschriebene Dateien (z.B.
content.json) können bis zu 30 Tage wiederhergestellt werden. Gelöschte Dateien sind 7 Tage per Soft Delete abrufbar (siehe Abschnitt 2.3). - Secrets: Passwörter und Schlüssel gehören NIE in den Code – immer als Umgebungsvariable in Azure.
14.5 Bekannte Limitierungen
- Keine Volltextsuche: Es gibt keine Suchfunktion auf der Webseite.
- Begrenzte Versionierung der Inhalte: Blob Versioning bewahrt ältere Versionen für 30 Tage auf. Danach werden sie automatisch gelöscht. Für ältere Stände gibt es das Änderungsprotokoll im CMS, aber keine vollständige Wiederherstellung.
- Einzelne JSON-Datei: Alle Inhalte in einer Datei – bei sehr vielen Inhalten könnte das irgendwann unhandlich werden (aktuell kein Problem).
- Kein Multi-User-CMS: Wenn zwei Admins gleichzeitig bearbeiten, gewinnt der letzte Speichervorgang.
- 10 MB Bild-Limit: Grössere Dateien müssen vorher komprimiert/verkleinert werden.
14.6 Nützliche Links
- Azure Portal – Zugang zu allen Azure-Ressourcen
- GitHub – Code-Repository & Deployment
- Azure Static Web Apps Doku – Offizielle Microsoft-Dokumentation
- Azure Functions Doku – Offizielle Doku für die API-Funktionen
- marked.js – Markdown-Parser Dokumentation
- Chart.js – Diagramm-Bibliothek Dokumentation
Version: 1.2 · Erstellt: 2. März 2026 · Aktualisiert: 10. März 2026 · Methode: Vibe Coding mit Claude Opus 4.6
Technische Verantwortung: Markus Simon (nomis.ch) · Inhaltliche Verantwortung: Bastian Herold