async function copyTokenValue(value){ if(!value)throw Error('empty'); try{if(navigator.clipboard?.writeText){await navigator.clipboard.writeText(value);return;}}catch{} const active=document.activeElement,selection=window.getSelection(),ranges=[]; if(selection)for(let i=0;ib.toString(16).padStart(2,'0')).join('');} async function accessPage(){ const view=document.querySelector('#view'); if(document.querySelector('#access-page'))return; view.innerHTML=`
EINSTELLUNGEN / ZUGANG

Dein Zugang. Deine Schlüssel.

Ein Administratorzugang für die Oberfläche und ein separater Token für API-Clients.

Oberflächenkennwort ändern

Alle bestehenden Browser-Sitzungen werden abgemeldet. Der API-Token bleibt gültig.

API-Token ändern

Der gespeicherte Token kann nicht zurückgelesen werden. Kopiert wird nur der neue Token im Eingabefeld. Einen zuvor gespeicherten Token findest du in deinem Passwortmanager.

Vor dem Speichern kopieren. Der bisherige Token wird sofort ungültig. Bestehende Browser-Sitzungen bleiben angemeldet. Verwende in API-Clients: Authorization: Bearer <Token>.

Der Token berechtigt zum Dienststatus, Hardwareabruf und Demo-Start/Stopp. Er erlaubt keine Kennwort-, Token- oder Netzwerkeinstellungen. Weitere Benutzer und Rollen sind noch nicht enthalten.

`; try{const r=await fetch('/api/v1/auth/status'),s=await r.json();if(!s.authenticated){location.reload();return}document.querySelector('#access-info').textContent=`Administrator · Diese Instanz: ${s.location}`;document.querySelector('#token-info').textContent=s.api_token_configured?('API-Token eingerichtet'+(s.token_changed_at?' · geändert am '+new Date(s.token_changed_at*1000).toLocaleString('de-DE'):'')+'. Der Wert wird nicht angezeigt.'):'Noch kein API-Token eingerichtet. API-Clients haben keinen Zugang.';}catch{document.querySelector('#access-message').textContent='Zugangsstatus nicht erreichbar.'} async function save(kind,data,form){if(accessBusy)return;accessBusy=true;view.querySelectorAll('button').forEach(b=>b.disabled=true);try{const r=await fetch('/api/v1/auth/'+kind,{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)}),s=await r.json();if(r.status===401){location.reload();return}if(!r.ok)throw Error(s.error);form.reset();if(s.login_required){location.reload();return}document.querySelector('#access-message').textContent='Neuer API-Token gespeichert. Der alte Token ist jetzt ungültig.';document.querySelector('#token-info').textContent='API-Token gerade geändert. Der Wert wird nicht angezeigt.';}catch(e){document.querySelector('#access-message').textContent=e.message}finally{accessBusy=false;view.querySelectorAll('button').forEach(b=>b.disabled=false);syncTokenCopy()}} document.querySelector('#password-form').onsubmit=e=>{e.preventDefault();const value=document.querySelector('#new-password').value;if(value!==document.querySelector('#repeat-password').value){document.querySelector('#access-message').textContent='Die neuen Kennwörter stimmen nicht überein.';return}save('password',{current_password:document.querySelector('#current-password').value,new_password:value},e.target)}; document.querySelector('#token-form').onsubmit=e=>{e.preventDefault();save('token',{current_password:document.querySelector('#token-password').value,new_token:document.querySelector('#new-token').value},e.target)}; const syncTokenCopy=()=>{document.querySelector('#token-copy').disabled=accessBusy||!document.querySelector('#new-token').value;}; document.querySelector('#new-token').addEventListener('input',syncTokenCopy);syncTokenCopy(); document.querySelector('#token-generate').onclick=()=>{document.querySelector('#new-token').value=accessToken();syncTokenCopy();document.querySelector('#access-message').textContent='Neuen Token vor dem Speichern kopieren.'}; document.querySelector('#token-show').onclick=()=>{const input=document.querySelector('#new-token');input.type=input.type==='password'?'text':'password';document.querySelector('#token-show').textContent=input.type==='password'?'Token anzeigen':'Token verbergen';}; document.querySelector('#token-copy').onclick=async()=>{try{const token=document.querySelector('#new-token').value;if(!token)throw Error();await copyTokenValue(token);document.querySelector('#access-message').textContent='Token kopiert. Bitte sicher speichern.';}catch{document.querySelector('#access-message').textContent='Der Browser hat das Kopieren nicht erlaubt. Bitte „Token anzeigen“ wählen und den neuen Token manuell kopieren.'}}; const hub=document.createElement('section');hub.className='card';hub.innerHTML=`

Hugging Face · Modelldownloads

Zugang wird geprüft …

Für private oder freigabepflichtige Modelle: Hugging-Face-Token erstellen ↗ mit Leserechten für das gewünschte Repository. Die Freigabe beantragst du auf dessen Modellkarte mit demselben Konto.

Separater Zugang für Hugging Face, nicht der Deck-API-Token. Speicherung auf Athena in einer Datei mit Zugriff nur für den Dienstbenutzer (0600), unverschlüsselt. Der gespeicherte Wert wird nie an den Browser zurückgegeben. Bereits laufende Downloads können nach Entfernen weiterlaufen.

`; document.querySelector('#access-page').insertBefore(hub,document.querySelector('#access-message')); const hfStatus=hub.querySelector('#hf-status'); const describe=s=>s.configured?'Token gespeichert. Ob die Modellfreigabe und Leserechte passen, prüft Hugging Face beim Download.':'Kein Hugging-Face-Token gespeichert. Öffentliche Downloads bleiben möglich.'; try{const r=await fetch('/api/v1/huggingface');if(!r.ok)throw Error();hfStatus.textContent=describe(await r.json());}catch{hfStatus.textContent='Hugging-Face-Zugangsstatus nicht erreichbar.';} async function saveHub(token){hub.querySelectorAll('button').forEach(b=>b.disabled=true);try{const r=await fetch('/api/v1/huggingface',{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify({token})}),s=await r.json();if(!r.ok)throw Error(s.error||'Speichern fehlgeschlagen.');hfStatus.textContent=describe(s);}catch(e){hfStatus.textContent=e.message;}finally{hub.querySelector('#hf-token').value='';hub.querySelectorAll('button').forEach(b=>b.disabled=false);}} hub.querySelector('#hf-form').onsubmit=e=>{e.preventDefault();saveHub(hub.querySelector('#hf-token').value.trim());}; hub.querySelector('#hf-remove').onclick=()=>saveHub(null); document.querySelector('#logout').onclick=async()=>{const r=await fetch('/api/v1/logout',{method:'POST',headers:{'X-Athena-Deck':'1'}});if(r.ok||r.status===401)location.reload();else document.querySelector('#access-message').textContent='Abmelden fehlgeschlagen. Bitte erneut versuchen.'}; }