Files
bryan.hoffmann 3202f26b76
Build und Push Docker Image / build-and-push (push) Successful in 9s
Update Dockerfile settings
2026-08-21 13:21:37 +02:00

13 KiB
Raw Permalink Blame History

WDM Performance Produktionserfassung für ~40 Maschinen

Flask-Backend mit serverseitigem Rendering (Jinja2-Templates, normale HTML-Formulare, Post-Redirect-Get) kein Client-JavaScript, genau wie beim Essensportal-Projekt. Postgres-Datenbank.

Start

Es gibt bewusst keine .env-Datei alle Einstellungen (DB-Zugangsdaten, Port, SECRET_KEY, MASCHINEN_PASSWORT) stehen direkt in docker-compose.yml. Vor dem ersten Start dort alle BITTE-AENDERN-...-Platzhalter durch echte Werte ersetzen (Datenbank-Passwort bei ens-db und ens-app muss identisch sein), dann:

docker compose up -d --build

Die Anwendung ist danach unter http://<server>:8081/ erreichbar (Port direkt im ports:-Abschnitt von docker-compose.yml änderbar).

Wichtig zur DB-Initialisierung: build/db/init.sql (= dein hochgeladenes Schema, ergänzt um ein paar Beispiel-Abweichungsgründe) wird nur beim allerersten Start ausgeführt, wenn das Datenvolume noch leer ist. Hast du bereits eine bestehende Postgres-Datenbank mit Daten, dann:

  • entweder das Datenverzeichnis vor dem ersten Start leer lassen und init.sql anpassen/ersetzen, oder
  • den ens-db-Service in docker-compose.yml entfernen und stattdessen über DB_HOST / DB_PORT / DB_NAME / DB_USER / DB_PASSWORD bei ens-app auf deine bestehende Datenbank zeigen.

Achtung, bitte prüfen: ens-db bindet aktuell als Volume das Verzeichnis /var/lib/docker/volumes/postgresql_ens_data ein. Laut eurer eigenen System-Doku ist genau das der Datenbankpfad des bestehenden Budibase- Systems. Läuft Budibase parallel mit seiner eigenen Postgres-Instanz auf demselben Host, würden zwei unabhängige Postgres-Prozesse gleichzeitig auf dasselbe Datenverzeichnis schreiben das kann das Datenverzeichnis beschädigen. Bitte kurz gegenprüfen, ob das so beabsichtigt ist (z.B. weil Budibase dort inzwischen abgelöst ist) oder ob hier versehentlich der falsche Pfad übernommen wurde; im Zweifel einen eigenen, neuen Pfad/Volume für ens-db verwenden.

Bauen und Verteilen als Container

Bei jedem Push nach main bzw. auf einen v*-Tag baut die Gitea-Actions- Pipeline (.gitea/workflows/build.yml) automatisch ein Image und pusht es in die Gitea-Registry (getaggt als :latest und mit dem Commit-Hash).

Für einen anderen Server, der nur das fertige Image ziehen und selbst nichts bauen soll, gibt es docker-compose.deploy.yml: sie referenziert das Image direkt aus der Registry (image: git.thiede-brauer.de/bryan.hoffmann/wdm:latest, kein build:). Auf diesem Server reicht allein diese eine Datei der Rest des Repos wird dort nicht benötigt:

docker login git.thiede-brauer.de -u <benutzername>   # falls die Registry nicht öffentlich lesbar ist
docker compose -f docker-compose.deploy.yml up -d

Update auf eine neue Version:

docker compose -f docker-compose.deploy.yml pull
docker compose -f docker-compose.deploy.yml up -d

Auch hier gilt: keine .env, alle Werte direkt in docker-compose.deploy.yml eintragen (dieselben Werte wie beim Bauen, insbesondere das DB-Passwort).

Aufruf der einzelnen Seiten

  • Maschine N (140): http://<server>:8081/m<N> z.B. http://<server>:8081/m1
  • Produktionsleiter: http://<server>:8081/start

Alte/lange URL-Form: http://<server>:8081/m<N>_m_view_main (z.B. .../m1_m_view_main) funktioniert unverändert weiter und zeigt auf dieselbe Seite. Sie bleibt bewusst zusätzlich zur kurzen Form erhalten, damit bereits auf Tablets als Kiosk-Startseite/Lesezeichen hinterlegte URLs nicht angepasst werden müssen. Für neu eingerichtete Geräte reicht die kurze Form /m<N>.

Hinweis zum ursprünglich gewünschten #/...-Format: Ein Hash-Fragment (#/...) wird nie an den Server geschickt und funktioniert nur mit Client-JavaScript, das per Definition entfällt (kein JS). Deshalb sind die Seiten stattdessen unter echten Server-Pfaden mit denselben Bezeichnern erreichbar (ohne #).

Die Maschinennummer <N> entspricht der id in der Tabelle Maschinen. Für Tablets an den Maschinen einfach jeweils die passende URL als Startseite/Kiosk-Link einrichten.

Reverse Proxy: Falls du die App hinter einem Unterpfad (z.B. /app/wdm-performance/) betreiben willst, muss der Proxy diesen Präfix vor Weiterleitung an den Container entfernen (strip prefix), da das Backend mit wurzelrelativen Pfaden arbeitet. Beispiel nginx:

location /app/wdm-performance/ {
    proxy_pass http://wdm-app:5000/;
}

Funktionsumfang

Maschinenseite (/m<N>, alte Form /m<N>_m_view_main weiterhin gültig)

  • Überschrift = Maschinenname aus der DB
  • Kein aktiver Auftrag → Start-Button → Auftragsauswahl (offene/pausierte Aufträge dieser Maschine, sortiert nach Priorität/sid) → Auswahl markiert Auftrag als aktiv, schreibt start-Eintrag in MDE
  • Aktiver Auftrag → Start-Button ausgeblendet, stattdessen:
    • Prod. erfassen: Menge (numerisch, ≥0, ≥ bisherige Menge, ≤110% Soll)
      • Pflicht-Dropdown Abweichungsgrund, Server validiert verbindlich vor dem Schreiben in MDE
    • Auftrag pausieren: gleiche Eingabemaske, setzt Auftrag auf status=pause, aktiv=false
    • Auftrag beenden: bei Menge < Soll erscheint eine große Bestätigungsabfrage; nach Bestätigung wird die Sollmenge als produziert in MDE (status=beendet) vermerkt, Auftrag auf abgeschlossen / aktiv=false gesetzt

Produktionsleiter (#/start)

  • Auftrag anlegen: Maschine (Dropdown), Auftragsnummer, optional Artikel, Produktionsmenge, Stück/Stunde sowie optional eine Priorität (Position in der Warteschlange dieser Maschine, 1 = als nächstes dran) alles serverseitig validiert. Wird eine Priorität angegeben, wird der Auftrag an genau dieser Position eingereiht und alle bestehenden offenen/pausierten Aufträge dieser Maschine rücken automatisch nach (dichte Nummerierung 1..N, kein bloßes Setzen eines rohen Zahlenwerts). Ohne Angabe wird der Auftrag ans Ende der Warteschlange gehängt.
  • Aufträge importieren (/start_import): Aufträge aus einer Excel- (.xlsx) oder CSV-Datei per Drag & Drop oder Dateiauswahl bulk-anlegen, siehe eigener Abschnitt unten
  • Auftragsübersicht: alle Aufträge (inkl. Artikel, Maschinenname), standardmäßig nur offen/pausiert (umschaltbar auf "Alle"), optional nach Maschine gefiltert, löschbar und (mit Einschränkungen) bearbeitbar:
    • Status und Priorität sind hier nicht editierbar (nur informativ angezeigt) Status ändert sich ausschließlich über die eigentlichen Maschinen-Aktionen (Start/Pause/Beenden), Priorität ausschließlich über die Drag & Drop-Seite "Auftragspriorität".
    • Aktive Aufträge werden angezeigt, können aber nicht bearbeitet werden ein Bearbeitungsversuch (auch ein direkter POST) wird mit einer Fehlermeldung abgelehnt, solange der Auftrag gerade an der Maschine läuft.
    • Editierbar bleiben Maschine (inkl. automatischem Umhängen der kompletten MDE-Buchungshistorie auf die neue Maschine), Auftragsnummer, Artikel, Produktionsmenge und Stück/Stunde.
    • Mengen-Schutz: Wurde an einem Auftrag bereits eine Menge gemeldet (MDE-Historie vorhanden), darf die Produktionsmenge nicht mehr so weit reduziert werden, dass die bereits gemeldete Menge außerhalb der 110%-Toleranz läge (dieselbe Toleranzregel, die auch an der Maschine beim Erfassen gilt) die App zeigt dabei den konkret noch zulässigen Mindestwert an.
  • Buchungskorrektur: direkter Eingriff in die MDE-Tabelle (editieren/ löschen), optional nach Maschine gefiltert
  • Auftragspriorität: Reihenfolge der offenen/pausierten Aufträge je Maschine per Drag & Drop ändern (Auftrag anfassen und an neue Position ziehen) wird automatisch gespeichert. Funktioniert am PC/Laptop mit Maus; auf reinen Touch-Geräten ohne Maus ggf. eingeschränkt (Hinweis dazu steht auf der Seite selbst).
  • Zu Maschine springen: Direktlinks zu jeder Maschinenseite (zeigt an, ob die Maschine gerade läuft)
  • Maschinenverwaltung (/start_maschinen_verwaltung, passwortgeschützt): Maschinen anlegen, umbenennen/Standort ändern, löschen (nur möglich wenn keine Aufträge mehr an der Maschine hängen). Passwort über die Umgebungsvariable MASCHINEN_PASSWORT in docker-compose.yml konfigurierbar (unbedingt vom Platzhalter auf einen echten Wert ändern!). Für die Login-Session wird zusätzlich ein SECRET_KEY benötigt (ebenfalls dort gesetzt).

Aufträge importieren (/start_import)

Excel- (.xlsx) oder CSV-Datei mit (mindestens) diesen Spalten hochladen (Reihenfolge egal, Erkennung anhand von Stichworten in der Kopfzeile). Die Datei kann per Drag & Drop auf das gestrichelte Feld gezogen oder klassisch über die Dateiauswahl ausgewählt werden. Bei CSV werden Zeichenkodierung (UTF-8/BOM, sonst cp1252), Trennzeichen (Komma, Semikolon wie bei deutschem Excel-Export üblich oder Tab) sowie deutsches/internationales Zahlformat (1.234,56 oder 1234.56) automatisch erkannt.

Spalte Verwendung
Maschine muss exakt (Groß-/Kleinschreibung egal) einer bestehenden Maschine entsprechen, sonst wird die Zeile verworfen
man. Prio wird direkt als Priorität (sid) übernommen (das Feld erlaubt auch Kommazahlen, z.B. 15.1)
FA.-Nr. → Auftragsnummer. Existiert diese FA.-Nr. bereits irgendwo im System, wird die Zeile unangetastet übersprungen
Rest-Menge zusammen mit Ist-Menge → Produktionsmenge (Soll) = Rest + Ist
Ist-Menge wird als bereits produzierte Menge übernommen (Start-Buchung in MDE), damit Fortschritt und Mengen-Validierung an der Maschine korrekt weiterlaufen
Artikel wird 1:1 übernommen und angezeigt
Soll-pro-Stunde → Stück/Stunde (Soll)
Bezeichnung, Auftrag, Lieferdatum, Bemerkung werden ignoriert

Ist die Rest-Menge einer Zeile 0, wird der Auftrag direkt mit Status "Abgeschlossen" angelegt (inkl. passender Buchung in der MDE-Historie). Nach dem Import zeigt die Seite eine Liste, was importiert, was wegen unbekannter Maschine verworfen und was wegen bereits vorhandener FA.-Nr. übersprungen wurde.

Artikel

Neues, optionales Textfeld je Auftrag (Spalte artikel). Kann beim manuellen Anlegen eingetragen werden oder kommt aus dem Import; wird auf der Maschinenseite, in der Auftragsauswahl, der Auftragsübersicht, der Prioritätsliste und (sofern gerade ein Auftrag aktiv ist) auch in der Maschinenübersicht ("Zu Maschine springen") mit angezeigt.

Logo / Icon

build/static/logo.svg wird als Favicon und über dem Seitentitel angezeigt. Die Darstellung ist proportional (feste Höhe 130px, Breite skaliert automatisch im echten Seitenverhältnis mit) funktioniert also auch mit einem breiten, nicht-quadratischen Logo. Einfach durch eine eigene Datei mit demselben Namen (logo.svg) ersetzen, um das Firmenlogo einzubinden kein Code muss dafür angepasst werden.

JavaScript-Einsatz (Ausnahmen vom "kein Client-JS"-Grundsatz)

Zwei bewusste, punktuelle Ausnahmen von der sonst reinen Server-Side- Rendering-Philosophie:

  • Die Filter-Dropdowns (Maschine/Status) in Auftragsübersicht, Buchungs- korrektur und Auftragspriorität lösen die Filterung direkt beim Auswählen aus (onchange-Submit) reiner Komfort, der "Filtern"-Button bleibt als Fallback sichtbar und funktioniert auch ohne JavaScript.
  • Die Drag-&-Drop-Sortierung auf der Auftragspriorität-Seite benötigt zwingend JavaScript (ohne JS erscheint dort ein Hinweis, dass Sortieren nicht möglich ist alle anderen Seiten funktionieren weiterhin ohne JS).

Stabilität im Dauerbetrieb

  • Backend läuft unter gunicorn (mehrere Worker/Threads statt Flask-Devserver)
  • Datenbank-Connection-Pool mit automatischem Retry beim Start (verträgt verzögerten DB-Start ohne Absturz)
  • Docker-Healthchecks für App und DB, restart: unless-stopped
  • Reines Server-Side-Rendering ohne Build-Step und ohne Client-JavaScript jede Aktion ist ein klassischer Formular-Submit (Post-Redirect-Get), das ist auf Tablets mit älteren/eingeschränkten Browsern besonders robust

Struktur

wdm/
├── Dockerfile              Build-Anweisungen (Kontext = Projektwurzel)
├── docker-compose.yml          lokal bauen/testen (build: .)
├── docker-compose.deploy.yml   nur Image ziehen (für andere Server)
├── .gitea/workflows/build.yml  CI: baut Image und pusht in die Gitea-Registry
├── README.md
└── build/
    ├── app.py               Flask-Anwendung (alle Routen, Server-Side-Rendering)
    ├── db.py                 DB-Pool mit Retry-Logik
    ├── requirements.txt
    ├── db/init.sql             Schema (dein Dump + Beispiel-Abweichungsgründe)
    ├── templates/               Jinja2-HTML-Templates (kein JavaScript)
    └── static/
        └── style.css             Großflächiges Touch-Design

Wichtig: docker compose muss aus dem Projektwurzel-Verzeichnis heraus aufgerufen werden (dort, wo docker-compose.yml liegt), nicht aus build/.