Clarify token copying and add clipboard fallback

This commit is contained in:
Mikei386
2026-09-30 23:08:04 +02:00
parent 5c458bff06
commit df2d55c838
3 changed files with 35 additions and 9 deletions
+15 -3
View File
@@ -1,9 +1,21 @@
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;i<selection.rangeCount;i++)ranges.push(selection.getRangeAt(i).cloneRange());
const area=document.createElement('textarea');area.value=value;area.readOnly=true;
area.style.cssText='position:fixed;left:-9999px;top:0;opacity:0';document.body.append(area);
try{area.focus();area.select();if(!document.execCommand('copy'))throw Error('clipboard');}
finally{area.value='';area.remove();active?.focus();if(selection){selection.removeAllRanges();for(const range of ranges)selection.addRange(range);}}
}
let firstSetup=false;
const form=document.querySelector('#login'),message=document.querySelector('#error');
function freshToken(){const bytes=crypto.getRandomValues(new Uint8Array(32));return 'ad_'+Array.from(bytes,b=>b.toString(16).padStart(2,'0')).join('');}
async function load(){try{const r=await fetch('/api/v1/auth/status'),s=await r.json();firstSetup=!s.initialized;document.querySelector('#title').textContent=firstSetup?'Dein Zugang zu Athena.':'Willkommen zurück.';document.querySelector('#intro').textContent=firstSetup?'Lege das Kennwort für diese Oberfläche und einen separaten Token für API-Clients fest.':'Melde dich mit deinem Oberflächenkennwort an.';document.querySelector('#setup-fields').hidden=!firstSetup;document.querySelector('#password').autocomplete=firstSetup?'new-password':'current-password';document.querySelector('#submit').textContent=firstSetup?'Zugang einrichten':'Anmelden';form.hidden=false;}catch(e){message.textContent='Zugangsstatus nicht erreichbar.'}}
document.querySelector('#generate').onclick=()=>{document.querySelector('#api-token').value=freshToken();message.textContent='Token erzeugt. Vor dem Einrichten kopieren und sicher speichern.';};
const syncSetupCopy=()=>document.querySelector('#copy').disabled=!document.querySelector('#api-token').value;
document.querySelector('#api-token').addEventListener('input',syncSetupCopy);syncSetupCopy();
document.querySelector('#generate').onclick=()=>{document.querySelector('#api-token').value=freshToken();syncSetupCopy();message.textContent='Token erzeugt. Vor dem Einrichten kopieren und sicher speichern.';};
document.querySelector('#show-token').onclick=()=>{const input=document.querySelector('#api-token');input.type=input.type==='password'?'text':'password';document.querySelector('#show-token').textContent=input.type==='password'?'Token anzeigen':'Token verbergen';};
document.querySelector('#copy').onclick=async()=>{try{const token=document.querySelector('#api-token').value;if(!token)throw Error();await navigator.clipboard.writeText(token);message.textContent='Token kopiert. Bitte sicher speichern.';}catch{message.textContent='Token konnte nicht kopiert werden. Bitte einen eigenen Token aus deinem Passwortmanager einfügen.'}};
form.addEventListener('submit',async event=>{event.preventDefault();const input=document.querySelector('#password'),button=document.querySelector('#submit');button.disabled=true;try{let data={password:input.value},path='login';if(firstSetup){if(input.value!==document.querySelector('#repeat').value)throw Error('Die Kennwörter stimmen nicht überein.');data.api_token=document.querySelector('#api-token').value;path='auth/setup';}const r=await fetch('/api/v1/'+path,{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)});const value=await r.json();if(!r.ok)throw Error(value.error);form.reset();if(firstSetup){await load();message.textContent='Zugang eingerichtet. Bitte jetzt anmelden.';}else location.reload();}catch(e){message.textContent=e.message}finally{button.disabled=false}});
document.querySelector('#copy').onclick=async()=>{try{const token=document.querySelector('#api-token').value;if(!token)throw Error();await copyTokenValue(token);message.textContent='Token kopiert. Bitte sicher speichern.';}catch{message.textContent='Der Browser hat das Kopieren nicht erlaubt. Bitte „Token anzeigen“ wählen und manuell kopieren.'}};
form.addEventListener('submit',async event=>{event.preventDefault();const input=document.querySelector('#password'),button=document.querySelector('#submit');button.disabled=true;try{let data={password:input.value},path='login';if(firstSetup){if(input.value!==document.querySelector('#repeat').value)throw Error('Die Kennwörter stimmen nicht überein.');data.api_token=document.querySelector('#api-token').value;path='auth/setup';}const r=await fetch('/api/v1/'+path,{method:'POST',headers:{'Content-Type':'application/json','X-Athena-Deck':'1'},body:JSON.stringify(data)});const value=await r.json();if(!r.ok)throw Error(value.error);form.reset();syncSetupCopy();if(firstSetup){await load();message.textContent='Zugang eingerichtet. Bitte jetzt anmelden.';}else location.reload();}catch(e){message.textContent=e.message}finally{button.disabled=false}});
load();