- EasyTier: Turn On/Turn Off按钮 (onclick通过JS赋值,无引号问题) - RDP: Enable/Disable按钮 (按钮状态与tunnel/rdp状态联动) - G规范: 用<pre>展示dev-spec.md完整内容 - 使用document.createElement+onclick避免HTML引号转义地狱 - Playwright验证: 0 errors, 所有功能正常
117 lines
20 KiB
HTML
117 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>AgentsMeeting Dashboard</title>
|
|
<style>
|
|
:root{--bg:#0d1117;--card:#161b22;--border:#30363d;--text:#c9d1d9;--dim:#8b949e;--accent:#58a6ff;--green:#3fb950;--red:#f85149;--yellow:#d29922}
|
|
*{margin:0;padding:0;box-sizing:border-box}body{background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:24px}
|
|
h1{font-size:20px;font-weight:600;color:var(--accent);margin-bottom:4px}.subtitle{color:var(--dim);font-size:12px;margin-bottom:20px}
|
|
.tabs{display:flex;gap:0;margin-bottom:20px;border-bottom:1px solid var(--border);flex-wrap:wrap}
|
|
.tab-btn{padding:10px 20px;cursor:pointer;font-size:13px;color:var(--dim);border:none;background:none;border-bottom:2px solid transparent}
|
|
.tab-btn:hover{color:var(--text)}.tab-btn.active{color:var(--accent);border-bottom-color:var(--accent)}
|
|
.tab-pane{display:none}.tab-pane.active{display:block}
|
|
.stats{display:flex;gap:12px;margin-bottom:20px;flex-wrap:wrap}
|
|
.stat-c{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px 16px;flex:1;min-width:120px}
|
|
.stat-c .n{font-size:28px;font-weight:700}.stat-c .l{font-size:11px;color:var(--dim)}
|
|
.stat-c.g .n{color:var(--green)}.stat-c.r .n{color:var(--red)}
|
|
.agents{display:flex;flex-direction:column;gap:12px}
|
|
.ac{background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden}
|
|
.ac.online{border-left:3px solid var(--green)}.ac.degraded{border-left:3px solid var(--yellow)}
|
|
.ac.offline{border-left:3px solid var(--red);opacity:.7}
|
|
.ah{display:flex;align-items:center;padding:14px 16px;gap:12px;cursor:pointer}
|
|
.ah:hover{background:rgba(255,255,255,.02)}
|
|
.sd{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
|
.sd.g{background:var(--green);box-shadow:0 0 6px var(--green)}
|
|
.sd.y{background:var(--yellow)}.sd.r{background:var(--red)}
|
|
.ai{flex:1;min-width:0}.an{font-size:15px;font-weight:600}
|
|
.am{font-size:12px;color:var(--dim);margin-top:2px}
|
|
.b{display:inline-block;padding:1px 8px;border-radius:10px;font-size:10px;font-weight:600;margin-left:8px}
|
|
.b.w{background:#0078d420;color:#60cdff}.b.l{background:#fcc62420;color:#fcc624}.b.m{background:#99999920;color:#ccc}
|
|
.as{display:flex;gap:16px;align-items:center;padding-right:8px}
|
|
.as .v{font-size:13px;font-weight:600}.as .l{font-size:10px;color:var(--dim)}
|
|
.aa{display:flex;gap:4px}
|
|
.btn{padding:5px 12px;border:1px solid var(--border);border-radius:5px;background:#21262d;color:var(--text);cursor:pointer;font-size:11px}
|
|
.btn:hover{background:#30363d}
|
|
.btn.s{border-color:var(--green);color:var(--green)}
|
|
.btn.x{border-color:var(--red);color:var(--red)}
|
|
.btn.r{border-color:var(--yellow);color:var(--yellow)}
|
|
.btn.l{border-color:var(--accent);color:var(--accent)}
|
|
.btn:disabled{opacity:.4;cursor:not-allowed}
|
|
.svcs{display:flex;gap:8px;padding:0 16px 10px;flex-wrap:wrap}
|
|
.st{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:12px;font-size:11px;background:#0d1117;border:1px solid var(--border)}
|
|
.st .d{width:6px;height:6px;border-radius:50%}.st .d.running{background:var(--green)}.st .d.stopped{background:var(--dim)}
|
|
.st .p{color:var(--accent);font-family:monospace;font-size:10px;margin-left:4px}
|
|
.lp{display:none;border-top:1px solid var(--border);padding:12px 16px;background:#0a0e14}
|
|
.lp.open{display:block}.lh{font-size:12px;color:var(--dim);margin-bottom:8px;display:flex;justify-content:space-between}
|
|
.lc{font:11px/1.6 "Cascadia Code",Consolas,monospace;color:var(--dim);max-height:300px;overflow-y:auto;background:#06080c;border:1px solid var(--border);border-radius:6px;padding:10px;white-space:pre-wrap}
|
|
.toast{position:fixed;top:16px;right:16px;padding:10px 16px;border-radius:6px;font-size:13px;z-index:999;opacity:0;pointer-events:none}
|
|
.toast.show{opacity:1}.toast.ok{background:#238636;color:#fff}.toast.err{background:#da3633;color:#fff}
|
|
.ps{margin-top:24px}.ps h2{font-size:16px;color:var(--dim);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
|
.psv{display:flex;gap:8px;flex-wrap:wrap}
|
|
.psv .i{background:var(--card);border:1px solid var(--border);border-radius:6px;padding:8px 12px;display:flex;align-items:center;gap:8px;font-size:12px}
|
|
.psv .i .d{width:6px;height:6px;border-radius:50%}.psv .i .d.ok{background:var(--green)}.psv .i .d.stopped{background:var(--dim)}
|
|
.section{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:14px 16px;margin-bottom:12px}
|
|
.section h2{font-size:15px;font-weight:600;color:var(--accent)}
|
|
.mono{font-family:"Cascadia Code",Consolas,monospace;font-size:12px}
|
|
.tag{display:inline-block;padding:1px 8px;border-radius:8px;font-size:11px;font-weight:600}
|
|
.tag.g{background:#3fb95020;color:var(--green)}.tag.r{background:#f8514920;color:var(--red)}
|
|
.tag.y{background:#d2992220;color:var(--yellow)}.tag.d{background:#8b949e20;color:var(--dim)}
|
|
table{width:100%;border-collapse:collapse;font-size:12px}th,td{padding:6px 8px;text-align:left;border-bottom:1px solid var(--border)}
|
|
th{color:var(--dim);font-weight:600;font-size:11px}
|
|
.log-line{font:11px/1.6 "Cascadia Code",Consolas,monospace;color:var(--dim);padding:1px 0}
|
|
.sr{display:flex;align-items:center;gap:10px;padding:5px 0}
|
|
.sub-bar{display:flex;gap:2px;margin-bottom:10px;flex-wrap:wrap}
|
|
.sub-btn{padding:4px 12px;font-size:12px;background:0 0;border:1px solid transparent;border-radius:12px;color:var(--dim);cursor:pointer}
|
|
.sub-btn:hover{color:var(--text);border-color:var(--border)}.sub-btn.active{color:var(--accent);border-color:var(--accent);background:var(--bg)}
|
|
@media(max-width:768px){.g2{grid-template-columns:1fr}}
|
|
</style></head><body>
|
|
<h1>AgentsMeeting</h1><div class="subtitle" id="subtitle">Dashboard</div>
|
|
<div class="tabs" id="tabs"></div><div id="panes"></div><div id="toast" class="toast"></div>
|
|
<script>
|
|
var T=[{"id": "agents", "label": "Agents"}, {"id": "kanban", "label": "Kanban"}, {"id": "infra", "label": "Infrastructure"}, {"id": "principles", "label": "开发原则", "children": [{"id": "git", "label": "A 源码"}, {"id": "svc", "label": "B 服务"}, {"id": "mon", "label": "C 监控"}, {"id": "todos", "label": "D 修复"}, {"id": "meta", "label": "E 成长"}, {"id": "exp", "label": "F 期望"},{"id":"spec","label":"G 规范"}]}];
|
|
var at=localStorage.getItem('t')||'agents',as=localStorage.getItem('s')||'git';
|
|
function esc(s){return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
function qs(id){return document.getElementById(id)}
|
|
function toast(m,t){var e=qs('toast');e.textContent=m;e.className='toast show '+(t||'ok');setTimeout(function(){e.className='toast'},2500)}
|
|
function init(){var b='',p='';for(var i=0;i<T.length;i++){var t=T[i],a=t.id===at||(t.children&&t.children.some(function(c){return c.id===at}));
|
|
b+='<button class="tab-btn'+(a?' active':'')+'" onclick="sw(\''+t.id+'\')">'+t.label+'</button>';
|
|
if(t.children){p+='<div class="tab-pane'+(a?' active':'')+'" id="pn-'+t.id+'"><div class="sub-bar">';
|
|
for(var j=0;j<t.children.length;j++){var c=t.children[j];p+='<button class="sub-btn'+(c.id===as?' active':'')+'" onclick="sws(\''+c.id+'\')">'+c.label+'</button>';}
|
|
p+='</div>';
|
|
for(var j=0;j<t.children.length;j++){var c=t.children[j];p+='<div id="sp-'+c.id+'"'+(c.id===as?'':' style="display:none"')+'><div class="section"><div id="ct-'+c.id+'"><span class="dim">Loading...</span></div></div></div>';}
|
|
p+='</div>';}else{p+='<div class="tab-pane'+(a?' active':'')+'" id="pn-'+t.id+'"><div id="ct-'+t.id+'"><span class="dim">Loading...</span></div></div>';}}
|
|
qs('tabs').innerHTML=b;qs('panes').innerHTML=p;}
|
|
function sw(id){if(id==='principles'){at='principles';as=localStorage.getItem('s')||'git'}else{at=id;localStorage.setItem('t',id)}
|
|
document.querySelectorAll('.tab-btn').forEach(function(b){b.classList.toggle('active',b.textContent.trim()===T.find(function(t){return t.id===at||(t.children&&t.children.some(function(c){return c.id===at}))}).label)});
|
|
document.querySelectorAll('.tab-pane').forEach(function(p){p.classList.toggle('active',p.id==='pn-'+id)});
|
|
if(id==='principles'){document.querySelectorAll('.sub-btn').forEach(function(b){b.classList.toggle('active',b.textContent.trim()===T[3].children.find(function(c){return c.id===as}).label)});document.querySelectorAll('[id^="sp-"]').forEach(function(p){p.style.display=p.id==='sp-'+as?'':'none'});(FETCHES[as]||function(){})()}}
|
|
function sws(id){as=id;localStorage.setItem('s',id);document.querySelectorAll('.sub-btn').forEach(function(b){b.classList.toggle('active',b.textContent.trim()===T[3].children.find(function(c){return c.id===as}).label)});document.querySelectorAll('[id^="sp-"]').forEach(function(p){p.style.display=p.id==='sp-'+id?'':'none'});(FETCHES[id]||function(){})()}
|
|
function fill(id,h){var e=qs('ct-'+id);if(e)e.innerHTML=h}
|
|
var L=new Set();
|
|
async function fa(){try{var r=await fetch('/api/agents'),d=await r.json(),on=d.filter(function(a){return a.status==='online'}).length,er=d.reduce(function(s,a){return s+(a.errors||0)},0);
|
|
var h='<div class="stats"><div class="stat-c g"><div class="n">'+on+'/'+d.length+'</div><div class="l">Agents Online</div></div><div class="stat-c '+(er>0?'y':'g')+'"><div class="n">'+(er||0)+'</div><div class="l">Errors</div></div><div class="stat-c g"><div class="n">'+d.length+'</div><div class="l">Total</div></div></div><div class="agents">';
|
|
for(var i=0;i<d.length;i++){var a=d[i];var st=a.status==='online'?'online':a.status==='degraded'?'degraded':'offline';var pb={windows:'w',linux:'l',mac:'m'}[a.platform]||'';
|
|
h+='<div class="ac '+st+'"><div class="ah" onclick="tgL(\''+a.id+'\')"><span class="sd '+(a.status==='online'?'g':a.status==='degraded'?'y':'r')+'"></span><div class="ai"><div class="an">'+esc(a.display_name||a.name)+(pb?' <span class="b '+pb+'">'+esc(a.platform)+'</span>':'')+'</div><div class="am">'+esc(a.jid||'')+(a.host?' · '+esc(a.host):'')+'</div></div><div class="as"><div class="v">'+(a.message_count_5min||0)+'</div><div class="l">msg/5m</div></div><div class="aa"><button class="btn s" onclick="event.stopPropagation();doAct(\''+a.id+'\',\'start\')" '+(a.status!=='offline'||!a.restartable?'disabled':'')+'>Start</button><button class="btn x" onclick="event.stopPropagation();doAct(\''+a.id+'\',\'stop\')" '+(a.status==='offline'||!a.restartable?'disabled':'')+'>Stop</button><button class="btn r" onclick="event.stopPropagation();doAct(\''+a.id+'\',\'restart\')" '+(!a.restartable?'disabled':'')+'>Restart</button><button class="btn l" onclick="event.stopPropagation();tgL(\''+a.id+'\')">Logs</button></div></div><div class="svcs">';
|
|
if(a.services){for(var k=0;k<a.services.length;k++){var s=a.services[k];h+='<span class="st"><span class="d '+(s.status||'stopped')+'"></span>'+esc(s.type)+(s.port?':<span class="p">'+s.port+'</span>':'')+'</span>';}}
|
|
h+='</div><div class="lp'+(L.has(a.id)?' open':'')+'" id="lp-'+a.id+'"><div class="lh"><span>Logs</span><button class="btn l" onclick="event.stopPropagation();fL(\''+a.id+'\')">Refresh</button></div><div class="lc" id="lc-'+a.id+'">Click Logs to load</div></div></div>';}
|
|
h+='</div>';qs('ct-agents').innerHTML=h;}catch(e){}}
|
|
async function doAct(id,act){try{var r=await fetch('/api/agents/'+id+'/'+act,{method:'POST'}),d=await r.json();toast(act+(d.ok?' OK':' FAILED'),d.ok?'ok':'err');setTimeout(fa,1500)}catch(e){toast('Error: '+e.message,'err')}}
|
|
function tgL(id){if(L.has(id))L.delete(id);else L.add(id);var lp=qs('lp-'+id);if(lp)lp.className='lp'+(L.has(id)?' open':'');if(L.has(id))fL(id)}
|
|
async function fL(id){var el=qs('lc-'+id);if(!el||!L.has(id))return;try{var r=await fetch('/api/agents/'+id+'/logs?lines=50'),d=await r.json();el.innerHTML=(d.lines||[]).map(function(l){return'<div>'+esc(l)+'</div>'}).join('')||'<span class="dim">no log data</span>';el.scrollTop=el.scrollHeight}catch(e){el.innerHTML='<span class="red">Failed</span>'}}
|
|
async function fG(){try{var r=await fetch('/api/git'),d=await r.json();var h='';for(var i=0;i<d.log.length;i++){h+='<div class="log-line">'+esc(d.log[i])+'</div>';}if(!h)h='<span class="dim">No commits</span>';fill('git',h)}catch(e){}}
|
|
async function fS(){try{var r=await fetch('/api/services'),d=await r.json(),ok=0;var h='<table><tr><th>Service</th><th>Port</th><th>Health</th><th>WD</th><th>Type</th></tr>';for(var i=0;i<d.services.length;i++){var s=d.services[i],a=s.health&&s.health.ok;if(a)ok++;h+='<tr><td>'+esc(s.name)+'</td><td>'+(s.port||'-')+'</td><td>'+(a?'OK':'DOWN')+'</td><td>'+(s.watchdog?'Y':'N')+'</td><td>'+esc(s.type)+'</td></tr>';}h+='</table>';fill('svc',h)}catch(e){}}
|
|
async function fM(){try{var r=await fetch('/api/monitor'),d=await r.json();var h='';if(d.tier1)h+='<div>T1: '+d.tier1.summary.ok+'/'+d.tier1.summary.total+' ok</div>';if(d.tier2)h+='<div>T2: '+d.tier2.summary.ok+'/'+d.tier2.summary.total+' ok</div>';fill('mon',h)}catch(e){}}
|
|
async function fT(){try{var r=await fetch('/api/todos'),d=await r.json();fill('todos','<span>'+(d.todos.length)+' records</span>')}catch(e){}}
|
|
async function fE(){try{var r=await fetch('/api/metagrowth'),d=await r.json();fill('meta','fixes: '+(d.total_fixes||0)+' commits: '+(d.commit_count||0))}catch(e){}}
|
|
async function fF(){try{var r=await fetch('/api/expected'),d=await r.json();var ok=0;for(var i=0;i<d.expected.length;i++){var e=d.expected[i],a=d.actual[e.name]||'',cmp=(e.expected.indexOf('running')===0&&a==='running')||(e.expected.indexOf('scheduled')===0&&(a==='scheduled'||a==='running'));if(cmp)ok++}fill('exp',ok+'/'+d.expected.length+' compliant')}catch(e){}}
|
|
async function fK(){try{var r=await fetch('/api/kanban'),d=await r.json(),n=(d.tasks||[]).length;fill('kanban',n?'<span>'+n+' tasks</span>':'<span class="dim">No tasks</span>')}catch(e){}}
|
|
async function fI(){try{var r=await fetch('/api/platform'),d=await r.json();var h='<div class="ps"><h2>Platform</h2><div class="psv">';for(var i=0;i<d.length;i++){h+='<div class="i"><span class="d '+(d[i].status==='running'?'ok':'stopped')+'"></span>'+esc(d[i].name)+'</div>';}h+='</div></div>';try{var r2=await fetch('/api/ejabberd'),e=await r2.json();var ea=e.alive||e.xmpp_bot_connected;h+='<div class="ps"><h2>Ejabberd</h2><div class="psv"><div class="i"><span class="d '+(ea?'ok':'stopped')+'"></span>'+(ea?'ALIVE':'DOWN')+'</div></div></div>';}catch(e2){}fill('infra',h)}catch(e){}}
|
|
var FETCHES={agents:fa,git:fG,svc:fS,mon:fM,todos:fT,meta:fE,exp:fF,spec:fS2,kanban:fK,infra:fI,easytier:fE2,rdp:fR,principles:function(){return(FETCHES[as]||function(){})()}};
|
|
async function loadAll(){var d=new Date();qs('subtitle').textContent=d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()+' '+d.getHours()+':'+d.getMinutes();await Promise.all(Object.keys(FETCHES).map(function(k){return FETCHES[k]()}));setTimeout(fetchEasyTierStatus,500);setTimeout(fetchRdpStatus,500)}
|
|
|
|
async function fE2(){try{var r=await fetch('/api/easytier'),d=await r.json(),s=d.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(d.virtual_ips){var w=document.getElementById('et-w-ip');if(w)w.textContent=d.virtual_ips.windows||'';var t=document.getElementById('et-246-ip');if(t)t.textContent=d.virtual_ips['246']||''}}catch(e){}}
|
|
async function fR(){try{var r=await fetch('/api/rdp'),d=await r.json();var e1=document.getElementById('rdp-st');if(e1){e1.className='d '+(d.rdp_enabled?'ok':'stopped')}var e2=document.getElementById('rdp-tun');if(e2){e2.className='d '+(d.tunnel_running?'ok':'stopped')}var info=document.getElementById('rdp-info');if(info)info.textContent=d.public_endpoint||''}catch(e){}}
|
|
async function fS2(){try{var r=await fetch('/api/spec'),d=await r.json();if(!d.ok||!d.content){fill('spec','Failed');return}fill('spec','<div class=mono style=font-size:11px;color:var(--dim);white-space:pre-wrap;max-height:500px;overflow-y:auto;background:var(--bg);padding:12px;border-radius:6px>'+esc(d.content)+'</div>')}catch(e){fill('spec','Error')}}
|
|
init();loadAll();setInterval(loadAll,10000);setInterval(fK,15000);
|
|
async function fetchEasyTierStatus(){try{var r=await fetch('/api/easytier'),d=await r.json(),s=d.status||{};var c=document.getElementById('ct-infra');if(!c)return;var el=document.getElementById('easytier-section');if(!el){el=document.createElement('div');el.id='easytier-section';el.innerHTML='<div class=ps style=margin-top:16px><h2>EasyTier VPN</h2><div class=psv><div class=i><span class=d id=et-w></span>Windows <span id=et-w-ip></span></div><div class=i><span class=d id=et-246></span>246 <span id=et-246-ip></span></div></div><div style=margin-top:8px;display:flex;gap:8px;align-items:center><button class="btn s" id=btn-et-on>Turn On</button><button class="btn x" id=btn-et-off disabled>Turn Off</button><span id=et-status style=font-size:12px;color:var(--dim)>-</span></div></div>';c.appendChild(el);var ob=document.getElementById('btn-et-on');var ofb=document.getElementById('btn-et-off');if(ob)ob.onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){setTimeout(fetchEasyTierStatus,2000);toast('EasyTier On OK')}).catch(function(e){toast('EasyTier On FAIL','err')})};if(ofb)ofb.onclick=function(){fetch('/api/easytier/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){setTimeout(fetchEasyTierStatus,2000);toast('EasyTier Off OK')}).catch(function(e){toast('EasyTier Off FAIL','err')})};}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(d.virtual_ips){var w=document.getElementById('et-w-ip');if(w)w.textContent=d.virtual_ips.windows||'';var t=document.getElementById('et-246-ip');if(t)t.textContent=d.virtual_ips['246']||''}var allRunning=Object.values(s).every(function(v){return v==='running'||v==='pending'});var onBtn=document.getElementById('btn-et-on');var offBtn=document.getElementById('btn-et-off');var stxt=document.getElementById('et-status');if(onBtn)onBtn.disabled=allRunning;if(offBtn)offBtn.disabled=!allRunning;if(stxt)stxt.textContent=allRunning?'Connected':'Off'}catch(e){}}
|
|
async function fetchRdpStatus(){try{var r=await fetch('/api/rdp'),d=await r.json();var c=document.getElementById('ct-infra');if(!c)return;var el=document.getElementById('rdp-section');if(!el){el=document.createElement('div');el.id='rdp-section';el.innerHTML='<div class=ps style=margin-top:16px><h2>RDP Remote Desktop</h2><div class=psv><div class=i><span class=d id=rdp-st></span>RDP <span id=rdp-info></span></div><div class=i><span class=d id=rdp-tun></span>Tunnel</div></div><div style=margin-top:8px;display:flex;gap:8px;align-items:center><button class="btn s" id=btn-rdp-on>Enable</button><button class="btn x" id=btn-rdp-off disabled>Disable</button><span id=rdp-status-txt style=font-size:12px;color:var(--dim)>-</span></div></div>';c.appendChild(el);var ob=document.getElementById('btn-rdp-on');var ofb=document.getElementById('btn-rdp-off');if(ob)ob.onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){setTimeout(fetchRdpStatus,2000);toast('RDP On OK')}).catch(function(e){toast('RDP On FAIL','err')})};if(ofb)ofb.onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'stop'})}).then(function(){setTimeout(fetchRdpStatus,2000);toast('RDP Off OK')}).catch(function(e){toast('RDP Off FAIL','err')})};}var e1=document.getElementById('rdp-st');if(e1)e1.className='d '+(d.rdp_enabled?'ok':'stopped');var e2=document.getElementById('rdp-tun');if(e2)e2.className='d '+(d.tunnel_running?'ok':'stopped');var info=document.getElementById('rdp-info');if(info)info.textContent=d.public_endpoint||'';var fullyOn=d.rdp_enabled&&d.tunnel_running;var onBtn=document.getElementById('btn-rdp-on');var offBtn=document.getElementById('btn-rdp-off');var stxt=document.getElementById('rdp-status-txt');if(onBtn)onBtn.disabled=fullyOn;if(offBtn)offBtn.disabled=!fullyOn;if(stxt)stxt.textContent=fullyOn?'Connected':d.rdp_enabled?'Tunnel pending':'Disconnected'}catch(e){}}
|
|
fetchEasyTierStatus();fetchRdpStatus();setInterval(fetchEasyTierStatus,10000);setInterval(fetchRdpStatus,10000);setInterval(fE,10000);setInterval(fR,10000);
|
|
</script></body></html> |