nightly: 0.9.27-nightly4

This commit is contained in:
2026-06-25 23:01:34 +02:00
parent 38d98666c4
commit ac309d5d3d
9 changed files with 93 additions and 49 deletions

View File

@ -1 +1 @@
0.9.27-nightly3 0.9.27-nightly4

View File

@ -812,40 +812,6 @@ class SpoolmanClient:
json={"use_length": round(use_length_mm, 2)}) 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: class KobraXBridge:
def __init__(self, client: KobraXClient, args=None, store=None, printer_id: str = "1", all_bridges=None): def __init__(self, client: KobraXClient, args=None, store=None, printer_id: str = "1", all_bridges=None):
self.client = client self.client = client
@ -4558,9 +4524,10 @@ class KobraXBridge:
# ─── Update ────────────────────────────────────────────────────────────── # ─── Update ──────────────────────────────────────────────────────────────
STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1" 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" NIGHTLY_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=5&pre-release=true"
GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag" 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: def _read_version(self) -> str:
# PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach # PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach
@ -4635,8 +4602,14 @@ class KobraXBridge:
async def handle_api_update_check(self, request): async def handle_api_update_check(self, request):
current = self._read_version() current = self._read_version()
is_nightly = "nightly" in current
is_dev = "-dev+" 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: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as resp: 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) releases = await resp.json(content_type=None)
if not releases: if not releases:
return web.json_response({"error": "Keine Releases gefunden"}, status=404) 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", "")] dev_releases = [r for r in releases if "-dev+" in r.get("tag_name", "")]
if not dev_releases: if not dev_releases:
return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404) return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404)
data = dev_releases[0] data = dev_releases[0]
else: 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", "") tag = data.get("tag_name", "")
latest = tag.lstrip("v") latest = tag.lstrip("v")
if is_dev: if is_dev:
@ -4666,6 +4669,7 @@ class KobraXBridge:
"update_available": update_available, "update_available": update_available,
"tag": tag, "tag": tag,
"download_url": download_url, "download_url": download_url,
"docker_only": False,
"changelog": data.get("body", ""), "changelog": data.get("body", ""),
}) })
except Exception as e: except Exception as e:
@ -4686,6 +4690,10 @@ class KobraXBridge:
async def handle_api_update_apply(self, request): async def handle_api_update_apply(self, request):
data = await request.json() data = await request.json()
new_tag = data.get("tag", "") 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): if getattr(sys, "frozen", False):
return web.json_response( return web.json_response(
{"error": "Self-Update wird im Binary-Modus nicht unterstützt " {"error": "Self-Update wird im Binary-Modus nicht unterstützt "

View File

@ -1661,21 +1661,33 @@ function saveSettings(){
clog('Settings-Fehler: '+e,'msg-err'); clog('Settings-Fehler: '+e,'msg-err');
}); });
} }
var _updateDockerOnly=false;
function checkUpdate(){ function checkUpdate(){
var sb=document.getElementById('update-status'); var sb=document.getElementById('update-status');
sb.textContent=T.update_checking; sb.textContent=T.update_checking;
document.getElementById('btn-update-apply').style.display='none'; 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){ 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;} if(d.error){sb.textContent=T.update_error+': '+d.error;return;}
var cl=document.getElementById('update-changelog'); var cl=document.getElementById('update-changelog');
if(d.changelog&&d.changelog.trim()){cl.textContent=d.changelog;cl.style.display='block';} if(d.changelog&&d.changelog.trim()){
else{cl.style.display='none';} // 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){ if(d.update_available){
sb.textContent='v'+d.latest+' '+T.update_available; sb.textContent=d.latest+' '+T.update_available;
sb.style.color='var(--ok)'; sb.style.color='var(--ok)';
_updateTag=d.tag;_updateUrl=d.download_url; _updateTag=d.tag;_updateUrl=d.download_url||'';
document.getElementById('btn-update-apply').style.display='inline-block'; 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 { } else {
sb.textContent=T.update_none; sb.textContent=T.update_none;
sb.style.color='var(--txt2)'; sb.style.color='var(--txt2)';
@ -1683,9 +1695,21 @@ function checkUpdate(){
}).catch(function(e){sb.textContent=T.update_error+': '+e;}); }).catch(function(e){sb.textContent=T.update_error+': '+e;});
} }
function applyUpdate(){ function applyUpdate(){
if(!_updateUrl)return;
var sb=document.getElementById('update-status'); var sb=document.getElementById('update-status');
var btn=document.getElementById('btn-update-apply'); 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; btn.disabled=true;sb.textContent=T.update_applying;
post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){ post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){
sb.textContent=T.update_restarting; sb.textContent=T.update_restarting;

View File

@ -321,6 +321,8 @@
"update_available": "verfügbar", "update_available": "verfügbar",
"update_check": "Auf Updates prüfen", "update_check": "Auf Updates prüfen",
"update_checking": "Prüfe...", "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_error": "Fehler",
"update_none": "Bereits aktuell", "update_none": "Bereits aktuell",
"update_restarting": "Starte neu..." "update_restarting": "Starte neu..."

View File

@ -321,6 +321,8 @@
"update_available": "available", "update_available": "available",
"update_check": "Check for Updates", "update_check": "Check for Updates",
"update_checking": "Checking...", "update_checking": "Checking...",
"update_docker": "Copy Command",
"update_docker_copied": "Copied! Run: docker compose pull && docker compose up -d",
"update_error": "Error", "update_error": "Error",
"update_none": "Already up to date", "update_none": "Already up to date",
"update_restarting": "Restarting..." "update_restarting": "Restarting..."

View File

@ -321,6 +321,8 @@
"update_available": "disponible", "update_available": "disponible",
"update_check": "Buscar actualizaciones", "update_check": "Buscar actualizaciones",
"update_checking": "Comprobando...", "update_checking": "Comprobando...",
"update_docker": "Copiar comando",
"update_docker_copied": "Copiado. Ejecutar: docker compose pull && docker compose up -d",
"update_error": "Error", "update_error": "Error",
"update_none": "Ya actualizado", "update_none": "Ya actualizado",
"update_restarting": "Reiniciando..." "update_restarting": "Reiniciando..."

View File

@ -321,6 +321,8 @@
"update_available": "disponible", "update_available": "disponible",
"update_check": "Vérifier les mises à jour", "update_check": "Vérifier les mises à jour",
"update_checking": "Vérification…", "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_error": "Erreur",
"update_none": "Déjà à jour", "update_none": "Déjà à jour",
"update_restarting": "Redémarrage…" "update_restarting": "Redémarrage…"

View File

@ -321,6 +321,8 @@
"update_available": "disponibile", "update_available": "disponibile",
"update_check": "Controlla aggiornamenti", "update_check": "Controlla aggiornamenti",
"update_checking": "Verifica in corso...", "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_error": "Errore",
"update_none": "Già aggiornato", "update_none": "Già aggiornato",
"update_restarting": "Riavvio in corso..." "update_restarting": "Riavvio in corso..."

View File

@ -321,6 +321,8 @@
"update_available": "可用", "update_available": "可用",
"update_check": "检查更新", "update_check": "检查更新",
"update_checking": "检查中...", "update_checking": "检查中...",
"update_docker": "复制命令",
"update_docker_copied": "已复制执行docker compose pull && docker compose up -d",
"update_error": "错误", "update_error": "错误",
"update_none": "已是最新版本", "update_none": "已是最新版本",
"update_restarting": "重启中..." "update_restarting": "重启中..."