diff --git a/static/admin/base.js b/static/admin/base.js index 7e5d654..841db89 100644 --- a/static/admin/base.js +++ b/static/admin/base.js @@ -112,7 +112,6 @@ function showSubTab(tabId, name){ const tab = document.getElementById('tab-'+tabId); tab.querySelectorAll('.sub-tab').forEach(b=>b.classList.toggle('active', b.dataset.sub===name)); tab.querySelectorAll('.sub-panel').forEach(p=>p.classList.toggle('active', p.id===tabId+'-sub-'+name)); - if(name==='screen' && typeof loadScreenDevices==='function') loadScreenDevices(); if(name==='devpool' && typeof loadDevPool==='function') loadDevPool(); } diff --git a/static/admin/tools.js b/static/admin/tools.js index 236cd04..376278c 100644 --- a/static/admin/tools.js +++ b/static/admin/tools.js @@ -277,127 +277,6 @@ function copyTsKey(){ } -// ================== Tab: 工具 - 远程看屏(MJPEG 流 + u2 触控) ================== -let _screenViewOn = false; - -async function loadScreenDevices(force){ - const sel=document.getElementById('screen-device'); - if(!sel)return; - const list=await _fetchAdbDevices(force); - if(list===null)return; - const cur=sel.value; - const stateTxt={device:'在线',offline:'离线',pool:'设备池(未连接)'}; - sel.innerHTML=list.map(d=> - '' - ).join(''); - if(cur)sel.value=cur; -} - -function _screenSerial(){ - const sel=document.getElementById('screen-device'); - return sel?sel.value:''; -} - -function startScreenView(){ - const serial=_screenSerial(); - if(!serial){showToast('请先选择设备','error');return;} - stopScreenView(); - const img=document.getElementById('screen-img'); - const loading=document.getElementById('screen-loading'); - loading.style.display='flex'; - document.getElementById('screen-status').textContent=''; - img.src='/api/screen/stream?serial='+encodeURIComponent(serial); - img.onload=()=>{loading.style.display='none';_screenViewOn=true;document.getElementById('screen-status').textContent='观看中';}; - img.onerror=()=>{loading.style.display='none';_screenViewOn=false;showToast('画面流中断(设备离线或已停止)','error');}; -} - -function stopScreenView(){ - const img=document.getElementById('screen-img'); - if(img)img.src=''; - _screenViewOn=false; - const loading=document.getElementById('screen-loading'); - if(loading)loading.style.display='none'; - const st=document.getElementById('screen-status'); - if(st)st.textContent=''; -} - -// 显示尺寸 -> 设备原生分辨率(img.naturalWidth/Height 即设备分辨率) -function _screenCoords(ev){ - const img=document.getElementById('screen-img'); - if(!img.naturalWidth)return [null,null]; - const r=img.getBoundingClientRect(); - return [ - Math.round((ev.clientX-r.left)/r.width*img.naturalWidth), - Math.round((ev.clientY-r.top)/r.height*img.naturalHeight), - ]; -} - -function _screenTap(x,y){ - const serial=_screenSerial(); - if(!serial||x==null||y==null)return; - apiPost('/api/screen/tap',{serial,x,y}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');}); -} - -function _screenSwipe(x1,y1,x2,y2){ - const serial=_screenSerial(); - if(!serial||x1==null)return; - apiPost('/api/screen/swipe',{serial,x1,y1,x2,y2}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');}); -} - -function screenKey(key){ - const serial=_screenSerial(); - if(!serial){showToast('请先选择设备','error');return;} - apiPost('/api/screen/key',{serial,key}).then(r=>{if(r&&!r.ok)showToast(r.error,'error');}); -} - -function screenSendText(){ - const serial=_screenSerial(); - const inp=document.getElementById('screen-text'); - const text=(inp.value||'').trim(); - if(!serial){showToast('请先选择设备','error');return;} - if(!text){showToast('请输入文字','error');return;} - apiPost('/api/screen/text',{serial,text}).then(r=>{ - if(r&&!r.ok)showToast(r.error,'error'); - else inp.value=''; - }); -} - -// 鼠标 + 触控:拖拽=滑动,点按=轻触 -(function(){ - const img=document.getElementById('screen-img'); - if(!img)return; - let dragging=false, sx=0, sy=0; - - function onDown(ev){ - if(!_screenViewOn)return; - ev.preventDefault(); - dragging=true; - const [x,y]=_screenCoords(ev); - sx=x; sy=y; - } - function onUp(ev){ - if(!dragging)return; - dragging=false; - const [x,y]=_screenCoords(ev); - if(x==null)return; - if(Math.abs(x-sx)<8 && Math.abs(y-sy)<8){ - _screenTap(x,y); // 位移小 → 点击 - }else{ - _screenSwipe(sx,sy,x,y); // 有位移 → 滑动 - } - } - - img.addEventListener('mousedown',onDown); - img.addEventListener('mousemove',ev=>{if(dragging)ev.preventDefault();}); - window.addEventListener('mouseup',onUp); - img.addEventListener('touchstart',onDown,{passive:false}); - img.addEventListener('touchend',ev=>{ - if(!dragging)return; - const t=ev.changedTouches[0]; - onUp({clientX:t.clientX,clientY:t.clientY,preventDefault(){}}); - },{passive:false}); -})(); - // ================== Tab: 工具 - 设备池管理(本地 SQLite 清单) ================== async function loadDevPool(){ const tb=document.getElementById('tb-devpool'); diff --git a/templates/admin/monitor.html b/templates/admin/monitor.html index 2e397db..6aa09d3 100644 --- a/templates/admin/monitor.html +++ b/templates/admin/monitor.html @@ -494,7 +494,6 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14 - @@ -674,41 +673,6 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14 - -
-
- - - - - -
-
-
- 远程画面 -
连接中(首次连接需推送 atx-agent,最长约 30 秒)...
-
-
-
操作(鼠标拖拽=滑动,点击=轻触)
-
- - - - - - - -
-
- - -
-
画面为设备原生分辨率 JPEG 流(约 3-6 帧/秒)。设备息屏时画面为黑,可先点电源/亮屏。
-
-
-
diff --git a/templates/admin/wall.html b/templates/admin/wall.html index d29f377..f9d37fa 100644 --- a/templates/admin/wall.html +++ b/templates/admin/wall.html @@ -21,62 +21,91 @@ *{box-sizing:border-box;margin:0;padding:0} html,body{height:100%} body{background:var(--bg);font-family:var(--body);color:var(--text);overflow:hidden} -/* 背景网格纹理 */ body::before{content:"";position:fixed;inset:0;pointer-events:none; background-image:linear-gradient(rgba(45,212,191,.035) 1px,transparent 1px), linear-gradient(90deg,rgba(45,212,191,.035) 1px,transparent 1px); background-size:42px 42px} /* 顶部栏 */ -header{display:flex;align-items:center;gap:28px;padding:16px 26px 12px;border-bottom:1px solid var(--ink-line);background:rgba(13,15,19,.85);backdrop-filter:blur(6px);position:relative;z-index:5} -header .title{font-family:var(--display);font-size:22px;font-weight:800;letter-spacing:1px;color:#fff;display:flex;align-items:center;gap:12px} +header{display:flex;align-items:center;gap:24px;padding:14px 26px 12px;border-bottom:1px solid var(--ink-line);background:rgba(13,15,19,.85);backdrop-filter:blur(6px);position:relative;z-index:5} +header .title{font-family:var(--display);font-size:21px;font-weight:800;letter-spacing:1px;color:#fff;display:flex;align-items:center;gap:12px;white-space:nowrap} header .title::before{content:"";width:10px;height:10px;border-radius:50%;background:var(--teal);box-shadow:0 0 0 0 rgba(45,212,191,.55);animation:pulse 2.2s infinite} @keyframes pulse{0%{box-shadow:0 0 0 0 rgba(45,212,191,.5)}70%{box-shadow:0 0 0 9px rgba(45,212,191,0)}100%{box-shadow:0 0 0 0 rgba(45,212,191,0)}} header .stats{display:flex;gap:10px;flex:1} -header .stat{background:var(--card);border:1px solid var(--card-line);border-radius:10px;padding:7px 18px;text-align:center;min-width:86px} -header .stat .n{font-family:var(--mono);font-size:21px;font-weight:600;line-height:1.15} +header .stat{background:var(--card);border:1px solid var(--card-line);border-radius:10px;padding:6px 16px;text-align:center;min-width:84px} +header .stat .n{font-family:var(--mono);font-size:20px;font-weight:600;line-height:1.15} header .stat .l{font-size:11px;color:var(--faint);margin-top:2px;letter-spacing:.4px} header .stat.online .n{color:var(--green)} header .stat.running .n{color:var(--teal)} header .stat.error .n{color:var(--red)} -header .clock{font-family:var(--mono);font-size:19px;color:var(--muted);text-align:right;line-height:1.2} +header .wall-btn{background:var(--card);border:1px solid var(--card-line);color:var(--teal);border-radius:10px;padding:8px 18px;font-size:13px;font-weight:600;cursor:pointer;transition:all .15s;font-family:var(--body)} +header .wall-btn:hover{background:#1a2029;border-color:var(--teal);transform:translateY(-1px)} +header .wall-btn:disabled{opacity:.5;cursor:not-allowed} +header .clock{font-family:var(--mono);font-size:18px;color:var(--muted);text-align:right;line-height:1.2;white-space:nowrap} header .clock .date{font-size:11px;color:var(--faint)} /* 设备网格 */ -main{position:relative;z-index:2;height:calc(100vh - 86px);overflow-y:auto;padding:18px 24px 30px;display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:16px;align-content:start} -/* 设备卡片 */ -.card{background:var(--card);border:1px solid var(--card-line);border-radius:14px;overflow:hidden;position:relative;animation:cardIn .4s ease-out backwards;transition:border-color .2s,box-shadow .2s} +main{position:relative;z-index:2;height:calc(100vh - 88px);overflow-y:auto;padding:16px 24px 30px;display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;align-content:start} +/* 设备卡片(可点击进操作) */ +.card{background:var(--card);border:1px solid var(--card-line);border-radius:14px;overflow:hidden;position:relative;animation:cardIn .4s ease-out backwards;cursor:pointer;transition:border-color .2s,box-shadow .2s,transform .15s} +.card:hover{transform:translateY(-3px);box-shadow:0 8px 22px rgba(0,0,0,.4)} @keyframes cardIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}} -.card.online{border-color:#263142} .card.running{border-color:rgba(45,212,191,.55);box-shadow:0 0 18px rgba(45,212,191,.12),inset 0 0 0 1px rgba(45,212,191,.2)} .card.error{border-color:rgba(248,113,113,.5)} .card.offline{opacity:.62} .card .thumb{aspect-ratio:9/16;background:#0a0c10;position:relative;overflow:hidden} .card .thumb img{width:100%;height:100%;object-fit:cover;display:block;transition:opacity .2s} .card.offline .thumb img{display:none} -.card .thumb .ph{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--faint);font-size:12px;background:repeating-linear-gradient(45deg,#0c0e12,#0c0e12 12px,#0f1218 12px,#0f1218 24px)} +.card .thumb .ph{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--faint);font-size:12px;background:repeating-linear-gradient(45deg,#0c0e12,#0c0e12 12px,#0f1218 12px,#0f1218 24px)} .card .thumb .ph .ico{font-size:26px;opacity:.6} /* 状态角标 */ -.card .badge{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:6px;background:rgba(13,15,19,.78);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;color:var(--muted);border:1px solid var(--ink-line)} +.card .badge{position:absolute;top:8px;left:8px;display:flex;align-items:center;gap:6px;background:rgba(13,15,19,.8);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;color:var(--muted);border:1px solid var(--ink-line)} .card.running .badge{color:var(--teal);border-color:rgba(45,212,191,.4)} .card.error .badge{color:var(--red)} .card .badge .dot{width:7px;height:7px;border-radius:50%;background:var(--faint)} -.card.online .badge .dot{background:var(--green);box-shadow:0 0 6px rgba(52,211,153,.7)} .card.running .badge .dot{background:var(--teal);box-shadow:0 0 7px rgba(45,212,191,.8);animation:pulse 1.6s infinite} .card.error .badge .dot{background:var(--red);box-shadow:0 0 6px rgba(248,113,113,.7)} .card.offline .badge .dot{background:var(--faint)} +/* 熄屏角标(右上) */ +.card .screen-tag{position:absolute;top:8px;right:8px;background:rgba(13,15,19,.8);backdrop-filter:blur(4px);border-radius:99px;padding:3px 10px;font-size:11px;font-weight:600;border:1px solid var(--ink-line)} +.card .screen-tag.on{color:var(--green)} +.card .screen-tag.off{color:var(--faint)} /* 信息区 */ -.card .info{padding:10px 12px 12px} -.card .info .serial{font-family:var(--mono);font-size:13px;font-weight:600;color:#eef2f7;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.card .info .model{font-size:11px;color:var(--faint);margin-top:2px} +.card .info{padding:9px 12px 11px} +.card .info .name{font-size:13.5px;font-weight:700;color:#f1f4f8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.card .info .ip{font-family:var(--mono);font-size:12px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.card .info .model{font-size:10.5px;color:var(--faint);margin-top:2px} .card .info .action{font-size:11.5px;color:var(--muted);margin-top:6px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:16px} .card .info .action b{color:var(--amber);font-weight:600} -.card .info .fg{margin-top:4px} -.card .info .fg .tag{font-size:10.5px;background:#1c2430;color:#9fb3c8;border:1px solid var(--ink-line);border-radius:5px;padding:1px 7px;display:inline-block} -/* 进度条 */ .card .info .prog{margin-top:7px;height:4px;background:#1d242e;border-radius:99px;overflow:hidden} .card .info .prog i{display:block;height:100%;background:linear-gradient(90deg,var(--teal),#5eead4);border-radius:99px;transition:width .5s} .card .info .prog-txt{font-size:10px;color:var(--faint);margin-top:3px;font-family:var(--mono)} +.card .info .hint{font-size:10px;color:var(--faint);margin-top:5px;text-align:center;opacity:0;transition:opacity .2s} +.card:hover .info .hint{opacity:.8} /* 空状态 */ .empty-wall{grid-column:1/-1;text-align:center;color:var(--faint);padding:80px 0;font-size:14px} +/* 设备操作弹窗 */ +.ctrl-overlay{display:none;position:fixed;inset:0;background:rgba(5,7,10,.78);backdrop-filter:blur(3px);z-index:100;justify-content:center;align-items:center;padding:26px} +.ctrl-overlay.show{display:flex} +.ctrl-box{background:var(--card);border:1px solid var(--card-line);border-radius:16px;width:min(420px,94vw);max-height:94vh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.6);animation:modalIn .18s ease-out} +@keyframes modalIn{from{opacity:0;transform:translateY(12px) scale(.98)}to{opacity:1;transform:none}} +.ctrl-head{padding:12px 16px;border-bottom:1px solid var(--ink-line);display:flex;align-items:center;gap:12px;background:#11151c} +.ctrl-head .info{flex:1;min-width:0} +.ctrl-head .name{font-size:15px;font-weight:700;color:#f1f4f8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.ctrl-head .sub{font-family:var(--mono);font-size:11.5px;color:var(--faint);margin-top:1px} +.ctrl-head .close{background:none;border:none;color:var(--faint);font-size:22px;cursor:pointer;padding:0 6px;line-height:1;border-radius:6px} +.ctrl-head .close:hover{color:#fff;background:#1d242e} +.ctrl-body{flex:1;overflow-y:auto;padding:12px;background:#0a0c10} +.ctrl-body .screen{position:relative;border-radius:10px;overflow:hidden;background:#000;cursor:crosshair;user-select:none;touch-action:none} +.ctrl-body .screen img{width:100%;display:block;max-height:62vh;object-fit:contain;background:#000} +.ctrl-body .screen .loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.55);color:var(--muted);font-size:12px;border-radius:10px} +.ctrl-tools{display:flex;gap:6px;flex-wrap:wrap;padding:10px 12px 12px;border-top:1px solid var(--ink-line);background:#11151c} +.ctrl-tools .btn{background:#1a2029;border:1px solid var(--card-line);color:var(--muted);border-radius:8px;padding:6px 12px;font-size:12px;cursor:pointer;transition:all .12s;font-family:var(--body)} +.ctrl-tools .btn:hover{color:var(--teal);border-color:var(--teal)} +.ctrl-tools .btn.wake{color:var(--green);border-color:rgba(52,211,153,.4)} +.ctrl-tools .btn.wake:hover{border-color:var(--green)} +.ctrl-tools .btn.sleep{color:var(--faint)} +.ctrl-tools .text-row{display:flex;gap:6px;flex:1 0 100%;margin-top:2px} +.ctrl-tools .text-row input{flex:1;background:#0d1117;border:1px solid var(--card-line);border-radius:8px;padding:6px 10px;font-size:12px;color:var(--text);outline:none;font-family:var(--body)} +.ctrl-tools .text-row input:focus{border-color:var(--teal)} /* 滚动条 */ ::-webkit-scrollbar{width:8px} ::-webkit-scrollbar-thumb{background:#232936;border-radius:4px} @@ -87,19 +116,60 @@ main{position:relative;z-index:2;height:calc(100vh - 86px);overflow-y:auto;paddi
设备监控大屏
+
--:--:--
+ +
+
+
+
+
设备
+
--
+
+ +
+
+
+ +
连接中(首次需推送 atx-agent,最长约 30 秒)...
+
+
+
+ + + + + + + +
+ + +
+
+
+
+