From ed165db8c9bdabc5e3ad9abab426292a943b583f Mon Sep 17 00:00:00 2001 From: hmo Date: Wed, 15 Jul 2026 00:23:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20EasyTier/RDP=20sections=20created=20once?= =?UTF-8?q?,=20only=20state=20values=20updated=20each=20poll=20=E2=80=94?= =?UTF-8?q?=20eliminates=20flicker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/scripts/templates/dashboard.html | 57 +++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/gateway/scripts/templates/dashboard.html b/gateway/scripts/templates/dashboard.html index 66d6657..be07f8a 100644 --- a/gateway/scripts/templates/dashboard.html +++ b/gateway/scripts/templates/dashboard.html @@ -119,7 +119,62 @@ fill('kanban',html); var es=document.getElementById('kf-status');var ea=document.getElementById('kf-assignee'); if(es&&!s)es.value=fs;if(ea&&!a)ea.value=fa; if(es)es.onchange=fK;if(ea)ea.onchange=fK;}catch(e){}} -async function fI(){try{ var r=await fetch('/api/platform'),d=await r.json(); var h='

Platform

'; for(var i=0;i'+esc(d[i].name)+'
';} h+='
'; try{var r2=await fetch('/api/ejabberd'),e=await r2.json();var ea=e.alive||e.xmpp_bot_connected; h+='

Ejabberd

'+(ea?'ALIVE':'DOWN')+'
';}catch(e2){} fill('infra',h); /* EasyTier section */ try{var r3=await fetch('/api/easytier'),e3=await r3.json(),s=e3.status||{}; var et=document.createElement('div');et.className='ps';et.style.marginTop='16px'; var av=(e3.virtual_ips||{});var wip=av.windows||'';var tip=av['246']||''; et.innerHTML='

EasyTier VPN

Windows '+wip+'
246 '+tip+'
-
'; var ci=document.getElementById('ct-infra');if(ci)ci.appendChild(et); var e1=document.getElementById('et-w');if(e1)e1.className='d '+(s.windows==='running'?'ok':'stopped'); var e2=document.getElementById('et-246');if(e2)e2.className='d '+(s['246']==='running'?'ok':'stopped'); var allRun=Object.values(s).every(function(v){return v==='running'||v==='pending'}); var ob=document.getElementById('btn-et-on');var ofb=document.getElementById('btn-et-off');var stx=document.getElementById('et-status'); if(ob){ob.disabled=allRun;ob.onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){setTimeout(fI,500)}).catch(function(e){toast('ET Fail','err')})};} if(ofb){ofb.disabled=!allRun;ofb.onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){setTimeout(fI,500)}).catch(function(e){toast('ET Fail','err')})};} if(stx)stx.textContent=allRun?'Connected':'Off';}catch(e3){} /* RDP section */ try{var r4=await fetch('/api/rdp'),e4=await r4.json(); var rd=document.createElement('div');rd.className='ps';rd.style.marginTop='16px'; rd.innerHTML='

RDP Remote Desktop

RDP '+esc(e4.public_endpoint||'')+'
Tunnel
-
'; var ci2=document.getElementById('ct-infra');if(ci2)ci2.appendChild(rd); var ee1=document.getElementById('rdp-st');if(ee1)ee1.className='d '+(e4.rdp_enabled?'ok':'stopped'); var ee2=document.getElementById('rdp-tun');if(ee2)ee2.className='d '+(e4.tunnel_running?'ok':'stopped'); var fOn=e4.rdp_enabled&&e4.tunnel_running; var ob2=document.getElementById('btn-rdp-on');var ofb2=document.getElementById('btn-rdp-off');var stx2=document.getElementById('rdp-status-txt'); if(ob2){ob2.disabled=fOn;ob2.onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){setTimeout(fI,500)}).catch(function(e){toast('RDP Fail','err')})};} if(ofb2){ofb2.disabled=!fOn;ofb2.onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){setTimeout(fI,500)}).catch(function(e){toast('RDP Fail','err')})};} if(stx2)stx2.textContent=fOn?'Connected':e4.rdp_enabled?'Tunnel pending':'Disconnected';}catch(e4){} }catch(e){}} +async function fI(){ + try{ + var ci=document.getElementById('ct-infra');if(!ci)return; + /* --- Stable wrapper for Platform+Ejabberd (rebuilt each cycle, no flicker since no state to preserve) --- */ + var pe=document.getElementById('infra-pe'); + if(!pe){pe=document.createElement('div');pe.id='infra-pe';ci.appendChild(pe);} + var h=''; + try{var r=await fetch('/api/platform'),d=await r.json(); + h+='

Platform

'; + for(var i=0;i'+esc(d[i].name)+'
';} + h+='
'; + }catch(e){} + try{var r2=await fetch('/api/ejabberd'),e=await r2.json();var ea=e.alive||e.xmpp_bot_connected; + h+='

Ejabberd

'+(ea?'ALIVE':'DOWN')+'
'; + }catch(e2){} + pe.innerHTML=h; + + /* --- EasyTier section: create once, update state each cycle (no flicker) --- */ + var et=document.getElementById('easytier-section'); + if(!et){ + et=document.createElement('div');et.id='easytier-section';et.className='ps';et.style.marginTop='16px'; + et.innerHTML='

EasyTier VPN

Windows
246
-
'; + ci.appendChild(et); + document.getElementById('btn-et-on').onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){fI()}).catch(function(){toast('ET Fail','err')})}; + document.getElementById('btn-et-off').onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){fI()}).catch(function(){toast('ET Fail','err')})}; + } + try{var e3=await fetch('/api/easytier').then(function(r){return r.json()});var s=e3.status||{}; + var e1=document.getElementById('et-w');if(e1)e1.className='d '+(s.windows==='running'?'ok':'stopped'); + var e2=document.getElementById('et-246');if(e2)e2.className='d '+(s['246']==='running'?'ok':'stopped'); + if(e3.virtual_ips){var w=document.getElementById('et-w-ip');if(w)w.textContent=e3.virtual_ips.windows||'';var t=document.getElementById('et-246-ip');if(t)t.textContent=e3.virtual_ips['246']||'';} + var allRun=Object.values(s).every(function(v){return v==='running'||v==='pending'}); + document.getElementById('btn-et-on').disabled=allRun; + document.getElementById('btn-et-off').disabled=!allRun; + document.getElementById('et-status').textContent=allRun?'Connected':'Off'; + }catch(e3){} + + /* --- RDP section: create once, update state each cycle (no flicker) --- */ + var rd=document.getElementById('rdp-section'); + if(!rd){ + rd=document.createElement('div');rd.id='rdp-section';rd.className='ps';rd.style.marginTop='16px'; + rd.innerHTML='

RDP Remote Desktop

RDP
Tunnel
-
'; + ci.appendChild(rd); + document.getElementById('btn-rdp-on').onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){fI()}).catch(function(){toast('RDP Fail','err')})}; + document.getElementById('btn-rdp-off').onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){fI()}).catch(function(){toast('RDP Fail','err')})}; + } + try{var e4=await fetch('/api/rdp').then(function(r){return r.json()}); + document.getElementById('rdp-st').className='d '+(e4.rdp_enabled?'ok':'stopped'); + document.getElementById('rdp-tun').className='d '+(e4.tunnel_running?'ok':'stopped'); + document.getElementById('rdp-info').textContent=e4.public_endpoint||''; + var fOn=e4.rdp_enabled&&e4.tunnel_running; + document.getElementById('btn-rdp-on').disabled=fOn; + document.getElementById('btn-rdp-off').disabled=!fOn; + document.getElementById('rdp-status-txt').textContent=fOn?'Connected':e4.rdp_enabled?'Tunnel pending':'Disconnected'; + }catch(e4){} + }catch(e){} +} async function fH(){try{var r=await fetch('/api/prd'),d=await r.json();if(!d.ok||!d.content){fill('prd','Failed');return}var el=document.getElementById('ct-prd');var sp=el?el.querySelector('div')?el.querySelector('div').scrollTop:0:0;fill('prd','
'+mdRender(d.content)+'
');var el2=document.getElementById('ct-prd');if(el2&&sp){setTimeout(function(){(el2.querySelector('div')||el2).scrollTop=sp},0)}}catch(e){fill('prd','Error')}}async function fTree(){try{var r=await fetch('/api/services'),d=await r.json();if(!d.services||!d.services.length){fill('tree','No data');return} var tree={name:'System',children:[]};var map={};d.services.forEach(function(s){map[s.name]=s;if(!s.depends_on||!s.depends_on.length){tree.children.push(s)}else{s._parents=s.depends_on}}); var h='
';