diff --git a/VERSION b/VERSION index 0dfdb9c..4aab532 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.27-nightly3 +0.9.27-nightly4 diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 38981f4..b094b3b 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -812,40 +812,6 @@ class SpoolmanClient: json={"use_length": round(use_length_mm, 2)}) -class SpoolmanClient: - """Thin synchronous HTTP client for Spoolman filament tracking. - - Designed to be called from daemon threads (poll loop, _on_print callbacks). - Uses requests (already in requirements) so no event-loop dependency. - """ - - def __init__(self, server_url: str, sync_rate: int = 0): - self.server_url = server_url.rstrip("/") - self.sync_rate = sync_rate - - def _req(self, method: str, path: str, **kwargs): - import requests - r = requests.request(method, f"{self.server_url}{path}", timeout=5, **kwargs) - r.raise_for_status() - return r.json() - - def health_check(self) -> bool: - try: - self._req("GET", "/api/v1/health") - return True - except Exception: - return False - - def list_spools(self) -> list: - return self._req("GET", "/api/v1/spool") - - def use_filament(self, spool_id: int, use_length_mm: float) -> None: - """Report consumed filament length in mm. Spoolman converts to weight - using the spool's filament profile density.""" - self._req("PUT", f"/api/v1/spool/{spool_id}/use", - json={"use_length": round(use_length_mm, 2)}) - - class KobraXBridge: def __init__(self, client: KobraXClient, args=None, store=None, printer_id: str = "1", all_bridges=None): self.client = client @@ -4558,9 +4524,10 @@ class KobraXBridge: # ─── Update ────────────────────────────────────────────────────────────── - STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1" - DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true" - GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag" + STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1" + NIGHTLY_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=5&pre-release=true" + DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true" + GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag" def _read_version(self) -> str: # PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach @@ -4635,8 +4602,14 @@ class KobraXBridge: async def handle_api_update_check(self, request): current = self._read_version() + is_nightly = "nightly" in current is_dev = "-dev+" in current - api_url = self.DEV_RELEASE_API if is_dev else self.STABLE_RELEASE_API + if is_nightly: + api_url = self.NIGHTLY_RELEASE_API + elif is_dev: + api_url = self.DEV_RELEASE_API + else: + api_url = self.STABLE_RELEASE_API try: async with aiohttp.ClientSession() as session: async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as resp: @@ -4645,14 +4618,44 @@ class KobraXBridge: releases = await resp.json(content_type=None) if not releases: return web.json_response({"error": "Keine Releases gefunden"}, status=404) - # Dev: neuestes Release mit "-dev+" im Tag suchen - if is_dev: + + if is_nightly: + # Neuestes Prerelease mit nightly-Tag suchen + nightly_releases = [r for r in releases if r.get("prerelease") and "nightly" in r.get("tag_name", "")] + if not nightly_releases: + return web.json_response({"error": "Keine Nightly-Releases gefunden"}, status=404) + data = nightly_releases[0] + tag = data.get("tag_name", "") + # Vergleich über Datum im Tag: nightly-YYYYMMDD vs aktuellem Datum in VERSION + # Aktuelles Datum aus VERSION extrahieren (Format: 0.9.27-nightlyN oder nightly-YYYYMMDD) + import re as _re + latest_date = _re.search(r"nightly-(\d{8})", tag) + current_date = _re.search(r"nightly-(\d{8})", current) + if latest_date and current_date: + update_available = latest_date.group(1) > current_date.group(1) + else: + # Fallback: Tag-Name unterschiedlich = Update verfügbar + update_available = tag != current and tag != f"nightly-{current}" + latest = tag + return web.json_response({ + "current": current, + "latest": latest, + "update_available": update_available, + "tag": tag, + "docker_only": True, + "changelog": data.get("body", ""), + }) + elif is_dev: dev_releases = [r for r in releases if "-dev+" in r.get("tag_name", "")] if not dev_releases: return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404) data = dev_releases[0] else: - data = releases[0] + # Stable: nur non-prerelease nehmen + stable_releases = [r for r in releases if not r.get("prerelease")] + if not stable_releases: + return web.json_response({"error": "Keine Stable-Releases gefunden"}, status=404) + data = stable_releases[0] tag = data.get("tag_name", "") latest = tag.lstrip("v") if is_dev: @@ -4666,6 +4669,7 @@ class KobraXBridge: "update_available": update_available, "tag": tag, "download_url": download_url, + "docker_only": False, "changelog": data.get("body", ""), }) except Exception as e: @@ -4686,6 +4690,10 @@ class KobraXBridge: async def handle_api_update_apply(self, request): data = await request.json() new_tag = data.get("tag", "") + if "nightly" in self._read_version(): + return web.json_response( + {"error": "Nightly-Updates laufen über Docker: " + "docker compose pull && docker compose up -d"}, status=400) if getattr(sys, "frozen", False): return web.json_response( {"error": "Self-Update wird im Binary-Modus nicht unterstützt – " diff --git a/web/themes/default/app.js b/web/themes/default/app.js index 3e8d6d4..082bb37 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -1661,21 +1661,33 @@ function saveSettings(){ clog('Settings-Fehler: '+e,'msg-err'); }); } +var _updateDockerOnly=false; function checkUpdate(){ var sb=document.getElementById('update-status'); sb.textContent=T.update_checking; document.getElementById('btn-update-apply').style.display='none'; - _updateTag='';_updateUrl=''; + _updateTag='';_updateUrl='';_updateDockerOnly=false; fetch(_apiUrl('/api/update/check')).then(function(r){return r.json()}).then(function(d){ if(d.error){sb.textContent=T.update_error+': '+d.error;return;} var cl=document.getElementById('update-changelog'); - if(d.changelog&&d.changelog.trim()){cl.textContent=d.changelog;cl.style.display='block';} - else{cl.style.display='none';} + if(d.changelog&&d.changelog.trim()){ + // Changelog als Markdown-Text (pre-formatiert) anzeigen + cl.textContent=d.changelog;cl.style.display='block'; + } else {cl.style.display='none';} + _updateDockerOnly=!!d.docker_only; if(d.update_available){ - sb.textContent='v'+d.latest+' '+T.update_available; + sb.textContent=d.latest+' '+T.update_available; sb.style.color='var(--ok)'; - _updateTag=d.tag;_updateUrl=d.download_url; - document.getElementById('btn-update-apply').style.display='inline-block'; + _updateTag=d.tag;_updateUrl=d.download_url||''; + var btn=document.getElementById('btn-update-apply'); + if(_updateDockerOnly){ + btn.textContent=T.update_docker||'docker compose pull'; + btn.title='docker compose pull && docker compose up -d'; + } else { + btn.textContent=T.update_apply; + btn.title=''; + } + btn.style.display='inline-block'; } else { sb.textContent=T.update_none; sb.style.color='var(--txt2)'; @@ -1683,9 +1695,21 @@ function checkUpdate(){ }).catch(function(e){sb.textContent=T.update_error+': '+e;}); } function applyUpdate(){ - if(!_updateUrl)return; var sb=document.getElementById('update-status'); var btn=document.getElementById('btn-update-apply'); + if(_updateDockerOnly){ + // Nightly: kein Self-Update, Docker-Befehl in Zwischenablage kopieren + var cmd='docker compose pull && docker compose up -d'; + if(navigator.clipboard){ + navigator.clipboard.writeText(cmd).then(function(){ + sb.textContent=T.update_docker_copied||'Befehl kopiert: '+cmd; + }); + } else { + sb.textContent=cmd; + } + return; + } + if(!_updateUrl)return; btn.disabled=true;sb.textContent=T.update_applying; post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){ sb.textContent=T.update_restarting; diff --git a/web/translations/de.json b/web/translations/de.json index a8a8f66..89815b4 100644 --- a/web/translations/de.json +++ b/web/translations/de.json @@ -321,6 +321,8 @@ "update_available": "verfügbar", "update_check": "Auf Updates prüfen", "update_checking": "Prüfe...", + "update_docker": "Befehl kopieren", + "update_docker_copied": "Kopiert! Ausführen: docker compose pull && docker compose up -d", "update_error": "Fehler", "update_none": "Bereits aktuell", "update_restarting": "Starte neu..." diff --git a/web/translations/en.json b/web/translations/en.json index b08bf41..6b28894 100644 --- a/web/translations/en.json +++ b/web/translations/en.json @@ -321,6 +321,8 @@ "update_available": "available", "update_check": "Check for Updates", "update_checking": "Checking...", + "update_docker": "Copy Command", + "update_docker_copied": "Copied! Run: docker compose pull && docker compose up -d", "update_error": "Error", "update_none": "Already up to date", "update_restarting": "Restarting..." diff --git a/web/translations/es.json b/web/translations/es.json index d418799..1782c68 100644 --- a/web/translations/es.json +++ b/web/translations/es.json @@ -321,6 +321,8 @@ "update_available": "disponible", "update_check": "Buscar actualizaciones", "update_checking": "Comprobando...", + "update_docker": "Copiar comando", + "update_docker_copied": "Copiado. Ejecutar: docker compose pull && docker compose up -d", "update_error": "Error", "update_none": "Ya actualizado", "update_restarting": "Reiniciando..." diff --git a/web/translations/fr.json b/web/translations/fr.json index 863ba44..5f3cd3f 100644 --- a/web/translations/fr.json +++ b/web/translations/fr.json @@ -321,6 +321,8 @@ "update_available": "disponible", "update_check": "Vérifier les mises à jour", "update_checking": "Vérification…", + "update_docker": "Copier la commande", + "update_docker_copied": "Copié ! Exécuter : docker compose pull && docker compose up -d", "update_error": "Erreur", "update_none": "Déjà à jour", "update_restarting": "Redémarrage…" diff --git a/web/translations/it.json b/web/translations/it.json index 4c50faa..b06d4a1 100644 --- a/web/translations/it.json +++ b/web/translations/it.json @@ -321,6 +321,8 @@ "update_available": "disponibile", "update_check": "Controlla aggiornamenti", "update_checking": "Verifica in corso...", + "update_docker": "Copia comando", + "update_docker_copied": "Copiato! Eseguire: docker compose pull && docker compose up -d", "update_error": "Errore", "update_none": "Già aggiornato", "update_restarting": "Riavvio in corso..." diff --git a/web/translations/zh-cn.json b/web/translations/zh-cn.json index 7a29773..0df7ce4 100644 --- a/web/translations/zh-cn.json +++ b/web/translations/zh-cn.json @@ -321,6 +321,8 @@ "update_available": "可用", "update_check": "检查更新", "update_checking": "检查中...", + "update_docker": "复制命令", + "update_docker_copied": "已复制!执行:docker compose pull && docker compose up -d", "update_error": "错误", "update_none": "已是最新版本", "update_restarting": "重启中..."