Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b4ba567ab |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
.env
|
||||
config/config.ini
|
||||
.dev
|
||||
__pycache__/
|
||||
*.pyc
|
||||
build/
|
||||
@@ -7,5 +9,3 @@ dist/
|
||||
releases/*/kx-bridge
|
||||
releases/*/extract_credentials
|
||||
releases/*/extract_credentials.exe
|
||||
|
||||
!kx-bridge.spec
|
||||
|
||||
@@ -1,43 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.15] – 2026-05-21
|
||||
|
||||
### Fixes (Issue #29)
|
||||
- **UI im OrcaSlicer-Device-Tab kaputt:** OrcaSlicers eingebetteter Webview lädt
|
||||
nur das nackte HTML und ignoriert externe `<script>`/`<link>`-Tags — nach der
|
||||
v0.9.14-Theme-Auslagerung funktionierte dort kein Button mehr. Die Bridge
|
||||
bettet CSS + JS jetzt inline in die Seite ein — funktioniert in Browser UND
|
||||
OrcaSlicer-Webview.
|
||||
- **Dropdowns unlesbar (weiß auf weiß) im OrcaSlicer-Webview:** `color-scheme` +
|
||||
explizite `select`/`option`-Farben ergänzt, damit die nativen Dropdowns in
|
||||
Hell- und Dunkel-Theme korrekt dargestellt werden.
|
||||
- **„Select slots"-Button tat direkt nach Upload nichts:** eine fehlende
|
||||
Variablen-Deklaration (`storeFiles`) warf einen `ReferenceError`, wenn vor dem
|
||||
Laden des Browser-Tabs geklickt wurde. Behoben.
|
||||
- **Upload-Banner kam nach abgeschlossenem Druck zurück:** der „file ready"-Status
|
||||
wurde nur bei Stop/Abbruch geleert, nicht bei `finished`. Jetzt auch nach
|
||||
erfolgreichem Druckende geleert.
|
||||
|
||||
## [0.9.14] – 2026-05-21
|
||||
|
||||
### Neu
|
||||
- **Theme-System (Community-Beitrag von @hirnwunde, PR #27):** Die Web-UI liegt
|
||||
jetzt in echten Dateien unter `web/themes/<name>/` (`index.html` + `style.css`
|
||||
+ `app.js`) statt im Python-Quelltext eingebettet. Theme umschalten mit
|
||||
`--ui-theme <name>`. Für Theme-Autoren gibt es eine dokumentierte Hook-Referenz
|
||||
(`web/DOC/THEME-CSS-HOOKS.md`, `THEME-JS-ID-HOOKS.md`). Das Default-Theme
|
||||
enthält die komplette aktuelle UI (ACE2, Objekte überspringen, Filament-Dialog).
|
||||
Für Nutzer keine Änderung — Binaries/Docker-Image liefern das Theme eingebettet.
|
||||
- **Neustart über API (Community-Beitrag von @gangoke, PR #28):** neuer Endpoint
|
||||
`POST /api/restart`, um die Bridge per API neu zu starten — z. B. für einen
|
||||
Neustart-Button in der Home-Assistant-Integration.
|
||||
|
||||
### Intern
|
||||
- Vereinheitlichter PyInstaller-Build (`kx-bridge.spec`) für Linux, Windows und
|
||||
Docker — bindet `web/` (Themes) ins Onefile-Binary ein, zur Laufzeit aus
|
||||
`sys._MEIPASS` gelesen. Theme-Einbettung in Linux-Binary und Windows-EXE verifiziert.
|
||||
- `data/` in `.gitignore` aufgenommen.
|
||||
|
||||
## [0.9.13] – 2026-05-20
|
||||
|
||||
============================================================
|
||||
|
||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -1,42 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [0.9.15] – 2026-05-21
|
||||
|
||||
### Fixes (Issue #29)
|
||||
- **UI in the OrcaSlicer device tab was broken:** OrcaSlicer's embedded webview
|
||||
only loads the bare HTML and ignores external `<script>`/`<link>` tags, so after
|
||||
the v0.9.14 theme split none of the buttons worked in the device tab. The
|
||||
bridge now inlines CSS + JS into the page — works in both the browser and the
|
||||
OrcaSlicer webview.
|
||||
- **Dropdowns unreadable (white-on-white) in the OrcaSlicer webview:** added
|
||||
`color-scheme` + explicit `select`/`option` colors so the native dropdowns
|
||||
render correctly in dark and light theme.
|
||||
- **"Select slots" button did nothing right after an upload:** a missing variable
|
||||
declaration (`storeFiles`) threw a `ReferenceError` when clicked before the
|
||||
Browser tab had loaded. Fixed.
|
||||
- **Upload banner came back after a finished print:** the "file ready" state was
|
||||
only cleared on stop/cancel, not on `finished`. Now cleared on completion too.
|
||||
|
||||
## [0.9.14] – 2026-05-21
|
||||
|
||||
### New
|
||||
- **Theme system (community contribution by @hirnwunde, PR #27):** the web UI now
|
||||
lives in real files under `web/themes/<name>/` (`index.html` + `style.css` +
|
||||
`app.js`) instead of being embedded in the Python source. Switch themes with
|
||||
`--ui-theme <name>`. Theme authors get a documented hook reference
|
||||
(`web/DOC/THEME-CSS-HOOKS.md`, `THEME-JS-ID-HOOKS.md`). The default theme
|
||||
carries the full current UI (ACE2, skip objects, filament dialog). No change
|
||||
for users — the bundled binaries/Docker image ship the theme embedded.
|
||||
- **Restart over API (community contribution by @gangoke, PR #28):** new
|
||||
`POST /api/restart` endpoint to restart the bridge remotely — e.g. a restart
|
||||
button in the Home Assistant integration.
|
||||
|
||||
### Internal
|
||||
- Unified PyInstaller build (`kx-bridge.spec`) for Linux, Windows and Docker —
|
||||
embeds `web/` (themes) into the one-file binary, read at runtime from
|
||||
`sys._MEIPASS`. Verified the theme ships in the Linux binary and the Windows EXE.
|
||||
- `data/` added to `.gitignore`.
|
||||
|
||||
## [0.9.13] – 2026-05-20
|
||||
|
||||
============================================================
|
||||
|
||||
@@ -6,7 +6,7 @@ COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY kobrax_moonraker_bridge.py .
|
||||
COPY web/ ./web/
|
||||
COPY _web_assets.py .
|
||||
COPY config_loader.py .
|
||||
COPY env_loader.py .
|
||||
COPY kobrax_client.py .
|
||||
|
||||
2991
_web_assets.py
Normal file
2991
_web_assets.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -49,10 +49,8 @@ import html
|
||||
# Bei PyInstaller-Binary liegt alles neben sys.executable, sonst neben __file__
|
||||
_BASE = os.path.dirname(sys.executable) if getattr(sys, "frozen", False) else os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, _BASE)
|
||||
# Read-Only Web-Assets (Themes) werden im Onefile-Binary via --add-data unter
|
||||
# sys._MEIPASS entpackt; im Script-/Docker-Modus liegen sie neben dieser Datei.
|
||||
_WEB_BASE = getattr(sys, "_MEIPASS", _BASE)
|
||||
from kobrax_client import KobraXClient
|
||||
from _web_assets import INDEX_HTML
|
||||
|
||||
|
||||
try:
|
||||
@@ -687,11 +685,6 @@ class KobraXBridge:
|
||||
log.info(f"Job abgebrochen: {self._current_job_id}")
|
||||
self._current_job_id = ""
|
||||
|
||||
# Nach Druckende das Upload-Banner verschwinden lassen (Issue #29): der
|
||||
# Drucker meldet "finished" nach erfolgreichem Druck — file_ready wurde
|
||||
# bisher nur bei stoped/canceled geleert, dadurch kam das Banner zurück.
|
||||
if kobra_state == "finished":
|
||||
self._state["file_ready"] = ""
|
||||
if kobra_state in ("stoped", "canceled"):
|
||||
self._state["progress"] = 0.0
|
||||
self._state["filename"] = ""
|
||||
@@ -733,10 +726,6 @@ class KobraXBridge:
|
||||
if kobra_state:
|
||||
self._state["print_state"] = KOBRA_TO_KLIPPER_STATE.get(kobra_state, "standby")
|
||||
self._state["kobra_state"] = kobra_state
|
||||
# Upload-Banner nach Druckende ausblenden (Issue #29) – der State kommt
|
||||
# je nach Drucker auch über info/report (project.state), nicht nur print/report.
|
||||
if kobra_state in ("finished", "stoped", "canceled"):
|
||||
self._state["file_ready"] = ""
|
||||
if project:
|
||||
if "filename" in project:
|
||||
self._state["filename"] = project["filename"]
|
||||
@@ -1897,7 +1886,7 @@ class KobraXBridge:
|
||||
log.warning("Druckstart: keine Antwort vom Drucker")
|
||||
|
||||
def _theme_index_path(self) -> str:
|
||||
return os.path.join(_WEB_BASE, "web", "themes", self._ui_theme, "index.html")
|
||||
return os.path.join(_BASE, "web", "themes", self._ui_theme, "index.html")
|
||||
|
||||
def _load_index_template_cached(self) -> str:
|
||||
path = self._theme_index_path()
|
||||
@@ -1911,7 +1900,7 @@ class KobraXBridge:
|
||||
return self._index_tpl_cache
|
||||
|
||||
def _ui_asset_cache_buster(self) -> str:
|
||||
base = os.path.join(_WEB_BASE, "web", "themes", self._ui_theme)
|
||||
base = os.path.join(_BASE, "web", "themes", self._ui_theme)
|
||||
mt = 0.0
|
||||
for fn in ("index.html", "style.css", "app.js"):
|
||||
try:
|
||||
@@ -2032,7 +2021,7 @@ class KobraXBridge:
|
||||
ctype = _KX_UI_ASSETS.get(name)
|
||||
if ctype is None:
|
||||
raise web.HTTPNotFound()
|
||||
path = os.path.join(_WEB_BASE, "web", "themes", self._ui_theme, name)
|
||||
path = os.path.join(_BASE, "web", "themes", self._ui_theme, name)
|
||||
try:
|
||||
raw = pathlib.Path(path).read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
@@ -2058,30 +2047,9 @@ class KobraXBridge:
|
||||
status=500,
|
||||
content_type="text/html; charset=utf-8",
|
||||
)
|
||||
page = tpl.replace("__UI_ASSETS_VER__", self._ui_asset_cache_buster())
|
||||
|
||||
# CSS + JS INLINE einbetten statt nur zu verlinken. OrcaSlicers
|
||||
# eingebetteter Device-Tab-Webview lädt externe <link>/<script src>
|
||||
# NICHT (nur das nackte HTML) → ohne Inlining funktioniert dort kein
|
||||
# einziger Button (Issue #29). Im normalen Browser ist es ebenso korrekt.
|
||||
base = os.path.join(_WEB_BASE, "web", "themes", self._ui_theme)
|
||||
try:
|
||||
css = pathlib.Path(os.path.join(base, "style.css")).read_text(encoding="utf-8")
|
||||
page = page.replace(
|
||||
'<link rel="stylesheet" href="/kx/ui/style.css">',
|
||||
"<style>\n" + css + "\n</style>")
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
js = pathlib.Path(os.path.join(base, "app.js")).read_text(encoding="utf-8")
|
||||
js = js.replace("'__VERSION__'", f"'{self._read_version()}'")
|
||||
page = page.replace(
|
||||
'<script src="/kx/ui/app.js"></script>',
|
||||
"<script>\n" + js + "\n</script>")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return web.Response(text=page, content_type="text/html",
|
||||
html = tpl.replace("__UI_ASSETS_VER__", self._ui_asset_cache_buster())
|
||||
|
||||
return web.Response(text=html, content_type="text/html",
|
||||
headers={"Cache-Control": "no-store, no-cache, must-revalidate"})
|
||||
|
||||
async def handle_api_light(self, request):
|
||||
@@ -2136,12 +2104,6 @@ class KobraXBridge:
|
||||
log.info("Manuell getrennt")
|
||||
return web.json_response({"result": "disconnected"})
|
||||
|
||||
async def handle_api_restart(self, request):
|
||||
log.info("Neustart über API angefordert")
|
||||
response = web.json_response({"status": "restarting"})
|
||||
asyncio.get_event_loop().call_later(0.3, self._restart_bridge)
|
||||
return response
|
||||
|
||||
async def handle_api_speed(self, request):
|
||||
try:
|
||||
body = await request.json()
|
||||
@@ -2986,13 +2948,12 @@ class KobraXBridge:
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=502)
|
||||
|
||||
# Bridge-Python-Module, die das Self-Update mitziehen muss. Wird nur die
|
||||
# Hauptdatei ersetzt, crasht die neue Version ggf. mit ModuleNotFoundError.
|
||||
# Hinweis: das Frontend liegt seit dem Theme-System unter web/themes/<name>/
|
||||
# (keine flache .py mehr); Theme-Dateien werden vom Self-Update derzeit NICHT
|
||||
# mitgeladen – Theme-Änderungen kommen über Docker-Image/Binary-Update.
|
||||
# Bridge-Python-Module, die das Self-Update mitziehen muss. Die Hauptdatei
|
||||
# importiert _web_assets (gebündeltes Frontend) etc. – wird nur die Hauptdatei
|
||||
# ersetzt, crasht die neue Version mit ModuleNotFoundError. Daher alle laden.
|
||||
_UPDATE_FILES = [
|
||||
"kobrax_moonraker_bridge.py",
|
||||
"_web_assets.py",
|
||||
"kobrax_client.py",
|
||||
"config_loader.py",
|
||||
"env_loader.py",
|
||||
@@ -3323,7 +3284,6 @@ def build_app(bridge: KobraXBridge) -> web.Application:
|
||||
r.add_post("/api/fan", bridge.handle_api_fan)
|
||||
r.add_post("/api/connect", bridge.handle_api_connect)
|
||||
r.add_post("/api/disconnect", bridge.handle_api_disconnect)
|
||||
r.add_post("/api/restart", bridge.handle_api_restart)
|
||||
r.add_post("/api/speed", bridge.handle_api_speed)
|
||||
r.add_post("/api/ams/feed", bridge.handle_api_ams_feed)
|
||||
r.add_post("/api/ams/set_slot", bridge.handle_api_ams_set_slot)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# PyInstaller-Spec für kx-bridge — plattformneutral (Linux + Windows via PyBuilder).
|
||||
# Wird relativ zum Repo-Root ausgeführt (`pyinstaller kx-bridge.spec`), wo
|
||||
# kobrax_moonraker_bridge.py und web/ flach liegen (Release-Repo-Layout).
|
||||
#
|
||||
# Bindet das Web-Theme-System (web/themes/<name>/ + web/DOC/) ins Onefile-Binary
|
||||
# ein → zur Laufzeit über sys._MEIPASS lesbar (_WEB_BASE in der Bridge).
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas = [("web", "web")]
|
||||
binaries = []
|
||||
hiddenimports = []
|
||||
|
||||
# pycryptodome vollständig einsammeln (Krypto für die Drucker-Auth)
|
||||
_d, _b, _h = collect_all("pycryptodome")
|
||||
datas += _d
|
||||
binaries += _b
|
||||
hiddenimports += _h
|
||||
|
||||
a = Analysis(
|
||||
["kobrax_moonraker_bridge.py"],
|
||||
pathex=[],
|
||||
binaries=binaries,
|
||||
datas=datas,
|
||||
hiddenimports=hiddenimports,
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name="kx-bridge",
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
console=True,
|
||||
onefile=True,
|
||||
)
|
||||
@@ -1577,10 +1577,6 @@ function formatDur(sec){
|
||||
var _storeFileId=null;
|
||||
var _storeFilename=null;
|
||||
var _filamentDialogMode='store'; // 'store' oder 'banner'
|
||||
// GCode-Store-Dateiliste. MUSS deklariert sein – sonst ReferenceError, wenn
|
||||
// "Slots wählen" im Banner geklickt wird, bevor der Browser-Tab je geladen
|
||||
// wurde (Issue #29 / Theme-Auslagerung PR #27).
|
||||
var storeFiles=[];
|
||||
|
||||
var _gcodeFilaments=[];
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
:root{
|
||||
color-scheme:dark; /* native Form-Controls (select) im Webview dunkel rendern */
|
||||
--bg:#1a1a1f;--card:#24242c;--raised:#2e2e3a;--border:#3a3a4a;
|
||||
--txt:#f0f0f5;--txt2:#8888aa;--accent:#00c8ff;--accent2:#ff6b35;
|
||||
--ok:#4cde80;--err:#ff4d6d;--warn:#ffb020;
|
||||
@@ -7,17 +6,12 @@
|
||||
--mono:"JetBrains Mono","Fira Code",monospace;
|
||||
}
|
||||
[data-theme=light]{
|
||||
color-scheme:light;
|
||||
--bg:#f0f0f5;--card:#fff;--raised:#e8e8f0;--border:#d0d0e0;
|
||||
--txt:#1a1a2e;--txt2:#666680;
|
||||
}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{background:var(--bg);color:var(--txt);font-family:var(--font);font-size:14px;min-height:100vh;display:flex;flex-direction:column}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
/* select/option-Farben explizit setzen — OrcaSlicers Device-Tab-Webview erbt
|
||||
sie sonst nicht und rendert weiße Schrift auf weißem Grund (Issue #29). */
|
||||
select{background:var(--raised)!important;color:var(--txt)!important}
|
||||
select option{background:var(--card)!important;color:var(--txt)!important}
|
||||
|
||||
/* ── HEADER ── */
|
||||
header{background:var(--card);border-bottom:1px solid var(--border);
|
||||
|
||||
Reference in New Issue
Block a user