Fidelis HuberandClaude Sonnet 5 a910d1e399
Build and Deploy / version (push) Successful in 2s
Build and Deploy / build (push) Successful in 11s
Build and Deploy / deploy (push) Successful in 2s
Replace horizontal-scroll table with a stacked card layout on mobile
Materialliste war auf schmalen Screens nur per Seitwärts-Scrollen lesbar.
Ab 680px Breite wird jede Zeile jetzt als Karte dargestellt (CSS Grid mit
benannten Areas, Bezeichnung prominent oben, Menge/Einheit/Art.-Nr./Notiz
darunter beschriftet, Position + Löschen-Button in der Kopfzeile der Karte).
Die Grid-Areas ordnen die Felder unabhängig von der DOM-Reihenfolge um, die
Desktop-Tabelle bleibt unverändert in der ursprünglichen Spaltenreihenfolge.

Temporärer test-build-only.yml Workflow zur Verifikation ohne Registry-Push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 19:38:07 +02:00
2026-08-21 17:24:14 +00:00

BergVOLT Materialschein

Materialschein & Kabelrechner für BergVOLT Elektrotechnik. React + Vite + TypeScript (PWA) im Client, Express + TypeScript + Prisma im Server, Postgres als Datenbank. Anmeldung über Microsoft Entra ID (gleiche App-Registrierung wie das Zeiterfassungs-Tool).

Lokale Entwicklung

# 1. Postgres lokal starten
docker compose -f docker-compose.dev.yml up -d

# 2. Server
cd server
npm install
echo 'DATABASE_URL="postgresql://materialschein:dev@localhost:5433/materialschein"' > .env
echo 'MSAL_CLIENT_ID="b1cd974c-77ce-4d93-b6e8-00e41a31adc8"' >> .env
echo 'MSAL_TENANT_ID="d9adeaaf-853b-4c4e-a822-8f1bedbb84f6"' >> .env
echo 'PORT=8787' >> .env
npx prisma migrate deploy
npm run dev        # läuft auf :8787

# 3. Client (neues Terminal)
cd client
npm install
npm run dev         # läuft auf :5173, proxied /api auf :8787

Architektur

  • client/ React 18 + Vite + TypeScript, vite-plugin-pwa für Manifest/Service Worker. MSAL.js (@azure/msal-browser + @azure/msal-react) für den Microsoft-Login.
  • server/ Express + TypeScript, validiert das MSAL-ID-Token serverseitig (JWKS von login.microsoftonline.com) und liefert zusätzlich die gebaute Client-App aus (public/ = client/dist, SPA-Fallback für React-Router).
  • server/prisma/schema.prisma zwei Tabellen: projekte (Baustellen) und material (Positionen je Projekt, per projektId verknüpft).
  • Ein Docker-Image (Dockerfile, Multi-Stage) enthält Client-Build + Server, Port 80.

Deploy (git.prox.ws → Portainer)

Folgt deploy-standard 1:1 gitops/stack.yml und .gitea/workflows/build.yml sind bereits fertig im Repo. Zum Scharfschalten:

1. Azure AD zweite Redirect-URI eintragen

Bestehende App-Registrierung "BergVolt Zeiterfassung" im Azure Portal öffnen → Authentifizierung → bei "Single-Page-Anwendung" ergänzen: https://materialschein.bergvolt.at (oder die tatsächlich vorgesehene Domain/Pfad). Redirect-URI in client/src/lib/authConfig.ts steht auf window.location.origin d.h. die exakte Domain, unter der die App später erreichbar ist, muss hier eingetragen werden.

2. Gitea Repo-Secrets setzen

Repo → Settings → Actions → Secrets:

Secret Wert
GITEAUSER Gitea-Username des Package-Namespace-Owners (siehe deploy-standard: Package-Push geht nur als Namespace-Owner)
GITEAPASSWORD PAT dieses Users, Scope nur write:package (Settings → Applications → Generate New Token)
PORTAINER_WEBHOOK_URL kommt aus Schritt 4

Repo → Settings → Actions: Default-Permission auf Read and Write, damit der Commit-back (gitops/stack.yml auf :<version> pinnen) funktioniert.

3. Erster Push

git push origin main

build wird grün, deploy schlägt fehl (Webhook-Secret fehlt noch) erwartet.

4. Portainer Stack anlegen

Auf https://10.1.10.111:9443 (Zugang laut deploy-standard bei "dominic"):

  • Stacks → Add stack → Repository
    • Repository URL: https://git.prox.ws/BERGVOLT/Materialschein
    • Reference: refs/heads/main
    • Compose path: gitops/stack.yml
    • Authentication: portainer-deploy + dessen PAT (Org-Repos sind über das deploy-read-Team bereits abgedeckt, nichts zusätzlich einzutragen)
    • Environment-Variablen setzen:
      • POSTGRES_PASSWORD = ein zufälliges, sicheres Passwort
      • JWT_SECRET = ein zufälliger, langer String (signiert die lokalen Login-Tokens niemals ins Repo, nur hier als Stack-Env). Beispiel zum Generieren: openssl rand -base64 48
      • SEED_ADMIN_USERNAME / SEED_ADMIN_PASSWORD = legt beim ersten Start automatisch ein lokales Fallback-Konto an (siehe "Lokales Konto" unten)
    • Webhook aktivieren, ForceUpdate an, ForcePullImage bleibt aus
  • Die angezeigte Webhook-URL (.../api/stacks/webhooks/<uuid>) als Gitea-Secret PORTAINER_WEBHOOK_URL eintragen (Schritt 2).
  • nginx-proxy-manager: Proxy Host auf Container materialschein, Port 80.

5. Verifizieren

  • Webhook von Hand feuern (curl -sk -X POST <url>) → 204, Container laufen (db + web).
  • Push oder „Run workflow" auslösen → beide Jobs grün, gitops/stack.yml zeigt danach image: …:1.0.<N> (kein :latest mehr), Commit deploy: v1.0.<N> [skip ci] im Log.
  • Cleanup-Rule für den Package-Owner einmalig setzen (Org/User → Settings → Packages → Cleanup Rules): keep most recent 10 + keep matching ^latest$.

Lokales Konto (Fallback ohne Microsoft-Login)

Auf dem Login-Screen gibt es unter dem Microsoft-Button den Link "Alternativ mit Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack JWT_SECRET gesetzt sein (Schritt 4).

Ein erstes Konto legt der Server automatisch beim Start an, wenn SEED_ADMIN_USERNAME und SEED_ADMIN_PASSWORD als Stack-Env gesetzt sind (Upsert läuft bei jedem Neustart mit und synchronisiert das Passwort neu, solange die Variable gesetzt ist ein falsch übernommenes Passwort lässt sich also einfach per Neustart korrigieren). Das Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, nie im Git-Repo der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank.

Vorsicht bei $ im Passwort: Docker-Compose/Portainer interpretieren $ in Env-Werten als Beginn einer Variablen-Referenz ($xyz wird als ${xyz} gelesen und bei fehlender Definition oft still zu leer aufgelöst) ein Passwort mit $ kann dadurch unbemerkt anders ankommen, als eingetippt. Entweder jedes $ im Passwortwert als $$ escapen, oder einfacher ein Passwort ohne $ verwenden.

Optional danach SEED_ADMIN_PASSWORD aus dem Stack wieder entfernen (Härtung), sobald das Konto wie gewünscht funktioniert SEED_ADMIN_USERNAME kann stehen bleiben, ohne Passwort-Variable wird nichts mehr verändert. Weitere lokale Konten aktuell nur direkt in der Datenbank anlegbar (INSERT INTO users … mit einem bcrypt-Hash) ein Verwaltungs-UI dafür gibt es noch nicht.

Danach: zum Homescreen hinzufügen

Seite im Handy-Browser öffnen, anmelden, "Zum Home-Bildschirm hinzufügen" (iOS Safari) bzw. "App installieren" (Android Chrome) die App ist als PWA installierbar (vite-plugin-pwa generiert Manifest + Service Worker automatisch beim Build).

S
Description
No description provided
Readme
204 KiB
Languages
TypeScript 73.6%
CSS 24.2%
Dockerfile 1.2%
HTML 1%