QUELLCODE / JS
app.js
src/hardware_box/web/app.js
1/*2OpenAss: nur privat und experimentell. Bau, Download und Nutzung auf eigene Verantwortung.3Niemals Unternehmensrechner, Produktivsysteme, Industrieanlagen oder sicherheitskritische Systeme anschließen oder bedienen.4Keine freiwillige Funktions-/Sicherheitsgarantie; zwingende gesetzliche Ansprüche bleiben unberührt. Siehe DISCLAIMER.txt.5*/6"use strict";7let token = "", lastStatus = null, imageUrl = null, connected = false, screenshotHash = "";8const $ = id => document.getElementById(id);9async function api(path, body) {10 const response = await fetch(`/api/${path}`, {method:body===undefined?"GET":"POST",cache:"no-store",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},...(body===undefined?{}:{body:JSON.stringify(body)})});11 if(!response.ok){const error=await response.json().catch(()=>({detail:"Verbindungsfehler"}));throw new Error(typeof error.detail==="string"?error.detail:"Ungültige Eingabe");}12 return response.json();13}14async function showScreenshot(){15 const response=await fetch("/api/screenshot",{cache:"no-store",headers:{Authorization:`Bearer ${token}`}});16 if(!response.ok)throw new Error("Kein aktueller Screenshot verfügbar");17 const hash=response.headers.get("X-Screenshot-SHA256");18 const next=URL.createObjectURL(await response.blob());19 await new Promise((resolve,reject)=>{const img=new Image();img.onload=resolve;img.onerror=reject;img.src=next;});20 $("screen").src=next;screenshotHash=hash;21 if(imageUrl)URL.revokeObjectURL(imageUrl);imageUrl=next;22}23function render(status){24 lastStatus=status;$("state").textContent=status.state;$("mode").textContent=status.demo?"DEMO · keine echten Eingaben":"ZIELRECHNER · Freigaben aktiv";25 $("metrics").textContent=`${status.actions} Aktionen · ${status.api_calls} API-Aufrufe · ${status.total_tokens} Tokens`;26 $("error").textContent=status.error||"";$("action").textContent=status.action?JSON.stringify(status.action,null,2):"Warte auf nächste Aktion";27 $("approval").hidden=!status.pending;$("pending").textContent=status.pending?JSON.stringify(status.pending.action,null,2):"";28 $("approve").disabled=!status.pending||screenshotHash!==status.pending.screenshot_sha256;29 $("review").hidden=status.state!=="RESULT_REVIEW";$("result").textContent=status.message||"";30 $("start").disabled=["RUNNING","WAITING_CONFIRMATION"].includes(status.state)||status.restart_required;31}32async function poll(){33 if(!connected)return;34 try{await showScreenshot();const [status,logs]=await Promise.all([api("status"),api("logs")]);render(status);$("logs").textContent=logs.slice(-25).map(x=>JSON.stringify(x)).join("\n");}35 catch(error){$("error").textContent=error.message;$("approve").disabled=true;}36 setTimeout(poll,900);37}38function run(action){return async()=>{try{await action();}catch(error){$("error").textContent=error.message;}};}39$("connect").onclick=run(async()=>{token=$("token").value;const status=await api("status");const settings=await api("settings");for(const field of $("settings").elements){if(field.name&&settings[field.name]!==undefined)field.value=Array.isArray(settings[field.name])?settings[field.name].map(combo=>combo.join("+")).join(", "):settings[field.name];}$("token").value="";$("login").hidden=true;$("workspace").hidden=false;render(status);if(!connected){connected=true;poll();}});40$("start").onclick=run(async()=>{await api("start",{task:$("task").value});});41$("pause").onclick=run(()=>api("pause",{}));$("stop").onclick=run(()=>api("stop",{}));42async function confirm(approved){const pending=lastStatus?.pending;if(!pending)throw new Error("Keine offene Freigabe");if(approved&&screenshotHash!==pending.screenshot_sha256)throw new Error("Das Freigabebild wurde noch nicht geladen");$("approve").disabled=true;await api("confirm",{nonce:pending.nonce,screenshot_sha256:pending.screenshot_sha256,approved});}43$("approve").onclick=run(()=>confirm(true));$("reject").onclick=run(()=>confirm(false));44$("verified").onclick=run(()=>api("review",{verified:true}));45$("check").onclick=run(async()=>{$("checkresult").textContent=JSON.stringify(await api("check",{}));});46$("manualsend").onclick=run(async()=>{await api("manual",JSON.parse($("manual").value));});47$("settings").onsubmit=event=>{event.preventDefault();run(async()=>{const changes={};for(const field of $("settings").elements){if(field.name)changes[field.name]=["extra_denied_keypresses","allowed_keypresses"].includes(field.name)?field.value.split(",").map(x=>x.trim()).filter(Boolean).map(x=>x.split("+").map(k=>k.trim())):field.name==="keyboard_layout"?field.value:Number(field.value);}await api("settings",changes);$("settingsresult").textContent="Gespeichert. Dienst neu starten und Zielrechner neu kalibrieren.";})()};