const $=id=>document.getElementById(id);function esc(s){return String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]))}function msg(t,e=false){$('msg').textContent=t;$('msg').className='msg'+(e?' err':'');$('msg').classList.remove('hidden');setTimeout(()=>$('msg').classList.add('hidden'),6500)}async function api(p,o={}){if(o.body&&typeof o.body!=='string'){o.headers={'Content-Type':'application/json',...(o.headers||{})};o.body=JSON.stringify(o.body)}const r=await fetch(p,{credentials:'same-origin',...o});const x=await r.json().catch(()=>({}));if(!r.ok){const e=new Error(x.error||`HTTP ${r.status}`);e.status=r.status;throw e}return x}function workerHTML(w){return `
${esc(w.worker_id||'—')}${esc(w.name||w.id)}
${esc(w.image)} · ${esc(w.status||w.state)}
${w.running?'RUNNING':'STOPPED'}
`}function bind(){document.querySelectorAll('.start').forEach(b=>b.onclick=()=>action(b,'start'));document.querySelectorAll('.stop').forEach(b=>b.onclick=()=>action(b,'stop'));document.querySelectorAll('.restart').forEach(b=>b.onclick=()=>action(b,'restart'))}async function action(b,a){b.disabled=true;try{await api(`/api/admin/workers/${encodeURIComponent(b.dataset.id)}/${a}`,{method:'POST'});msg(`Worker ${a}`);await load()}catch(e){msg(e.message,true)}finally{b.disabled=false}}async function load(){const x=await api('/api/admin/overview');$('loginBox').classList.add('hidden');$('panel').classList.remove('hidden');$('controllerName').textContent=x.controller?.name||x.controller?.id||'Service Controller';const mc=x.controller?.master_connected?'MASTER ONLINE':'MASTER OFFLINE';const ma=x.controller?.master_last_attempt?new Date(x.controller.master_last_attempt).toLocaleString('de-DE'):'';const me=x.controller?.master_last_error?` · ${x.controller.master_last_error}`:'';$('controllerMeta').textContent=`${x.controller?.id||''} · ${x.controller?.advertise_url||''} · Master ${x.controller?.master_url||''} · ${mc}${ma?' · '+ma:''}${me}`;$('running').textContent=x.running||0;$('maxRunning').textContent=x.controller?.max_running||0;$('total').textContent=x.total||0;$('maxWorkers').textContent=x.controller?.max_workers||0;$('workers').innerHTML=(x.workers||[]).map(workerHTML).join('')||'
Keine verwalteten Worker auf diesem Host.
';bind()}$('login').onclick=async()=>{try{await api('/api/admin/login',{method:'POST',body:{Username:$('user').value,Password:$('pass').value}});await load()}catch(e){msg(e.message,true)}};$('logout').onclick=async()=>{await api('/api/admin/logout',{method:'POST'}).catch(()=>{});location.reload()};$('refresh').onclick=()=>load().catch(e=>msg(e.message,true));$('stopAll').onclick=async()=>{if(!confirm('ALLE laufenden Neural-Hunt-Worker auf diesem Host stoppen?'))return;try{const x=await api('/api/admin/workers/stop-all',{method:'POST'});msg(`${x.stopped||0} Worker gestoppt${x.warnings?.length?' · '+x.warnings.length+' Warnungen':''}`,!!x.warnings?.length);await load()}catch(e){msg(e.message,true)}};$('pullImage').onclick=async()=>{try{const x=await api('/api/admin/image/pull',{method:'POST'});msg(`Image aktualisiert: ${x.image}`)}catch(e){msg(e.message,true)}};load().catch(()=>{});