feat(rdp): robust RDP enable — step-machine + deep health check + self-heal + end-to-end verify + live progress & structured log

This commit is contained in:
hmo
2026-08-26 02:40:06 +08:00
parent 7036b667b1
commit 811d2dd6db
3 changed files with 390 additions and 77 deletions
+21
View File
@@ -1376,6 +1376,27 @@ def api_rdp_toggle():
return jsonify({"ok": False, "error": str(e)})
@app.route("/api/rdp/progress")
def api_rdp_progress():
"""RDP enable 实时进度 — proxy to xmpp_bot on Windows (polls rdp_progress.json)."""
try:
data = _bridge_post("/rdp", {"action": "progress"}, timeout=8)
return jsonify(data)
except Exception as e:
return jsonify({"ok": False, "state": "unknown", "message": "progress 查询失败", "error": str(e), "steps": []})
@app.route("/api/rdp/enable_log")
def api_rdp_enable_log():
"""RDP enable 结构化日志 — proxy to xmpp_bot on Windows (for post-mortem debug)."""
try:
n = request.args.get("lines", 80, type=int)
data = _bridge_post("/rdp", {"action": "enable_log", "lines": n}, timeout=8)
return jsonify(data)
except Exception as e:
return jsonify({"ok": False, "error": str(e), "lines": []})
# ════════════════════════════════════════════════════════════
# OpenCode Go Usage Monitor — 4个账号用量配额监控 (246 本地采集)
# See gateway/scripts/specs/usage_monitor.json
+39 -2
View File
@@ -384,10 +384,11 @@ async function fI(){
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='<h2>RDP Remote Desktop<span class="help-btn" onclick="showModuleHelp(\'rdp\',\'human\')" title="使用说明">?</span><span class="help-btn ai" onclick="showModuleHelp(\'rdp\',\'ai\')" title="AI Spec">§</span></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>Disable</button><span id=rdp-status-txt style="font-size:12px;color:var(--dim)">-</span></div>';
rd.innerHTML='<h2>RDP Remote Desktop<span class="help-btn" onclick="showModuleHelp(\'rdp\',\'human\')" title="使用说明">?</span><span class="help-btn ai" onclick="showModuleHelp(\'rdp\',\'ai\')" title="AI Spec">§</span></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>Disable</button><span id=rdp-status-txt style="font-size:12px;color:var(--dim)">-</span><a href="javascript:void(0)" id="rdp-log-link" style="font-size:11px;color:var(--dim);margin-left:auto">日志</a></div><div id="rdp-progress" style="margin-top:10px;display:none;font-size:12px"><div id="rdp-prog-msg" style="color:var(--accent);margin-bottom:6px;font-weight:600"></div><div id="rdp-prog-steps" style="line-height:1.7"></div></div><div id="rdp-logbox" style="margin-top:8px;display:none;font-size:11px;color:var(--dim);background:rgba(0,0,0,.2);padding:8px;border-radius:6px;max-height:180px;overflow:auto;white-space:pre-wrap;font-family:monospace"></div>';
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-on').onclick=function(){fetch('/api/rdp/toggle',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'start'})}).then(function(){startRdpProgress()}).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')})};
document.getElementById('rdp-log-link').onclick=function(){var b=document.getElementById('rdp-logbox');if(b.style.display==='none'||!b.style.display){b.style.display='block';loadRdpLog()}else{b.style.display='none'}};
}
try{var e4=await fetch('/api/rdp').then(function(r){return r.json()});
document.getElementById('rdp-st').className='d '+(e4.rdp_enabled?'ok':'stopped');
@@ -399,6 +400,42 @@ async function fI(){
document.getElementById('rdp-status-txt').textContent=fOn?'Connected':e4.rdp_enabled?'Tunnel pending':'Disconnected';
}catch(e4){}
/* --- RDP enable 实时进度轮询 + 结构化日志 --- */
var _rdpProgTimer=null;
function startRdpProgress(){
var box=document.getElementById('rdp-progress'); if(box) box.style.display='block';
if(_rdpProgTimer) clearInterval(_rdpProgTimer);
pollRdpProgress();
_rdpProgTimer=setInterval(pollRdpProgress,1500);
}
async function pollRdpProgress(){
try{
var d=await fetch('/api/rdp/progress').then(function(r){return r.json()});
var msg=document.getElementById('rdp-prog-msg');
if(msg) msg.textContent=(d.state==='running'?'⏳ ':'')+(d.message||'');
var st=document.getElementById('rdp-prog-steps');
if(st && d.steps){
st.innerHTML=d.steps.map(function(s){
var icon=s.status==='ok'?'✓':(s.status==='fail'?'✗':(s.status==='warn'?'⚠':(s.status==='skip'?'—':'…')));
var color=s.status==='ok'?'#4caf50':(s.status==='fail'?'#f44336':(s.status==='warn'?'#ff9800':'#9aa'));
return '<div style="color:'+color+'">'+icon+' '+s.name+' <span style="color:#778;font-size:11px">'+(s.detail||'')+'</span></div>';
}).join('');
}
if(d.state==='done'||d.state==='failed'){
if(_rdpProgTimer){clearInterval(_rdpProgTimer);_rdpProgTimer=null;}
if(msg) msg.textContent=(d.state==='done'?'✓ ':'✗ ')+(d.message||'');
fI();
}
}catch(e){}
}
async function loadRdpLog(){
try{
var d=await fetch('/api/rdp/enable_log?lines=80').then(function(r){return r.json()});
var b=document.getElementById('rdp-logbox');
if(b) b.textContent=(d.lines&&d.lines.length)?d.lines.join('\n'):'(暂无日志)';
}catch(e){var b=document.getElementById('rdp-logbox');if(b)b.textContent='日志加载失败';}
}
/* --- OpenCode Go Usage section: create once, update state each cycle (no flicker) --- */
var us=document.getElementById('usage-section');
if(!us){