Compare commits

...

7 Commits

Author SHA1 Message Date
efde35130b release: v0.9.27
All checks were successful
Stable Release / release (push) Successful in 4m9s
2026-06-28 17:00:49 +02:00
a31e01d28c chore: Version auf 0.9.27 erhöhen 2026-06-28 16:55:39 +02:00
7a43698ecc chore: Ein-Repo-Modell — Tests, Doku, gitignore (CLAUDE.md+release.sh) 2026-06-28 16:51:35 +02:00
6b12bfb321 chore: master auf nightly9-Stand bringen 2026-06-28 16:49:14 +02:00
823cbfe1a9 build: sources for v0.9.27-rc1
All checks were successful
Stable Release / release (push) Successful in 4m21s
2026-06-28 16:21:04 +02:00
ce416f3b9a ci: release.yml nur noch Docker-Build (Release macht release.sh)
All checks were successful
Nightly Build / build (push) Successful in 3m36s
- "Create Gitea Release"-Step entfernt → keine doppelte Release-Erstellung
  mehr (release.sh legt Release + englischen Auto-Changelog + Assets an).
- Image-Tag strippt fuehrendes 'v' (VERSION-Datei hat keins).
- Tag-Pattern auf 'v*' erweitert (vorher matchte v0.9.x.y-Hotfixes nicht).
2026-06-26 23:45:23 +02:00
67c013f4ff nightly: 0.9.27-nightly9
All checks were successful
Nightly Build / build (push) Successful in 3m41s
2026-06-26 23:19:02 +02:00
28 changed files with 916 additions and 261 deletions

View File

@@ -3,7 +3,7 @@ name: Stable Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v*'
jobs:
release:
@@ -56,9 +56,10 @@ jobs:
echo "${{ secrets.REGISTRY_TOKEN }}" | \
docker login gitea.it-drui.de -u "${{ secrets.REGISTRY_USER }}" --password-stdin
# Strip fuehrendes 'v' fuer den Image-Tag (VERSION-Datei hat kein 'v').
- name: Build & push (amd64 + arm64)
run: |
VERSION="${GITHUB_REF#refs/tags/}"
VERSION="${GITHUB_REF#refs/tags/v}"
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
@@ -68,20 +69,6 @@ jobs:
-t "gitea.it-drui.de/viewit/kx-bridge:${VERSION}" \
.
- name: Create Gitea Release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
VERSION="${GITHUB_REF#refs/tags/}"
CHANGELOG=$(awk "/^## \[${VERSION}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md || echo "")
curl -s -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases" \
-d "{
\"tag_name\": \"${VERSION}\",
\"name\": \"KX-Bridge ${VERSION}\",
\"body\": $(echo "$CHANGELOG" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))'),
\"draft\": false,
\"prerelease\": false
}"
# Hinweis: Das Gitea-Release (inkl. englischem Auto-Changelog + Binaries als
# Assets) erstellt release.sh synchron, da es die lokal via CodeBuilder
# gebauten Binaries direkt hochlaedt. Dieser Workflow baut nur das Docker-Image.

4
.gitignore vendored
View File

@@ -18,3 +18,7 @@ data/
!data/orca_filaments.json
.runner-token
# Dev-only Dateien — nicht ins öffentliche Repo
CLAUDE.md
release.sh

View File

@@ -1,9 +1,10 @@
## KX-Bridge 0.9.27-nightly8 — Nightly Build
## KX-Bridge 0.9.27-nightly9 — Nightly Build
[experimentell] Ungetestete Features, nur für Tester geeignet.
### Änderungen seit `v0.9.13`
### Änderungen seit `v0.9.26`
- fix(spoolman): Status-Dot beim Seitenload initialisieren
- chore: CHANGES.md mit echtem Changelog aus Dev-Repo ins Release-Repo schreiben
- fix(spoolman): SPOOLMAN_* env-Cache bei Restart leeren, Status-Dot nach Save aktualisieren
- chore: README.es.md + CONTRIBUTING.md in release.sh-Sync aufnehmen
@@ -20,245 +21,6 @@
- feat(release): Nightly/Stable Release-Workflow mit eigenem Docker-Tag
- feat(spoolman): optionale Spoolman-Filamentverbrauch-Integration (PR #65, @p2l)
- fix(release): Artifact-Download per HTTP statt lokalem Dateipfad
- feat(ui): PR #60 — Print-Workflow + Skip-Dialog Fixes (@gangoke)
- Revert "feat(ui): PR #60 — Print-Workflow + Skip-Dialog Fixes (@gangoke)"
- feat(ui): PR #60 — Print-Workflow + Skip-Dialog Fixes (@gangoke)
- fix(release): PyInstaller-Cache zwischen Builds via extra_git_cmds leeren
- fix(upload): File-Browser nur GCode zulassen (Issue #59)
- fix(mqtt): TLS-Socket Read/Write-Race — Segfault in libcrypto (Issue #53)
- feat(ui): Slot-Mapper Skip-Parität + konkrete Profilnamen (Issue #57)
- feat(print): Auto-Open-Druckdialog + Per-Print-Auto-Leveling + Skip-Fix (PR #56)
- feat(ui): Idle-Buttons im Progress-Card + fix(moonraker): Mobileraker-Kompatibilität (Issue #48, #55)
- feat(filament): Vendor-Visibility-Filter für Profil-Dropdown (Issue #41)
- feat(ui): Settings als Master-Detail-Panel mit Kategorie-Sidebar
- chore: CodeBuilder-URL auf 192.168.178.204 gesetzt
- docs(changelog): 0.9.21 — Android-Kamera, IPv4, Debian-Bookworm, Mobileraker-Fix
- fix(ui): Kamera-Stream auf Android via Snapshot-Polling
- fix(docker): python:3.11-slim-bookworm pinnen (glibc 2.41 GPF auf älteren Kerneln, Issue #53)
- fix(moonraker): configfile extruder-Stub um sensor_type + filament_diameter erweitert (Mobileraker #48)
- fix(release): Artifact-Pfad /app/ → Host-Pfad umschreiben
- docs(changelog): 0.9.20 — French, Z-height, camera fixes, light bug
- fix(docker): ffmpeg ins Image aufnehmen
- chore(i18n): Log-Meldungen auf Englisch umgestellt
- chore(security): 04_configs/decrypted/ zu .gitignore hinzugefügt
- fix(camera): Kamera-Autostart ignoriert Setting nach Bridge-Restart (#50)
- feat(i18n): Französisch hinzugefügt (PR #45, @Nathacks)
- merge: PR #49 — Z-Höhe in der Print-UI anzeigen (@Nathacks)
- Revert "fix(ace): ACE-Slot-Offset um 1 korrigiert (Issue #47)"
- fix(ace): ACE-Slot-Offset um 1 korrigiert (Issue #47)
- fix(compat): configfile-Stub + absolute Webcam-URLs für Mobileraker/Obico
- docs(claude): CLAUDE.md auf aktuellen Stand 0.9.19.1
- docs(changelog): translate 0.9.19.1 entry to English (CHANGELOG.md was German by mistake)
- fix(spec): VERSION-Datei ins Onefile einbetten — beseitigt vunknown in standalone Binaries
- chore: CHANGELOG 0.9.19.1 + VERSION zurueck fuer release.sh-Bump
- feat(filaments): nur Kobra-X-sichtbare Profile (209 statt 399)
- fix(mqtt): klare Meldung wenn anycubic_slicer.crt/.key fehlen (statt Errno 2)
- chore(gitignore): ignore lokale Build-/Test-/TODO-Pfade
- fix(spec): VERSION-Datei ins Onefile einbetten — beseitigt vunknown in standalone Binaries
- docs(changelog): 0.9.19.1 hotfix entry
- docs(changelog): remove internal test-profile name 'Bert - PLA'
- docs: README — Features für 0.9.17/18/19 + OrcaSlicer-KX-PR-Liste + Kobra S1/S1Max-Hinweis
- chore: 0.9.19 — filament sync matches real presets, custom profile import
- fix(profile-import): User-Profile mit Inherits-Kette parsen
- fix(ui): Slot-Card direkt nach Speichern neu rendern
- fix(filament-sync): Default ist Generic <Typ> statt Anycubic <Typ>
- fix(filament-sync): Phantom-Profile raus, Default-Lookup aus JSON
- feat(filament-sync): gate_filament_name im HH-MMU-Objekt befüllen
- feat(filament-sync): sende name + vendor_name pro Lane (PR #13719-kompatibel)
- fix(ace-dryer): setDry fire-and-forget (PR #42 von @gangoke)
- feat(filament): Custom-Profile-Import via Bridge-UI Upload (Issue #41)
- fix(filament): Kobra-X-kompatible IDs in orca_filaments.json + Defaults
- chore: 0.9.18 — Filament set from bridge works, Obico Z-height, slot profile picker fixes
- feat(ui): Slot-Profil-Dropdown auf (vendor,name) + Pause/Resume-Toggle + Vendor-Badge auf Slot-Karte
- feat(bridge): Slot-Profil-Roundtrip + Z-Höhe für Obico + multiColorBox/setInfo via web/printer (Issue #39)
- feat(filament): (vendor,name)-Selector statt filament_id im config-Schema
- feat(i18n): sync Spanish translation refactor from KX-Bridge-Release PR #40
- fix(mqtt): persistenter Reconnect — Bridge erkennt Drucker-Reboot (Issue #33)
- chore: 0.9.17 — Obico (experimental), multi-language UI, filament-profile sync, H.264 stream
- fix(i18n): GCode-Browser nach Sprachwechsel neu rendern
- fix(upload): trennt Connect-/Send-/Read-Timeout für große GCode-Uploads
- feat(bridge): CameraCache + Translations-Route + h264-Endpoint
- feat(i18n): sync translation refactor from KX-Bridge-Release PR #37
- fix(temp): tempature/set über web/printer-Topic mit type-Feld
- fix(obico): Klipper-SET_HEATER_TEMPERATURE + korrekter MQTT-Pfad für Temp-Set
- fix(filament-list): erweiterte Vendor-Profile + Dropdown-Refresh bei Material-Wechsel
- feat(ams-sync): User-Override pro Slot für konkrete OrcaSlicer-Filament-Marke
- refactor(release): alle 3 kx-bridge-Targets via CodeBuilder-API
- feat(obico): Layer-aligned Time-Lapse + First-Layer-Scan Pseudo-Macros
- fix(obico): start_time/end_time als Unix-Timestamp (float) statt ISO-String
- feat(obico): Moonraker-Kompatibilität für moonraker-obico-Plugin
- feat: GCode Web-Upload + Download + Verify-Dialog (PR #32 von @gangoke)
- docs: OrcaSlicer-KX (gepatchter Slicer) im README verlinken
- docs: CHANGELOG 0.9.16 — Filament-Sync-Fix ergänzt
- fix: Filament-Sync positionstreu + Happy-Hare-Farbformat (OrcaSlicer)
- docs: CHANGELOG 0.9.16
- fix: i18n-Lücken im ACE-Dry-Dialog (DE-Übersetzungen + fehlender Key)
- fix: Slicer-Zeit + Thumbnail nach Reload / bei OrcaSlicer-Druck anzeigen
- feat: Settings-Option "Kamera bei Druckstart einschalten"
- feat: Log-Verbesserungen (Dedup-Zähler, Level-Filter, Fehler-Toast, mehr Infos)
- fix: einfarbiger Druck blockiert durch leeren AMS-Slot (OrcaSlicer)
- fix: release.sh resynct Windows-Agent hart vor dem Build
- docs: CHANGELOG 0.9.15 (Issue #29 OrcaSlicer-Webview-Fixes)
- fix: Print-Banner + UI im OrcaSlicer-Webview (Issue #29)
- fix: Upload-Banner nach Druckende ausblenden (Issue #29)
- docs: CHANGELOG 0.9.14 (Theme-System #27, API-Restart #28)
- feat: POST /api/restart Endpoint (PR #28 von @gangoke)
- fix: release.sh stellt kx-bridge.spec im Release-Repo sicher (Windows-Theme-Build)
- chore: kx-bridge.spec von *.spec-ignore ausnehmen
- build: kx-bridge.spec ins Repo aufnehmen (war durch *.spec geignored)
- build: einheitlicher PyInstaller-Spec für Linux + Windows (Theme-Einbettung)
- feat: Theme-System (PR #27 von hirnwunde/Olli)
- docs: klare Update-Warnung (EXE/Docker) im 0.9.13-CHANGELOG
- docs: CHANGELOG 0.9.13 (Self-Update-Fix)
- fix: Self-Update lädt alle Bridge-Module (nicht nur Hauptdatei)
- docs: CHANGELOG 0.9.12
- docs: Downloads-Badge in READMEs (800+ App-Downloads)
- docs: Home-Assistant-Integration (Community) verlinken (Issue #24)
- revert: Moonraker-UI-Integration entfernen (Mainsail/Fluidd/Mobileraker)
- fix: Mainsail Live-Werte + Pause-State (Issue #25)
- feat: Moonraker-Kompatibilität für Mainsail/Fluidd/Mobileraker (Issue #25)
- refactor: Frontend aus kobrax_moonraker_bridge.py auslagern
- docs: CHANGELOG 0.9.11 (ACE Pro 2 experimentell) + Version-Bump
- feat: ACE Pro 2 Support (PR #26 von gangoke)
- release: LICENSE + NOTICE.md ins Release-Repo syncen
- docs: GPLv3-Lizenz + NOTICE für Anycubic-Drittmaterial
- release: PyBuilder statt GitHub Actions fuer Windows-Build
- release: Versions-Marker-Check entfernt (Binary enthaelt VERSION nicht)
- release: verify_binary nutzt kobrax_moonraker_bridge + VERSION als Marker
- docs: CHANGELOG fuer 0.9.10 + Fokus-Hinweis Stabilisierung
- release: release.sh haerten Sanity-Checks + Build-Verifikation
- feat: Part-Skip (Exclude Object) pre-print + mid-print
- release: rohe kx-bridge + kx-bridge.exe Binaries als Release-Assets hochladen
- docs: CHANGELOG für 0.9.9 (bridge_url + Windows config_loader)
- fix: bridge_url leer im Single-Printer-Modus → relative Pfade (Issue #21)
- fix: release.sh kopiert config_loader.py ins Windows-Build-Repo
- docs: README EN/DE getrennt — README.md englisch, README.de.md deutsch (neues Design); release.sh kopiert beide ins Release-Repo
- docs: CHANGELOG für 0.9.8
- feat: Standalone-Tauglichkeit + Drucker add/remove + leerer Zustand
- feat: Multi-Printer in einer Bridge-Instanz
- feat: T14 Suche/Filter/Sortierung im Browser-Tab + T17 GCode-Filamente
- feat: Drucker-Name editierbar in Settings
- fix: Settings und Update-Check zeigen aktiven Drucker (via _apiUrl)
- fix: i18n für Drucker-Tab (DE/EN)
- feat: Drucker-Tab mit Live-Status aller Instanzen
- feat: Print-History im Browser-Tab (T10)
- fix: CORS-Middleware für alle Endpunkte + bridge_url via request.host
- feat: Slot-Auswahl auch im Upload-Banner (3. Button)
- feat: Filament-Slot-Dialog + POST /kx/print (T06+T08)
- feat: Multi-Printer WebUI + Routing (T05)
- feat: Multi-Printer Config-Format (T04)
- feat: i18n für Browser-Tab (DE/EN)
- chore: Browser-Tab umbenennen (Store → Browser)
- feat: GCode Store Tab in WebUI (T03)
- feat: GCode Store — SQLite-Persistenz, /kx/ API, Thumbnail-Extraktion (T01+T02)
- feat: Happy Hare MMU-Emulation für OrcaSlicer Filament-Sync
- chore: Changelog für 0.9.7 ergänzt
- fix: Upload großer GCode-Dateien (client_max_size 256MB, Socket-Timeout 120s)
- docs: CHANGELOG 0.9.6 nachgetragen (Licht-Sync, Zeit-Cards, Status-Strings, Fixes)
- build: fetch_credentials in release.sh ergänzt, build.sh auf Linux-only bereinigt
- build: sudo durch rm+cp ersetzt für Windows EXE-Copy (Docker root-owned)
- build: fetch_credentials Linux + Windows Binary in build.sh ergänzt
- docs: fetch_credentials als primärer Setup-Weg, Ko-fi Button ergänzt
- feat: fetch_credentials --write-config ersetzt extract_credentials Workflow
- feat: fetch_credentials.py aus PR #19 übernommen
- fix: YouTube Thumbnail auf hqdefault (maxresdefault nicht verfügbar)
- feat: Licht-Status live vom Drucker synchronisieren, Video Tutorial in README
- fix: Zeiten (verstrichen/rest/slicer) werden bei Stopp/Abbruch zurückgesetzt
- fix: file_ready-Banner nach Stopp/Abbruch wird nicht mehr angezeigt
- feat: Progress-Card mit Zeit-Minicards und Layer-Badge, Slicer-Zeit aus GCode-Ende (OrcaSlicer)
- fix: start.sh erstellt config/ und kopiert config.ini.example automatisch (Issue #15)
- feat: Upload-Banner mit Start/Abbrechen, Thumbnail bei Nur-hochladen, Auto-Print-Fix, Log-Scroll-Fix
- fix: auto_print-Reset nach Multipart-Loop entfernt, Log-Scroll-Position bei deaktiviertem Auto-Scroll erhalten
- feat: file_ready Banner nach Upload-only Druck-Start-Button anzeigen
- fix: Upload-only vs. Upload+Print korrekt unterscheiden
- fix: release.sh Release-Name einheitlich als 'v0.9.x' (wie bisherige Releases)
- fix: release.sh CHANGELOG-Body automatisch in Gitea-Release schreiben
- fix: release.sh stabile Releases nicht als Pre-Release markieren
- fix: i18n Kamera-Placeholder und Log-Dir-Button übersetzt
- feat: AMS-Slot-Dialog vollständig i18n (DE/EN)
- fix: AMS-Slot onclick JSON.stringify im HTML-Attribut vermeiden
- feat: AMS-Slot-Editor Farbe und Material per Klick ändern
- feat: Log-Tab erweitert volle Payload + TX/RX/Topic-Filter
- docs: CHANGELOG und README auf v0.9.3 aktualisiert
- fix: Update-Check liefert stable statt Dev, VERSION im Dockerfile, Version im Header (#14)
- docs: README.de.md hinzugefügt, README.en.md entfernt einheitliche Benennung
- docs: CHANGELOG.de.md deutsche Version des Changelogs
- chore: README und CHANGELOG auf Englisch, UI-Fehlertexte englisch, i18n für connection_error Banner
- chore: 0.9.2 CHANGELOG, toter Code in config_loader entfernt
- chore: Dev-Version auf 0.9.1-dev+1a8ee2a gesetzt
- chore: Release-Binaries und Logs aus Git-Index entfernen, .gitignore erweitert
- fix: Verbindungsfehler in Web-UI als Banner anzeigen (#11); README: OrcaSlicer-URL und extract_credentials-Link präzisiert (#12/#13)
- feat: config.ini statt .env persistente Settings über docker-compose restart
- docs: CHANGELOG dev Slicer-Schätzzeit und Update-Dialog Changelog ergänzt
- chore: Dev-Version auf 0.9.1-dev+14cfb27 gesetzt
- feat: Slicer-Schätzzeit im Fortschritts-Panel anzeigen
- revert: Obico-Integration entfernt
- feat(obico): Kamera-Snapshots aktiv zu Obico hochladen
- fix(obico): BRIDGE_URL für Docker-Netzwerk-Kompatibilität
- feat(obico): Layer-Fortschritt in Obico anzeigen
- fix(obico): Snapshot-Endpoint und Layer-Anzeige
- feat(obico): Obico Selfhosted Integration vollständig
- chore: Dev-Version auf 0.9.1-dev+ea587fc gesetzt
- feat: Obico Verbindungsstatus Badge im Header, connected-Tracking in ObicoAgent
- chore: Dev-Version auf 0.9.1-dev+aca91a5 gesetzt
- feat: Obico Selfhosted Integration ObicoAgent, Snapshot-Endpoint, Settings-Modal
- chore: Dev-Version auf 0.9.1-dev+5ddfa13 gesetzt
- feat: Changelog aus Gitea-Release im Update-Dialog anzeigen
- chore: Dev-Version auf 0.9.1-dev+60d4287 gesetzt
- docs: CHANGELOG für dev-Branch ergänzt
- chore: Dev-Version auf 0.9.1-dev+374af9e gesetzt
- chore: Log-Buffer auf 500 Einträge vereinheitlicht (Server + Browser)
- chore: Dev-Version auf 0.9.1-dev+1aa8a75 gesetzt
- feat: Log-Tab Auto-Scroll toggle, Textfilter, Error-Badge im Nav-Tab
- chore: Dev-Version auf 0.9.1-dev+da0bf51 gesetzt
- feat: Server-Log live in Browser-Console (SSE) + Download-Button
- chore: Dev-Version auf 0.9.1-dev+a52f76f gesetzt
- feat: MQTT-Logging strukturiertes TX/RX-Log mit Duplikat-Filter
- chore: Dev-Version auf 0.9.1-dev+1828eec gesetzt
- feat: Dev-Infrastruktur Versionsschema 0.9.1-dev+hash, separater Update-Kanal, README.dev.md
- feat: AMS Slot-Auswahl und Auto-Leveling-Option im Settings-Modal (Issue #6)
- chore: _archive/releases Binaries aus Git-Tracking entfernt
- chore: knlogo.png aus Dev-Repo entfernt (nur Release-Repo)
- fix: Bridge zeigt beim Start lokale IP statt 0.0.0.0; README-Hinweis verbessert
- chore: release.sh aktualisiert Version in Release-Repo-READMEs und kopiert Logo
- feat: Logo ins englische README hinzugefügt
- feat: Logo ins README und Projektverzeichnis hinzugefügt
- fix: AMS leere Slots in UI grau darstellen (Issue #5)
- fix: use_ams nur true wenn belegte Slots vorhanden
- fix: leere AMS-Slots beim Druckstart filtern (status!=5), material_type-Feld korrigiert
- fix: apt ffmpeg entfernt, imageio-ffmpeg übernimmt
- fix: Home X/Y entfernt, homeAxis durch homeZ/homeXY ersetzt
- feat: beta14 imageio-ffmpeg, Z-Buttons fix, Home XY/All, Motors Off
- feat: beta13 Windows-Fixes, Projektstruktur-Reorganisation
- refactor: Projektstruktur überarbeitet
- feat: Changelog, Tests, start.sh und Dockerfile-Fixes hinzufügen
- docs: Version auf 0.9.1-beta10 korrigieren
- docs: LAN-Modus korrekt beschreiben (Drucker-Menüoption)
- feat: Offline-Start + Verbinden/Trennen-Button im Header
- feat: Release-ZIPs mit Zertifikaten (linux/windows/certs-only)
- fix: PyInstaller frozen-Binary __file__ durch sys.executable-Pfad ersetzen
- feat: Windows kx-bridge.exe via GitHub Actions bauen
- fix: release.sh kopiert READMEs nicht mehr (Release-Repo hat eigene)
- docs: README.md aktualisiert, README.en.md hinzugefügt, release.sh kopiert READMEs
- docs: Benutzerdoku aktualisiert (v0.9.1-beta4) + englische Version hinzugefügt
- fix: progress und filename auf 0 zurücksetzen bei stoped/canceled
- feat: neue Drucker-Zustände pausing/paused/resuming/resumed/stopping/stoped
- fix: release.sh rm Fehler nach Docker-Build abfangen
- fix: release.sh Windows-EXE Build korrigiert (leeres Workdir, richtiger dist-Pfad)
- feat: release.sh Binaries bauen + als Gitea-Assets hochladen
- feat: Print-Speed-Card, Übersetzungen vervollständigt
- fix: Temperatursteuerung während des Drucks
- chore: release.sh Version-Bump und Release-Sync Skript
- feat: UI-Komplettüberarbeitung Settings, Self-Update, Dashboard, Responsive
- feat: UI-Überarbeitung KX-Bridge Titel, ViewIT Footer, Kobra-State-Anzeige, Dashboard-Erweiterung
- feat: UI Titel KX-Bridge, Footer ViewIT 2026
- fix: README Fehlerbehebung Credential-Rotation entfernt, --verbose Hinweis ergänzt
- fix: sensible Credentials aus README entfernt (Beispielausgabe anonymisiert)
- docs: CLAUDE.md Projektstruktur + Deployment-Status aktualisiert
- feat: Public Beta 0.9.0-beta1 Docker, Binaries, README, Build-Prozess
- feat: extract_credentials.py + Bridge Tip-Forming & lane_data Endpoint
- feat: AMS Einziehen/Ausziehen UI + feedFilament-Protokoll dokumentiert
- Initial commit: KX-Bridge + Protokoll-Dokumentation
---

147
README.dev.md Normal file
View File

@@ -0,0 +1,147 @@
<p align="center"><img src="knlogo.png" alt="KX-Bridge Logo" width="180"/></p>
# KX-Bridge Dev Branch
> **Achtung:** Dies ist der Entwicklungs-Branch. Builds hier sind experimentell und nicht für den produktiven Einsatz geeignet.
> Für stabile Releases → [KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
---
## Versionsschema
Dev-Builds verwenden das Format:
```
<basis-version>-dev+<git-hash>
```
**Beispiel:** `0.9.1-dev+04a6a20`
- `0.9.1` Basis der aktuellen stabilen Version
- `-dev` kennzeichnet den Entwicklungs-Branch
- `+04a6a20` 7-stelliger Git-Commit-Hash, eindeutig je Build
---
## Dev-Binaries testen
Dev-Releases sind auf Gitea als Pre-Releases verfügbar:
[Dev-Releases](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
### Docker (empfohlen)
```bash
git clone <repo-url> -b dev
cd kobrax
docker compose up -d
```
### Linux-Binary
```bash
# Dev-Release herunterladen (kx-bridge-linux.zip)
unzip kx-bridge-linux.zip
chmod +x kx-bridge
./kx-bridge
```
`config/config.ini` und `data/` (SQLite + GCode-Store) werden **neben dem Binary**
angelegt. Beim Erststart ohne Drucker zeigt die UI auf `http://localhost:7125` den
Drucker-Tab mit "+ Drucker hinzufügen" — dort nur die IP eingeben, der Rest wird
automatisch importiert.
### Windows-EXE
```
# Dev-Release herunterladen (kx-bridge-windows.zip)
# kx-bridge.exe starten — config/ und data/ liegen daneben
```
---
## Update-Kanal
Dev-Versionen prüfen automatisch auf neue **Dev-Releases** — nicht auf stabile Releases.
Im Settings-Modal → „Auf Updates prüfen" zeigt den neuesten Dev-Build an.
---
## Aktive Entwicklung (Stand 2026-05-10)
Stand `dev`-Branch über v0.9.7 hinaus:
| Feature | Status |
|---------|--------|
| MMU-Emulation (`/printer/objects/query?mmu`) für OrcaSlicer Filament-Sync | ✅ |
| GCode Store (SQLite + Thumbnails) | ✅ |
| Browser-Tab mit Suche/Filter/Sortierung | ✅ |
| Filament-Dialog: Per-Kanal-Remapping (GCode-Kanal → AMS-Slot) | ✅ |
| MQTT Print-Payload `ams_settings.ams_box_mapping` (nested) | ✅ |
| Print-History in SQLite | ✅ |
| Multi-Printer Support (Drucker-Tab + Header-Dropdown) | ✅ |
| **Multi-Printer in einer Bridge-Instanz** (ein Prozess, N Listener) | ✅ |
| Drucker-Emulator (`_archive/tools/kx_printer_emulator.py`) | ✅ |
| i18n DE/EN für alle neuen UI-Elemente | ✅ |
---
## Multi-Printer-Setup
Eine Bridge-Instanz kann jetzt mehrere Drucker gleichzeitig verwalten — ein Prozess,
N MQTT-Verbindungen, N HTTP-Listener, geteilte SQLite + GCode-Verzeichnis.
### Konfiguration
In `config/config.ini` pro Drucker eine `[printer_N]`-Sektion anlegen:
```ini
[printer_1]
name = Kobra X
printer_ip = <DRUCKER_IP_1>
mqtt_port = 9883
username = <MQTT_USER>
password = <MQTT_PASSWORT>
mode_id = 20030
device_id = <DEVICE_ID_1>
http_port = 7125
[printer_2]
name = Drucker 2
printer_ip = <DRUCKER_IP_2>
mqtt_port = 9883
username = <MQTT_USER>
password = <MQTT_PASSWORT>
mode_id = 20030
device_id = <DEVICE_ID_2>
http_port = 7126
```
Credentials per `extract_credentials` oder `fetch_credentials` ermitteln (siehe Haupt-README).
`http_port` ist optional — Default ist `7125 + (N-1)`. Wenn keine `[printer_N]`-Sektionen
existieren, läuft die Bridge im klassischen Einzel-Modus mit `[connection]` und einem Listener.
### Docker
`docker-compose.yml` exposed jetzt einen Port-Range `7125-7130`:
```yaml
ports:
- "7125-7130:7125-7130"
```
```bash
docker compose up -d
# Drucker 1: http://localhost:7125
# Drucker 2: http://localhost:7126
```
OrcaSlicer / Mainsail richten den Klipper-Endpunkt pro Drucker auf den jeweiligen Port —
keine Slicer-Anpassungen nötig.
---
## Stabile Version
Für den produktiven Einsatz bitte die stabile Version verwenden:
[→ Zum stabilen Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)

View File

@@ -1 +1 @@
0.9.27-nightly8
0.9.27

3
pytest.ini Normal file
View File

@@ -0,0 +1,3 @@
[pytest]
asyncio_mode = auto
testpaths = tests

View File

@@ -0,0 +1,11 @@
06873a3b6773e6e755deff33dba468dee3af09859f9b026b14a600501c8285cb anycubic-certs.zip
68e08c6b6badb2bf86f61e2164e96b902944c885f083a598ea4e3c854c432e91 extract_credentials
8d4d78bdc887e2512790805b711b941420536d5f0f30043b5f3bc74d4fa893d3 extract_credentials.exe
9b79a5f8edc734018fc547ac232ddd822ec5fee050e99e3305ccf8f38b177c9e fetch_credentials
b79615bc2f9df33bccd5471bfcd83c9bdc5291e857fe5b04927b171342bc1b48 fetch_credentials.exe
37ae8e3f4dc7bb6cf434de980db4aed9c1047d995063ce289bdc268f7764380e kx-bridge.exe
4f636f91fef7eb1ea1ad1ae8c7f7eaa64b725e4dc2e74db42a53ce5dc5808ba3 kx-bridge-linux-amd64
b0ff6fa222cfbf79e0ab609aac4c9d3b1acd80b5c4df10c7847e0c7d125913bc kx-bridge-linux-amd64.zip
0cd9448ceec03cc7f545f9c62a21b7075a866dba5ee396ec26b48816f927db1a kx-bridge-linux-arm64
99c4641bbdcc000b2cf2142ee01c16942b13da47ea837885fa1be68e984f6101 kx-bridge-linux-arm64.zip
31659f76913e1c785cfa9e0a9e87f893a483a6a55248006b046ac07b25b40870 kx-bridge-windows.zip

Binary file not shown.

View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEDTCCAvWgAwIBAgICAZAwDQYJKoZIhvcNAQEFBQAwgZsxCzAJBgNVBAYTAkNO
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxEzARBgNVBAMMCkFDIFJvb3Qg
Q0ExKjAoBgkqhkiG9w0BCQEWG2FueWN1YmljX2Nsb3VkQGFueWN1YmljLmNvbTAg
Fw0yMzA3MjAwMzI3NTFaGA8yMTIzMDcyMTAzMjc1MVowgZ8xCzAJBgNVBAYTAkNO
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxFzAVBgNVBAMMDkFueWN1Ymlj
U2xpY2VyMSowKAYJKoZIhvcNAQkBFhthbnljdWJpY19jbG91ZEBhbnljdWJpYy5j
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdoQ7g2F/yecfpdlqT
b8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYnyPep
43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0YpVk
K3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5ZwaTP
8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0ThecJ
cTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUaeLWkI
ajSJAgMBAAGjUzBRMB0GA1UdDgQWBBRI4P3/uKdYYFPEcFIwYxdv1p9gETAfBgNV
HSMEGDAWgBQlkDqpFERfr3u1rR9gNbNKtgrHIjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQBP3ws80Y9eBR2lpjYP3rVvH8kA6+LnEXT4PpHj+fSw
jciaNskzpiwNvBy00m32ACR5YKlMUjevlQuyyw+LQbTUwAEOwyy9SDQpiXdjL6q3
SPQ4aB4A57nFXOGrthc/nb9yFcteWrZrKbwvVUu2vqU7U8n7lJKjhVuFRWSXS3SV
sPc9JZ21kpPYWKbGtfD6jUlW0Ip+PurLw9FrbVwnEcOMf/ezSlrH5c8mfJyo8pVk
aC/6PpReqijusOSRZ5oLyhPvtgddXseJFByun1Ud0CDlFA05nGGPmnVcXD+GMnHH
i6baCTeifwp5Jpdzv4imcCPvayKUNuX32vYNfNkWC/R5
-----END CERTIFICATE-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdoQ7g2F/yecfp
dlqTb8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYn
yPep43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0
YpVkK3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5Z
waTP8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0T
hecJcTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUae
LWkIajSJAgMBAAECggEASwRkC9lRiLqN30kvWW5g6hsec8KrTfLm2pMCVy2AlgxB
B3VD51YvKzERyBwSKITT/1RPK9K/4xe3NrpAkmGsd3vLd8W+vorvXFePr7gct7VP
4Wb+J7D+keKXlg2sswRiHqI0PN45Nzq/iBaCaJiIMiPbB0+PHBl9J/Cv7XsD3tq+
9WKhvXf2g1g9GMrLaCCcWXWCqcu0LlbqJnw3yMnJLSltmyFTmlVLjDHM75bMVz97
4emQzOlnRN2yA5cWWCaM+mgjNM2aWwUsXBZzCgwSqSaj1QD4B/epCuDBORWHS9D6
jL15w8xjly9q8OS+4d6beR5h9GiPyMK4Ff2wXImCXQKBgQDwXxtrL+kVZrQ/qftj
24F3+QDN0j5Z3lUMTfZPn6ng/E/aBfn8KcWJHj2vYkKZdB5wOXJr56BYe3Hukzfp
QF0E2+g1WAGskF1mb/vVab54geox5Y6CA+ionRn2kcCwybVkktR/0JK2UV9Qjb/z
k1WU+RUhNrW/GDBqYulaadnR+wKBgQDsCf2/yKGPxj4pIvAtn5RFSlfscddgkSnc
ouBkDXEp5ta+5PGrlrdzS/F0vFhvBPbfbVJxVwRnM/Oqj8c0/bj7oc5RpPxirciO
AaovKVPTiORaviytnB2HgkflkJfy5vdXv4ZQahAV/UwtSmLwBshe+Ya68MAFrQRa
7M4z6k4QSwKBgQCm7OVVoofzXMeADsONrTpT3pA4XvD95/CYAuwyj2ah35Z0igH4
o+mSN3YO/eXSO1mIBdz4Inqv98o/K+2ABjqSzUSNBvjipb63DL2Oj0i+1zmUPR6i
G6TOs4r8OGvgWbOmjHEV8fpwskHG5ymONZsRQYjy79N3SY0V1GrJZwjlUQKBgD0x
AeWcP7YkMK09b4KEYk3sTgrwIGPafj3Cw+VsTrAMNhPbCoPvWLO9NmWLBmoRoWae
0sarRmry3vKSv5QPSsuBURl9aiiy4NFfwRzk2+R1Eq4rqy1+0XD152muKJZCJlFL
R6jFNlJdDkiXhjqvp3ZnvfPswfs2tXBU/8gZsA8tAoGBALXfc5m9I5R1l1zN7tpa
ncA0S3EKzqmuCc3KzlS6OS0e9Lz1MsmfEsvxvW3w4SrdfTbwQpEy9RNg89dlgPtc
rdId1QdN2eWPY5M4lz9n9EYdzi9ufoKAEYu2a0lP+qz690JwmL1Jx49bvQEn5Nu0
4swn72uwBRlhjAw46MF77SBQ
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,89 @@
pyinstaller --onefile --name extract_credentials /src/extract_credentials.py && cp /src/dist/extract_credentials.exe /out/ && pyinstaller --onefile --name fetch_credentials /src/fetch_credentials.py && cp /src/dist/fetch_credentials.exe /out/
114 INFO: PyInstaller: 3.6
114 INFO: Python: 3.7.5
114 INFO: Platform: Windows-7-6.1.7601-SP1
115 INFO: wrote Z:\src\extract_credentials.spec
120 INFO: UPX is not available.
121 INFO: Extending PYTHONPATH with paths
['Z:\\src', 'Z:\\src']
121 INFO: checking Analysis
121 INFO: Building Analysis because Analysis-00.toc is non existent
121 INFO: Initializing module dependency graph...
124 INFO: Caching module graph hooks...
130 INFO: Analyzing base_library.zip ...
2142 INFO: Caching module dependency graph...
2226 INFO: running Analysis Analysis-00.toc
2234 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
required by c:\Python37\python.exe
2467 INFO: Analyzing \src\extract_credentials.py
2497 INFO: Processing module hooks...
2497 INFO: Loading module hook "hook-encodings.py"...
2649 INFO: Loading module hook "hook-pydoc.py"...
2650 INFO: Loading module hook "hook-xml.py"...
2788 INFO: Looking for ctypes DLLs
2791 INFO: Analyzing run-time hooks ...
2794 INFO: Looking for dynamic libraries
2909 INFO: Looking for eggs
2909 INFO: Using Python library c:\Python37\python37.dll
2909 INFO: Found binding redirects:
[]
2910 INFO: Warnings written to Z:\src\build\extract_credentials\warn-extract_credentials.txt
2927 INFO: Graph cross-reference written to Z:\src\build\extract_credentials\xref-extract_credentials.html
2939 INFO: checking PYZ
2939 INFO: Building PYZ because PYZ-00.toc is non existent
2939 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz
3199 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz completed successfully.
3203 INFO: checking PKG
3203 INFO: Building PKG because PKG-00.toc is non existent
3203 INFO: Building PKG (CArchive) PKG-00.pkg
4599 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
4601 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
4601 INFO: checking EXE
4601 INFO: Building EXE because EXE-00.toc is non existent
4601 INFO: Building EXE from EXE-00.toc
4601 INFO: Appending archive to EXE Z:\src\dist\extract_credentials.exe
4604 INFO: Building EXE from EXE-00.toc completed successfully.
77 INFO: PyInstaller: 3.6
77 INFO: Python: 3.7.5
77 INFO: Platform: Windows-7-6.1.7601-SP1
77 INFO: wrote Z:\src\fetch_credentials.spec
83 INFO: UPX is not available.
84 INFO: Extending PYTHONPATH with paths
['Z:\\src', 'Z:\\src']
84 INFO: checking Analysis
84 INFO: Building Analysis because Analysis-00.toc is non existent
84 INFO: Initializing module dependency graph...
88 INFO: Caching module graph hooks...
94 INFO: Analyzing base_library.zip ...
2079 INFO: Caching module dependency graph...
2168 INFO: running Analysis Analysis-00.toc
2170 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
required by c:\Python37\python.exe
2425 INFO: Analyzing \src\fetch_credentials.py
2501 INFO: Processing module hooks...
2501 INFO: Loading module hook "hook-encodings.py"...
2630 INFO: Loading module hook "hook-pydoc.py"...
2631 INFO: Loading module hook "hook-xml.py"...
2765 INFO: Looking for ctypes DLLs
2765 INFO: Analyzing run-time hooks ...
2768 INFO: Looking for dynamic libraries
2878 INFO: Looking for eggs
2878 INFO: Using Python library c:\Python37\python37.dll
2878 INFO: Found binding redirects:
[]
2880 INFO: Warnings written to Z:\src\build\fetch_credentials\warn-fetch_credentials.txt
2896 INFO: Graph cross-reference written to Z:\src\build\fetch_credentials\xref-fetch_credentials.html
2901 INFO: checking PYZ
2901 INFO: Building PYZ because PYZ-00.toc is non existent
2901 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz
3159 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz completed successfully.
3163 INFO: checking PKG
3163 INFO: Building PKG because PKG-00.toc is non existent
3163 INFO: Building PKG (CArchive) PKG-00.pkg
4428 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
4430 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
4430 INFO: checking EXE
4430 INFO: Building EXE because EXE-00.toc is non existent
4430 INFO: Building EXE from EXE-00.toc
4430 INFO: Appending archive to EXE Z:\src\dist\fetch_credentials.exe
4434 INFO: Building EXE from EXE-00.toc completed successfully.

View File

@@ -0,0 +1,56 @@
32 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
32 INFO: Python: 3.12.3
33 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
33 INFO: Python environment: /usr
33 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/extract_credentials.spec
34 INFO: Module search paths (PYTHONPATH):
['/usr/lib/python312.zip',
'/usr/lib/python3.12',
'/usr/lib/python3.12/lib-dynload',
'/home/coding/.local/lib/python3.12/site-packages',
'/usr/local/lib/python3.12/dist-packages',
'/usr/lib/python3/dist-packages',
'/usr/lib/python3.12/dist-packages',
'/home/coding/Source/KX-Bridge-Release']
352 INFO: checking Analysis
352 INFO: Building Analysis because Analysis-00.toc is non existent
352 INFO: Looking for Python shared library...
364 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
364 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
364 INFO: Running Analysis Analysis-00.toc
364 INFO: Target bytecode optimization level: 0
364 INFO: Initializing module dependency graph...
366 INFO: Initializing module graph hook caches...
380 INFO: Analyzing modules for base_library.zip ...
1005 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
1923 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2491 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2739 INFO: Caching module dependency graph...
2764 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/extract_credentials.py
2790 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2801 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2808 INFO: Processing module hooks (post-graph stage)...
2812 INFO: Performing binary vs. data reclassification (1 entries)
2818 INFO: Looking for ctypes DLLs
2825 INFO: Analyzing run-time hooks ...
2825 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
2829 INFO: Creating base_library.zip...
2841 INFO: Looking for dynamic libraries
2987 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/warn-extract_credentials.txt
2993 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/xref-extract_credentials.html
3000 INFO: checking PYZ
3000 INFO: Building PYZ because PYZ-00.toc is non existent
3000 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz
3099 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz completed successfully.
3105 INFO: checking PKG
3105 INFO: Building PKG because PKG-00.toc is non existent
3105 INFO: Building PKG (CArchive) extract_credentials.pkg
5376 INFO: Building PKG (CArchive) extract_credentials.pkg completed successfully.
5376 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
5376 INFO: checking EXE
5376 INFO: Building EXE because EXE-00.toc is non existent
5376 INFO: Building EXE from EXE-00.toc
5376 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/extract_credentials
5376 INFO: Appending PKG archive to custom ELF section in EXE
5386 INFO: Building EXE from EXE-00.toc completed successfully.
5386 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27

View File

@@ -0,0 +1,91 @@
35 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
35 INFO: Python: 3.12.3
36 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
36 INFO: Python environment: /usr
36 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/fetch_credentials.spec
37 WARNING: collect_data_files - skipping data collection for module 'pycryptodome' as it is not a package.
37 WARNING: collect_dynamic_libs - skipping library collection for module 'pycryptodome' as it is not a package.
117 INFO: Module search paths (PYTHONPATH):
['/usr/lib/python312.zip',
'/usr/lib/python3.12',
'/usr/lib/python3.12/lib-dynload',
'/home/coding/.local/lib/python3.12/site-packages',
'/usr/local/lib/python3.12/dist-packages',
'/usr/lib/python3/dist-packages',
'/usr/lib/python3.12/dist-packages',
'/home/coding/Source/KX-Bridge-Release']
458 INFO: checking Analysis
458 INFO: Building Analysis because Analysis-00.toc is non existent
458 INFO: Looking for Python shared library...
475 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
475 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
475 INFO: Running Analysis Analysis-00.toc
475 INFO: Target bytecode optimization level: 0
476 INFO: Initializing module dependency graph...
476 INFO: Initializing module graph hook caches...
491 INFO: Analyzing modules for base_library.zip ...
1017 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
1871 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2469 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
2692 INFO: Caching module dependency graph...
2717 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/fetch_credentials.py
2750 INFO: Processing standard module hook 'hook-urllib3.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
2817 INFO: Processing pre-safe-import-module hook 'hook-typing_extensions.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
2818 INFO: SetuptoolsInfo: initializing cached setuptools info...
3154 INFO: Processing standard module hook 'hook-multiprocessing.util.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
3216 INFO: Processing standard module hook 'hook-xml.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
3402 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
4175 INFO: Processing standard module hook 'hook-chardet.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
4759 INFO: Processing standard module hook 'hook-cryptography.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
5142 INFO: hook-cryptography: cryptography uses dynamically-linked OpenSSL: '/lib/x86_64-linux-gnu/libssl.so.3'
5351 INFO: Processing standard module hook 'hook-bcrypt.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
5426 INFO: Processing standard module hook 'hook-certifi.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
5502 INFO: Processing standard module hook 'hook-Crypto.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
5706 INFO: Processing standard module hook 'hook-pycparser.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
5802 INFO: Processing standard module hook 'hook-setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
5806 INFO: Processing pre-safe-import-module hook 'hook-distutils.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
5861 INFO: Processing standard module hook 'hook-sysconfig.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
5896 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
5906 INFO: Processing standard module hook 'hook-_osx_support.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
6028 INFO: Processing pre-safe-import-module hook 'hook-importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
6141 INFO: Processing pre-safe-import-module hook 'hook-packaging.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
6502 INFO: Processing standard module hook 'hook-pkg_resources.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
7089 INFO: Processing standard module hook 'hook-xml.dom.domreg.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
7323 INFO: Processing standard module hook 'hook-sqlite3.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
7648 INFO: Processing standard module hook 'hook-difflib.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
7995 INFO: Processing module hooks (post-graph stage)...
7997 WARNING: Hidden import "pycparser.lextab" not found!
7997 WARNING: Hidden import "pycparser.yacctab" not found!
8231 INFO: Processing pre-safe-import-module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
8237 INFO: Processing standard module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
8304 INFO: Processing standard module hook 'hook-setuptools._vendor.importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
8391 INFO: Processing standard module hook 'hook-setuptools._vendor.jaraco.text.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
8490 INFO: Performing binary vs. data reclassification (51 entries)
8605 INFO: Looking for ctypes DLLs
8618 INFO: Analyzing run-time hooks ...
8620 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
8622 INFO: Including run-time hook 'pyi_rth_pkgutil.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
8623 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
8625 INFO: Including run-time hook 'pyi_rth_setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
8626 INFO: Including run-time hook 'pyi_rth_pkgres.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
8628 INFO: Including run-time hook 'pyi_rth_cryptography_openssl.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/rthooks'
8635 INFO: Creating base_library.zip...
8664 INFO: Looking for dynamic libraries
9293 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/warn-fetch_credentials.txt
9315 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/xref-fetch_credentials.html
9327 INFO: checking PYZ
9327 INFO: Building PYZ because PYZ-00.toc is non existent
9327 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz
9755 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz completed successfully.
9766 INFO: checking PKG
9766 INFO: Building PKG because PKG-00.toc is non existent
9766 INFO: Building PKG (CArchive) fetch_credentials.pkg
14335 INFO: Building PKG (CArchive) fetch_credentials.pkg completed successfully.
14336 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
14336 INFO: checking EXE
14336 INFO: Building EXE because EXE-00.toc is non existent
14336 INFO: Building EXE from EXE-00.toc
14336 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/fetch_credentials
14336 INFO: Appending PKG archive to custom ELF section in EXE
14360 INFO: Building EXE from EXE-00.toc completed successfully.
14361 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27

BIN
releases/0.9.27/fetch_credentials Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
releases/0.9.27/kx-bridge.exe Executable file

Binary file not shown.

68
tests/conftest.py Normal file
View File

@@ -0,0 +1,68 @@
"""
Shared fixtures für KX-Bridge Tests.
Startet die Bridge in-process mit einem Mock-MQTT-Client (kein Drucker nötig).
"""
import sys, types, argparse, pytest, pytest_asyncio
from unittest.mock import MagicMock
from aiohttp.test_utils import TestClient, TestServer
# ── Pfad ──────────────────────────────────────────────────────────────────────
sys.path.insert(0, str(__import__("pathlib").Path(__file__).parent.parent / "bridge"))
# ── env_loader mocken (keine .env nötig) ──────────────────────────────────────
env_mod = types.ModuleType("env_loader")
env_mod.PRINTER_IP = ""
env_mod.MQTT_PORT = 9883
env_mod.USERNAME = ""
env_mod.PASSWORD = ""
env_mod.MODE_ID = "20030"
env_mod.DEVICE_ID = ""
sys.modules["env_loader"] = env_mod
# ── Bridge + App importieren ───────────────────────────────────────────────────
from kobrax_moonraker_bridge import KobraXBridge, build_app # noqa: E402
def make_mock_client():
"""Minimaler Mock-MQTT-Client — keine Verbindung, keine Threads."""
c = MagicMock()
c.callbacks = {}
c.connected = False
return c
def make_args(**overrides):
args = argparse.Namespace(
printer_ip = "",
mqtt_port = 9883,
username = "",
password = "",
mode_id = "20030",
device_id = "",
host = "127.0.0.1",
port = 7125,
)
for k, v in overrides.items():
setattr(args, k, v)
return args
@pytest_asyncio.fixture
async def client():
"""TestClient mit frischer Bridge-Instanz, ohne MQTT-Verbindung."""
mock_client = make_mock_client()
bridge = KobraXBridge(mock_client, args=make_args())
app = build_app(bridge)
async with TestClient(TestServer(app)) as c:
yield c, bridge
@pytest_asyncio.fixture
async def client_configured():
"""TestClient mit bereits konfigurierten Zugangsdaten."""
mock_client = make_mock_client()
args = make_args(printer_ip="192.168.1.100", device_id="abc123deadbeef")
bridge = KobraXBridge(mock_client, args=args)
app = build_app(bridge)
async with TestClient(TestServer(app)) as c:
yield c, bridge

View File

@@ -0,0 +1,3 @@
pytest
pytest-asyncio
aiohttp

113
tests/test_api_state.py Normal file
View File

@@ -0,0 +1,113 @@
"""
Tests für /api/state — Drucker-Zustandsabfrage.
"""
import pytest
@pytest.mark.asyncio
async def test_state_returns_200(client):
c, _ = client
resp = await c.get("/api/state")
assert resp.status == 200
@pytest.mark.asyncio
async def test_state_schema(client):
"""Alle erwarteten Felder müssen vorhanden und typsicher sein."""
c, _ = client
resp = await c.get("/api/state")
data = await resp.json()
assert isinstance(data["print_state"], str)
assert isinstance(data["kobra_state"], str)
assert isinstance(data["nozzle_temp"], float)
assert isinstance(data["nozzle_target"], float)
assert isinstance(data["bed_temp"], float)
assert isinstance(data["bed_target"], float)
assert isinstance(data["progress"], float)
assert isinstance(data["print_duration"], int)
assert isinstance(data["remain_time"], int)
assert isinstance(data["curr_layer"], int)
assert isinstance(data["total_layers"], int)
assert isinstance(data["filename"], str)
assert isinstance(data["fan_speed"], int)
assert isinstance(data["light_on"], bool)
assert isinstance(data["ams_slots"], list)
@pytest.mark.asyncio
async def test_state_initial_values(client):
"""Im Offline-Start müssen Temperaturen 0 und Zustand 'standby' sein."""
c, _ = client
data = await (await c.get("/api/state")).json()
assert data["print_state"] == "standby"
assert data["nozzle_temp"] == 0.0
assert data["bed_temp"] == 0.0
assert data["progress"] == 0.0
assert data["filename"] == ""
@pytest.mark.asyncio
async def test_state_updates_after_mqtt_print_report(client):
"""Simuliert ein eingehendes print/report MQTT-Paket und prüft State-Update."""
c, bridge = client
# Simuliere MQTT-Nachricht wie vom echten Drucker
bridge._on_print({
"state": "printing",
"data": {
"filename": "test.gcode",
"progress": 42,
"print_time": 10, # Minuten → 600s
"remain_time": 5, # Minuten → 300s
"curr_layer": 20,
"total_layers": 100,
}
})
data = await (await c.get("/api/state")).json()
assert data["print_state"] == "printing"
assert data["filename"] == "test.gcode"
assert data["progress"] == pytest.approx(0.42)
assert data["print_duration"] == 600
assert data["remain_time"] == 300
assert data["curr_layer"] == 20
assert data["total_layers"] == 100
@pytest.mark.asyncio
async def test_state_updates_after_mqtt_temp_report(client):
"""Simuliert ein tempature/report Paket."""
c, bridge = client
bridge._on_temp({
"data": {
"curr_nozzle_temp": 215.3,
"target_nozzle_temp": 220.0,
"curr_hotbed_temp": 59.8,
"target_hotbed_temp": 60.0,
}
})
data = await (await c.get("/api/state")).json()
assert data["nozzle_temp"] == pytest.approx(215.3)
assert data["nozzle_target"] == pytest.approx(220.0)
assert data["bed_temp"] == pytest.approx(59.8)
assert data["bed_target"] == pytest.approx(60.0)
@pytest.mark.asyncio
async def test_state_resets_on_cancel(client):
"""Nach 'stoped' müssen Progress und Filename zurückgesetzt werden."""
c, bridge = client
# Erst Druck simulieren
bridge._on_print({"state": "printing", "data": {"filename": "x.gcode", "progress": 50}})
# Dann Abbruch
bridge._on_print({"state": "stoped", "data": {}})
data = await (await c.get("/api/state")).json()
assert data["progress"] == 0.0
assert data["filename"] == ""

109
tests/test_install.sh Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# test_install.sh Smoke-Test: Release-Repo klonen, start.sh ausführen, HTTP prüfen.
#
# Simuliert den Weg eines anonymen Nutzers:
# 1. Release-Repo klonen
# 2. start.sh ausführen (baut Docker-Image, startet Container)
# 3. HTTP-Endpunkte prüfen
# 4. Aufräumen
#
# Voraussetzung: Docker installiert, Port 7125 frei
#
# Verwendung:
# bash tests/test_install.sh
set -euo pipefail
GITEA_URL="https://gitea.it-drui.de/viewit/KX-Bridge-Release"
WORK_DIR=$(mktemp -d /tmp/kx-bridge-test-XXXXXX)
PASS=0; FAIL=0
ok() { echo "$*"; PASS=$((PASS+1)); }
fail() { echo "$*"; FAIL=$((FAIL+1)); }
cleanup() {
echo ""
echo "[cleanup] Stoppe Container und lösche Testverzeichnis ..."
cd "$WORK_DIR/KX-Bridge-Release" 2>/dev/null && docker-compose down 2>/dev/null || true
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
echo "=== KX-Bridge Installations-Smoke-Test ==="
echo ""
# ── Schritt 1: Repo klonen ────────────────────────────────────────────────────
echo "[1/5] Klone Release-Repo ..."
git clone --depth=1 "$GITEA_URL" "$WORK_DIR/KX-Bridge-Release" > /dev/null 2>&1 \
&& ok "Repo geklont" \
|| { fail "git clone fehlgeschlagen"; exit 1; }
cd "$WORK_DIR/KX-Bridge-Release"
# ── Schritt 2: Erwartete Dateien vorhanden ────────────────────────────────────
echo "[2/5] Prüfe Dateien im Repo ..."
for f in start.sh docker-compose.yml Dockerfile kobrax_moonraker_bridge.py \
anycubic_slicer.crt anycubic_slicer.key .env.example; do
[[ -f "$f" ]] && ok "$f vorhanden" || fail "$f FEHLT"
done
# Dockerfile darf keine 05_scripts/-Pfade enthalten
if grep -q "05_scripts/" Dockerfile; then
fail "Dockerfile enthält '05_scripts/' falsches Dockerfile im Release-Repo!"
else
ok "Dockerfile Pfade korrekt (kein 05_scripts/-Präfix)"
fi
# ── Schritt 3: start.sh ausführen ────────────────────────────────────────────
echo "[3/5] Führe start.sh aus ..."
chmod +x start.sh
./start.sh > /tmp/kx-bridge-start.log 2>&1 \
&& ok "start.sh erfolgreich" \
|| { fail "start.sh fehlgeschlagen (siehe /tmp/kx-bridge-start.log)"; cat /tmp/kx-bridge-start.log; exit 1; }
# Kurz warten bis Bridge hochgefahren
sleep 3
# ── Schritt 4: HTTP-Endpunkte prüfen ─────────────────────────────────────────
echo "[4/5] Prüfe HTTP-Endpunkte ..."
BASE="http://localhost:7125"
check_endpoint() {
local path="$1"
local desc="$2"
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE$path")
[[ "$http_code" == "200" ]] \
&& ok "$desc ($path$http_code)" \
|| fail "$desc ($path$http_code)"
}
check_endpoint "/" "Web-UI (index.html)"
check_endpoint "/api/state" "GET /api/state"
check_endpoint "/api/settings" "GET /api/settings"
check_endpoint "/server/info" "GET /server/info (Moonraker)"
check_endpoint "/printer/info" "GET /printer/info (Moonraker)"
check_endpoint "/printer/objects/list" "GET /printer/objects/list"
check_endpoint "/api/version" "GET /api/version (OctoPrint compat)"
# Beim ersten Start: printer_ip muss leer sein → Settings-Modal würde sich öffnen
SETTINGS=$(curl -s "$BASE/api/settings")
PRINTER_IP=$(echo "$SETTINGS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('printer_ip',''))" 2>/dev/null || echo "ERROR")
[[ -z "$PRINTER_IP" ]] \
&& ok "Erstkonfiguration erkannt: printer_ip leer → Settings-Modal öffnet sich" \
|| fail "printer_ip sollte beim Erststart leer sein, ist: '$PRINTER_IP'"
# ── Schritt 5: Container läuft stabil ────────────────────────────────────────
echo "[5/5] Prüfe Container-Stabilität ..."
sleep 2
RUNNING=$(docker-compose ps --services --filter "status=running" 2>/dev/null || true)
[[ -n "$RUNNING" ]] \
&& ok "Container läuft stabil" \
|| fail "Container ist nicht mehr aktiv"
# ── Ergebnis ──────────────────────────────────────────────────────────────────
echo ""
echo "══════════════════════════════════════"
echo " Ergebnis: $PASS bestanden, $FAIL fehlgeschlagen"
echo "══════════════════════════════════════"
[[ $FAIL -eq 0 ]] && exit 0 || exit 1

77
tests/test_moonraker.py Normal file
View File

@@ -0,0 +1,77 @@
"""
Tests für Moonraker-kompatible Endpunkte die OrcaSlicer aufruft.
"""
import pytest
@pytest.mark.asyncio
async def test_server_info(client):
c, _ = client
resp = await c.get("/server/info")
assert resp.status == 200
data = await resp.json()
assert data["result"]["klippy_state"] in ("ready", "standby", "error")
@pytest.mark.asyncio
async def test_printer_info(client):
c, _ = client
resp = await c.get("/printer/info")
assert resp.status == 200
data = await resp.json()
assert "hostname" in data["result"]
@pytest.mark.asyncio
async def test_objects_list(client):
c, _ = client
resp = await c.get("/printer/objects/list")
assert resp.status == 200
data = await resp.json()
objects = data["result"]["objects"]
# OrcaSlicer erwartet mindestens diese Objekte
for obj in ("print_stats", "heater_bed", "extruder", "display_status"):
assert obj in objects
@pytest.mark.asyncio
async def test_objects_query_print_stats(client):
c, _ = client
resp = await c.get("/printer/objects/query?print_stats")
assert resp.status == 200
data = await resp.json()
ps = data["result"]["status"]["print_stats"]
assert "state" in ps
assert "filename" in ps
assert "print_duration" in ps
@pytest.mark.asyncio
async def test_objects_query_temperatures(client):
c, _ = client
resp = await c.get("/printer/objects/query?extruder&heater_bed")
assert resp.status == 200
data = await resp.json()
status = data["result"]["status"]
assert "temperature" in status["extruder"]
assert "temperature" in status["heater_bed"]
@pytest.mark.asyncio
async def test_octoprint_version(client):
"""OrcaSlicer probt /api/version um Drucker-Typ zu erkennen."""
c, _ = client
resp = await c.get("/api/version")
assert resp.status == 200
data = await resp.json()
assert "server" in data
assert "api" in data
@pytest.mark.asyncio
async def test_files_list(client):
c, _ = client
resp = await c.get("/server/files/list")
assert resp.status == 200
data = await resp.json()
assert isinstance(data["result"], list)

82
tests/test_settings.py Normal file
View File

@@ -0,0 +1,82 @@
"""
Tests für /api/settings — Lesen und Schreiben der Verbindungseinstellungen.
"""
import pytest
import tempfile
import pathlib
@pytest.mark.asyncio
async def test_settings_get_returns_200(client):
c, _ = client
resp = await c.get("/api/settings")
assert resp.status == 200
@pytest.mark.asyncio
async def test_settings_get_schema(client):
c, _ = client
data = await (await c.get("/api/settings")).json()
for key in ("printer_ip", "mqtt_port", "username", "password", "device_id", "mode_id"):
assert key in data
@pytest.mark.asyncio
async def test_settings_get_empty_when_unconfigured(client):
"""Frische Bridge ohne Zugangsdaten → printer_ip und device_id leer."""
c, _ = client
data = await (await c.get("/api/settings")).json()
assert data["printer_ip"] == ""
assert data["device_id"] == ""
@pytest.mark.asyncio
async def test_settings_get_returns_configured_values(client_configured):
"""Bridge mit Zugangsdaten → Werte korrekt zurückgegeben."""
c, _ = client_configured
data = await (await c.get("/api/settings")).json()
assert data["printer_ip"] == "192.168.1.100"
assert data["device_id"] == "abc123deadbeef"
@pytest.mark.asyncio
async def test_settings_post_writes_env(client):
"""POST /api/settings schreibt Werte in .env-Datei."""
c, bridge = client
with tempfile.TemporaryDirectory() as tmpdir:
env_path = pathlib.Path(tmpdir) / ".env"
env_path.write_text("")
bridge._find_env_path = lambda: env_path
resp = await c.post("/api/settings", json={
"printer_ip": "10.0.0.5",
"mqtt_port": 9883,
"username": "userABCD",
"password": "secret123",
"device_id": "deadbeef01234567",
"mode_id": "20030",
})
assert resp.status == 200
content = env_path.read_text()
assert "PRINTER_IP=10.0.0.5" in content
assert "MQTT_USERNAME=userABCD" in content
assert "DEVICE_ID=deadbeef01234567" in content
@pytest.mark.asyncio
async def test_settings_post_preserves_existing_keys(client):
"""POST darf unbekannte Keys in .env nicht löschen (z.B. GITEA_TOKEN)."""
c, bridge = client
with tempfile.TemporaryDirectory() as tmpdir:
env_path = pathlib.Path(tmpdir) / ".env"
env_path.write_text("GITEA_TOKEN=mytoken\nPRINTER_IP=old\n")
bridge._find_env_path = lambda: env_path
await c.post("/api/settings", json={"printer_ip": "10.0.0.99"})
content = env_path.read_text()
assert "GITEA_TOKEN=mytoken" in content
assert "PRINTER_IP=10.0.0.99" in content

View File

@@ -550,6 +550,7 @@ function ensureAceDryCards(){
// defer until DOM ready
window.addEventListener('DOMContentLoaded',function(){
setLanguage(currentLang).catch(function(){});
_loadSpoolmanStatus();
// Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen")
fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){
if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();}