Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
455f513f6e | ||
|
|
d6c066e18f |
@@ -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 }}
|
||||
@@ -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/`.
|
||||
+42
-11
@@ -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)
|
||||
@@ -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ö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ü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ät</span>
|
||||
<span>{{ auftrag.sid_fmt }} <span style="color:var(--text-dim); font-size:0.85em;">(änderbar unter "Auftragspriorität")</span></span>
|
||||
|
||||
Reference in New Issue
Block a user