diff --git a/src/web_ui/index.html b/src/web_ui/index.html index 01e6da0..cff51b3 100644 --- a/src/web_ui/index.html +++ b/src/web_ui/index.html @@ -2311,6 +2311,64 @@ function applyTheme(t){document.body.className=t==='dark'?'':'theme-'+t;localSto async function logout(){try{await fetch('/api/logout',{method:'POST'});location.reload()}catch{}} +/* ── Federation peers editor (Settings) ─────────────────────────── */ +let remotesEdit=[]; // {name,url,kind,username,has_password,password} +async function renderRemotesEditor(){ + try{ + remotesEdit=(await(await api('/api/remotes')).json()).map(x=>({ + name:x.name,url:x.url,kind:x.kind||'catacomb', + username:x.username||'',has_password:!!x.has_password,password:''})); + }catch(e){remotesEdit=[];} + drawRemotesEditor(); +} +function drawRemotesEditor(){ + const box=document.getElementById('remotesRows'); if(!box)return; box.innerHTML=''; + const inp='background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:12px'; + remotesEdit.forEach((rm,i)=>{ + const row=document.createElement('div'); + row.style.cssText='display:flex;flex-wrap:wrap;gap:4px;align-items:center;border-bottom:1px solid var(--border);padding:6px 0'; + row.innerHTML=` + + + + + + + + `; + box.appendChild(row); + }); + box.querySelectorAll('.rm-kind').forEach(el=>el.onchange=e=>{remotesEdit[+e.target.dataset.i].kind=e.target.value;drawRemotesEditor();}); + const bind=(cls,f)=>box.querySelectorAll('.'+cls).forEach(el=>el.oninput=e=>{remotesEdit[+e.target.dataset.i][f]=e.target.value;}); + bind('rm-name','name');bind('rm-url','url');bind('rm-user','username');bind('rm-pass','password'); + box.querySelectorAll('.rm-del').forEach(el=>el.onclick=e=>{remotesEdit.splice(+e.target.dataset.i,1);drawRemotesEditor();}); + box.querySelectorAll('.rm-test').forEach(el=>el.onclick=e=>testRemoteRow(+e.target.dataset.i)); +} +function addRemoteRow(){remotesEdit.push({name:'',url:'',kind:'catacomb',username:'',has_password:false,password:''});drawRemotesEditor();} +async function testRemoteRow(i){ + const rm=remotesEdit[i]; + const out=document.querySelector('.rm-result[data-i="'+i+'"]'); if(out)out.textContent='testing…'; + try{ + const j=await(await api('/api/remotes/test',{method:'POST',headers:{'Content-Type':'application/json'}, + body:JSON.stringify({url:rm.url,kind:rm.kind,username:rm.username||null,password:rm.password||null})})).json(); + if(out)out.textContent=j.ok?('✓ ok'+(j.channels!=null?` (${j.channels} ch)`:'')):('✗ '+(j.error||'failed')); + }catch(e){if(out)out.textContent='✗ '+e;} +} +async function saveRemotesEditor(btn){ + const payload=remotesEdit.map(rm=>({name:rm.name,url:rm.url,kind:rm.kind, + username:rm.kind==='peertube'?(rm.username||null):null, + password:rm.password?rm.password:null})); + const st=document.getElementById('remotes-edit-status'); if(st)st.textContent='saving…'; + try{ + await api('/api/remotes',{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); + if(st)st.textContent='saved'; + await renderRemotesEditor(); loadRemotes(); + }catch(e){if(st)st.textContent='save failed';} +} + async function openSettings(){ let cur={transcode:false,source_url:null,current_bind:null,available_binds:[],download_password_required:false};try{cur=await(await api('/api/settings')).json()}catch{} const savedTheme=localStorage.getItem('theme')||'dark'; @@ -2534,6 +2592,15 @@ async function openSettings(){ +
+
🌐 Federation peers
+
Browse other Catacomb instances, or PeerTube channels, from this one. PeerTube peers can be added now; browsing them arrives in a later update.
+
+
+ + + +
${srcRow}
${logoutBtn} @@ -2542,6 +2609,7 @@ async function openSettings(){
`; document.body.appendChild(bg); + renderRemotesEditor(); } function loadCookieFile(input){