3 Commits
Author SHA1 Message Date
bryan.hoffmann 455f513f6e Auftragsübersicht anpassen
Build und Push Docker Image / build-and-push (push) Successful in 11s
2026-08-27 11:52:24 +02:00
bryan.hoffmann d6c066e18f Versionsnummer
Build und Push Docker Image / build-and-push (push) Successful in 9s
2026-08-26 11:32:52 +02:00
bryan.hoffmann b54b599ba9 beta
Build und Push Docker Image / build-and-push (push) Successful in 6s
2026-08-26 08:26:30 +02:00
6 changed files with 130 additions and 37 deletions
+31 -8
View File
@@ -2,7 +2,7 @@ name: Build und Push Docker Image
on:
push:
branches: [main, Dev]
branches: [main, Dev, beta]
tags: ["v*"]
env:
@@ -26,22 +26,45 @@ jobs:
- name: Bei der Registry anmelden
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
- name: Image-Namen und Tag bestimmen
# Die Versionsnummer kommt aus der Datei VERSION im Repo-Root (z.B.
# "1.4.0") - vor einem Release dort einfach den Wert erhoehen.
#
# Tags je nach Push:
# - Branch main -> :latest und :v<VERSION>
# - Branch Dev -> :dev und :v<VERSION>-dev
# - Branch beta -> :beta und :v<VERSION>-beta
# - Git-Tag "v1.4.0" -> genau :v1.4.0 (expliziter Versions-Release)
# - immer zusaetzlich -> :<commit-sha> (exakte Nachverfolgung)
- name: Image-Namen und Tags bestimmen
id: vars
run: |
IMAGE=${{ env.REGISTRY }}/${{ github.repository }}
if [ "${{ github.ref_name }}" = "main" ]; then
TAG=latest
VERSION=$(tr -d '[:space:]' < VERSION)
if [ "${{ github.ref_type }}" = "tag" ]; then
TAGS="${{ github.ref_name }}"
elif [ "${{ github.ref_name }}" = "main" ]; then
TAGS="latest v${VERSION}"
else
TAG=$(echo "${{ github.ref_name }}" | tr '[:upper:]' '[:lower:]')
BRANCH=$(echo "${{ github.ref_name }}" | tr '[:upper:]' '[:lower:]')
TAGS="${BRANCH} v${VERSION}-${BRANCH}"
fi
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
echo "Ermittelte Tags: $TAGS"
- name: Image bauen
run: docker build -t ${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.tag }} -t ${{ steps.vars.outputs.image }}:${{ github.sha }} .
run: |
BUILD_ARGS=""
for TAG in ${{ steps.vars.outputs.tags }}; do
BUILD_ARGS="$BUILD_ARGS -t ${{ steps.vars.outputs.image }}:${TAG}"
done
docker build $BUILD_ARGS -t ${{ steps.vars.outputs.image }}:${{ github.sha }} .
- name: Image pushen
run: |
docker push ${{ steps.vars.outputs.image }}:${{ steps.vars.outputs.tag }}
for TAG in ${{ steps.vars.outputs.tags }}; do
docker push ${{ steps.vars.outputs.image }}:${TAG}
done
docker push ${{ steps.vars.outputs.image }}:${{ github.sha }}
+39 -16
View File
@@ -43,15 +43,33 @@ bestehende Postgres-Datenbank mit Daten, dann:
## 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).
Bei jedem Push auf `main`, `Dev`, `beta` oder einen `v*`-Tag baut die
Gitea-Actions-Pipeline (`.gitea/workflows/build.yml`) automatisch ein Image
und pusht es in die Gitea-Registry. Welche Tags dabei entstehen, hängt vom
Branch ab:
| Push auf … | Image-Tags |
|--------------------|--------------------------------------|
| `main` | `:latest` und `:v<VERSION>` |
| `Dev` | `:dev` und `:v<VERSION>-dev` |
| `beta` | `:beta` und `:v<VERSION>-beta` |
| Git-Tag `v1.4.0` | genau `:v1.4.0` |
Zusätzlich wird bei jedem Build immer auch `:<commit-sha>` gepusht, für die
exakte Nachverfolgung einzelner Builds.
`<VERSION>` kommt aus der Datei `VERSION` im Repo-Root (aktuell `1.0.0`).
Vor einem Release dort einfach die Zahl erhöhen und committen die Pipeline
liest den Wert automatisch bei jedem Build neu ein, es ist keine weitere
Konfiguration nötig.
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:
des Repos wird dort nicht benötigt. Statt `:latest` kann dort auch gezielt
ein Versions-Tag (z.B. `:v1.4.0`) oder `:dev`/`:beta` eingetragen werden, um
eine bestimmte Version bzw. einen bestimmten Entwicklungsstand zu deployen:
```bash
docker login git.thiede-brauer.de -u <benutzername> # falls die Registry nicht öffentlich lesbar ist
@@ -133,16 +151,20 @@ location /app/wdm-performance/ {
(.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.
standardmäßig offen/pausiert **und aktiv** (umschaltbar auf "Alle", zeigt
dann zusätzlich auch abgeschlossene Aufträge), optional nach Maschine
gefiltert, löschbar und (mit Einschränkungen) bearbeitbar:
- **Status ist hier manuell änderbar** (z.B. um einen versehentlich
abgeschlossenen Auftrag wieder auf "Offen" zu setzen) **"Aktiv" ist als
Zielwert bewusst ausgeschlossen**, das passiert ausschließlich über die
eigentlichen Maschinen-Aktionen (Start/Pause/Beenden). **Priorität bleibt
weiterhin nicht editierbar**, dafür gibt es die Drag & Drop-Seite
"Auftragspriorität".
- **Aktive Aufträge werden angezeigt, können aber weder bearbeitet noch
gelöscht werden** Bearbeiten- und Löschen-Button werden für sie in der
Liste durch einen Hinweistext ersetzt; ein direkter POST-Versuch (am UI
vorbei) wird serverseitig ebenfalls 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.
@@ -238,7 +260,8 @@ 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
├── .gitea/workflows/build.yml CI: baut Image (branch-/versionsabhängig getaggt) und pusht in die Gitea-Registry
├── VERSION aktuelle Versionsnummer (z.B. "1.0.0"), von der CI-Pipeline gelesen
├── README.md
└── build/
├── app.py Flask-Anwendung (alle Routen, Server-Side-Rendering)
@@ -251,4 +274,4 @@ wdm/
```
Wichtig: `docker compose` muss aus dem Projektwurzel-Verzeichnis heraus
aufgerufen werden (dort, wo `docker-compose.yml` liegt), nicht aus `build/`.
aufgerufen werden (dort, wo `docker-compose.yml` liegt), nicht aus `build/`.
+1
View File
@@ -0,0 +1 @@
1.0.1
+42 -11
View File
@@ -58,6 +58,11 @@ STATUS_LABEL = {
"abgeschlossen": "Abgeschlossen",
}
STATUS_OPTIONEN = [("offen", "Offen"), ("aktiv", "Aktiv"), ("pause", "Pausiert"), ("abgeschlossen", "Abgeschlossen")]
# In der Auftragsübersicht darf der Status manuell gesetzt werden (z.B. um einen
# Auftrag versehentlich abgeschlossenen wieder zu öffnen) - "aktiv" ist hier
# aber bewusst ausgeschlossen, das passiert ausschließlich über die
# Start-Aktion an der Maschine selbst.
STATUS_OPTIONEN_BEARBEITBAR = [(wert, label) for wert, label in STATUS_OPTIONEN if wert != "aktiv"]
# Anzahl Zeilen, auf die MDE-/Auftragslisten ausgelegt sind (kein hartes Limit,
# aber Referenzgröße für Layout/Performance-Überlegungen).
@@ -705,7 +710,11 @@ def leitstand_uebersicht():
bedingungen.append('maschine_id = %s')
params.append(filter_maschine_id)
if filter_status == "offen_pause":
bedingungen.append("status IN ('offen', 'pause')")
# "aktiv" wird hier bewusst mit angezeigt (nur informativ, nicht
# bearbeitbar/löschbar - siehe leitstand_uebersicht_bearbeiten
# bzw. leitstand_uebersicht_loeschen), damit laufende Aufträge in
# der Übersicht nicht verschwinden.
bedingungen.append("status IN ('offen', 'pause', 'aktiv')")
where_sql = (" WHERE " + " AND ".join(bedingungen)) if bedingungen else ""
cur.execute(
@@ -735,12 +744,14 @@ def leitstand_uebersicht():
@app.route("/start_uebersicht/<int:auftrag_id>/bearbeiten", methods=["GET", "POST"])
def leitstand_uebersicht_bearbeiten(auftrag_id):
"""Auftrag bearbeiten. Status und Priorität (sid) sind hier bewusst NICHT
editierbar - Status ändert sich nur über die eigentlichen Maschinen-
Aktionen (Start/Pause/Beenden), Priorität nur über die Drag&Drop-Seite
"Auftragspriorität". Aktive Aufträge werden nur angezeigt, eine
Bearbeitung wird komplett verweigert (Fehlermeldung), da sie gerade an
der Maschine läuft."""
"""Auftrag bearbeiten. Der Status ist hier manuell änderbar (z.B. um einen
versehentlich abgeschlossenen Auftrag wieder auf "offen" zu setzen) -
"aktiv" ist als Zielwert bewusst ausgeschlossen, das passiert
ausschließlich über die eigentlichen Maschinen-Aktionen (Start/Pause/
Beenden). Priorität (sid) ist weiterhin NICHT editierbar, dafür gibt es
die Drag&Drop-Seite "Auftragspriorität". Aktive Aufträge werden nur
angezeigt, eine Bearbeitung wird komplett verweigert (Fehlermeldung), da
sie gerade an der Maschine läuft."""
filter_maschine_id = request.args.get("maschine_id") or request.form.get("maschine_id_filter") or ""
filter_status = request.args.get("status") or request.form.get("status_filter") or "offen_pause"
with db.get_cursor() as cur:
@@ -759,6 +770,7 @@ def leitstand_uebersicht_bearbeiten(auftrag_id):
"leitstand_uebersicht_bearbeiten.html", title="Auftrag bearbeiten",
auftrag=alt, maschinen=maschinen, gesperrt=True,
status_anzeige=STATUS_LABEL.get(alt["status"], alt["status"]),
status_optionen=STATUS_OPTIONEN_BEARBEITBAR,
filter_maschine_id=filter_maschine_id, filter_status=filter_status,
error="Dieser Auftrag ist aktuell aktiv und kann deshalb nicht bearbeitet werden. "
"Bitte an der Maschine erst pausieren oder beenden.",
@@ -770,6 +782,7 @@ def leitstand_uebersicht_bearbeiten(auftrag_id):
soll_h = request.form.get("soll_h")
artikel = (request.form.get("artikel") or "").strip() or None
neue_maschine_id = request.form.get("maschine_id")
neuer_status = request.form.get("status")
error = None
einheiten_f = soll_h_f = None
@@ -782,6 +795,10 @@ def leitstand_uebersicht_bearbeiten(auftrag_id):
if not error and not auftragsnummer:
error = "Auftragsnummer darf nicht leer sein."
gueltige_status = {wert for wert, _ in STATUS_OPTIONEN_BEARBEITBAR}
if not error and neuer_status not in gueltige_status:
error = "Ungültiger Status."
if not error:
if not neue_maschine_id:
error = "Maschine muss ausgewählt werden."
@@ -806,17 +823,27 @@ def leitstand_uebersicht_bearbeiten(auftrag_id):
auftrag["sid_fmt"] = fmt_num(alt["sid"])
auftrag["artikel"] = artikel
auftrag["maschine_id"] = neue_maschine_id or alt["maschine_id"]
auftrag["status"] = neuer_status if neuer_status in gueltige_status else alt["status"]
return render_template(
"leitstand_uebersicht_bearbeiten.html", title="Auftrag bearbeiten",
auftrag=auftrag, maschinen=maschinen,
status_anzeige=STATUS_LABEL.get(alt["status"], alt["status"]),
status_optionen=STATUS_OPTIONEN_BEARBEITBAR,
filter_maschine_id=filter_maschine_id, filter_status=filter_status, error=error,
)
# Wurde der Status auf "abgeschlossen" geändert (oder war er das
# schon und bleibt es), Endzeitpunkt entsprechend setzen bzw.
# beibehalten - beim (Wieder-)Öffnen wird er zurückgesetzt.
if neuer_status == "abgeschlossen":
ende_wert = alt["ende"] if alt["status"] == "abgeschlossen" else now_ts()
else:
ende_wert = None
cur.execute(
'UPDATE "Auftraege" SET auftragsnummer = %s, einheiten = %s, soll_h = %s, '
'artikel = %s, maschine_id = %s WHERE id = %s',
(auftragsnummer, einheiten_f, soll_h_f, artikel, neue_maschine_id, auftrag_id),
'artikel = %s, maschine_id = %s, status = %s, aktiv = false, ende = %s WHERE id = %s',
(auftragsnummer, einheiten_f, soll_h_f, artikel, neue_maschine_id, neuer_status, ende_wert, auftrag_id),
)
# Maschine und/oder Auftragsnummer geändert? Dann muss die MDE-Historie
# (dort per maschine_id + Auftragsnummer verknüpft) mitgezogen werden.
@@ -835,6 +862,7 @@ def leitstand_uebersicht_bearbeiten(auftrag_id):
"leitstand_uebersicht_bearbeiten.html", title="Auftrag bearbeiten",
auftrag=alt, maschinen=maschinen,
status_anzeige=STATUS_LABEL.get(alt["status"], alt["status"]),
status_optionen=STATUS_OPTIONEN_BEARBEITBAR,
filter_maschine_id=filter_maschine_id, filter_status=filter_status,
)
@@ -844,7 +872,10 @@ def leitstand_uebersicht_loeschen(auftrag_id):
filter_maschine_id = request.form.get("maschine_id") or ""
filter_status = request.form.get("status") or "offen_pause"
with db.get_cursor() as cur:
cur.execute('DELETE FROM "Auftraege" WHERE id = %s', (auftrag_id,))
# Aktive Aufträge (gerade an der Maschine laufend) dürfen hier nicht
# gelöscht werden können, unabhängig davon, was im UI angeboten wird
# (analog zur Bearbeiten-Sperre für aktive Aufträge).
cur.execute('DELETE FROM "Auftraege" WHERE id = %s AND status <> \'aktiv\'', (auftrag_id,))
return redirect(url_for("leitstand_uebersicht", maschine_id=filter_maschine_id, status=filter_status))
@@ -1123,4 +1154,4 @@ def health():
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)
app.run(host="0.0.0.0", port=5000, debug=False)
+6 -2
View File
@@ -14,7 +14,7 @@
</select>
<span>Status:</span>
<select name="status" onchange="this.form.submit()">
<option value="offen_pause" {% if filter_status == "offen_pause" %}selected{% endif %}>Offen/Pausiert</option>
<option value="offen_pause" {% if filter_status == "offen_pause" %}selected{% endif %}>Offen/Pausiert/Aktiv</option>
<option value="alle" {% if filter_status == "alle" %}selected{% endif %}>Alle</option>
</select>
<button type="submit">Filtern</button>
@@ -42,6 +42,9 @@
<td>{{ "Ja" if a.aktiv else "Nein" }}</td>
<td>{{ a.sid_fmt }}</td>
<td class="col-actions">
{% if a.status == "aktiv" %}
<span style="color:var(--text-dim); font-size:0.85em;">gesperrt (aktiv an Maschine)</span>
{% else %}
<div class="row-actions">
<a class="icon-btn" href="{{ url_for('leitstand_uebersicht_bearbeiten', auftrag_id=a.id, maschine_id=filter_maschine_id, status=filter_status) }}">Bearbeiten</a>
<form method="post" action="{{ url_for('leitstand_uebersicht_loeschen', auftrag_id=a.id) }}" class="order-form">
@@ -50,6 +53,7 @@
<button type="submit" class="icon-btn danger">L&ouml;schen</button>
</form>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
@@ -57,4 +61,4 @@
</table>
</div>
{% endif %}
{% endblock %}
{% endblock %}
@@ -31,7 +31,18 @@
<label for="soll_h">St&uuml;ck pro Stunde</label>
<input type="number" step="any" id="soll_h" name="soll_h" value="{{ auftrag.soll_h_fmt }}" {{ 'readonly' if gesperrt }} required>
</div>
{% if gesperrt %}
<div class="status-line"><span class="label">Status</span><span>{{ status_anzeige }}</span></div>
{% else %}
<div class="form-group">
<label for="status">Status</label>
<select id="status" name="status" required>
{% for wert, label in status_optionen %}
<option value="{{ wert }}" {% if auftrag.status == wert %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="status-line">
<span class="label">Priorit&auml;t</span>
<span>{{ auftrag.sid_fmt }} <span style="color:var(--text-dim); font-size:0.85em;">(&auml;nderbar unter "Auftragspriorit&auml;t")</span></span>