Spaces:
No application file
No application file
File size: 14,496 Bytes
e908443 5c659ee d11e6d5 e908443 4e5febb cf2de85 8544ed8 d11e6d5 5c659ee cf2de85 d11e6d5 d01f58a d11e6d5 0a22178 d11e6d5 cf2de85 d11e6d5 8544ed8 d11e6d5 0a22178 d11e6d5 6090064 d11e6d5 6090064 d11e6d5 6090064 d11e6d5 0a22178 d11e6d5 42a75da b51b775 07285f4 42a75da 5057f9b 07285f4 b51b775 d11e6d5 472832d 5057f9b 472832d d11e6d5 8544ed8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
<!-- ========================= Browser Automation UI =========================
Fullโscreen screenshot โข floating logs โข collapsible sideโpanel controls
Working version โ buttons fixed, complete JS, download, session manager,
inspect overlay, confirm dialogs, log accumulation
============================================================================ -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Headless Browserย Console</title>
<!-- Fonts & Icons -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500&display=swap" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
<style>
:root { --panel-w: 280px; --accent: #2563eb; --bg: #f8fafc; --dark: #1e293b; }
*{box-sizing:border-box;font-family:'Inter',sans-serif;margin:0;padding:0}
body{height:100vh;overflow:hidden;background:#000;color:#1e293b}
/* โโโ Side panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.side-panel{position:fixed;left:0;top:0;width:var(--panel-w);height:100%;background:var(--bg);box-shadow:2px 0 6px rgba(0,0,0,.12);transition:transform .3s ease;z-index:900;overflow-y:auto}
.side-panel.closed{transform:translateX(-100%)}
.panel-toggle{position:absolute;right:-24px;top:12px;width:24px;height:24px;border:none;border-radius:4px;background:var(--accent);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px}
.panel-content{padding:14px 16px 40px 16px}
h3{font-size:16px;margin-bottom:6px;color:var(--dark)}
.btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:none;border-radius:6px;font-size:13px;cursor:pointer}
.btn-primary{background:var(--accent);color:#fff}
.btn-secondary{background:#e2e8f0;color:#334155}
.btn-danger{background:#ef4444;color:#fff}
.btn-sm{padding:4px 8px;font-size:11px}
.form-group{display:flex;flex-direction:column;margin-bottom:8px}
.form-group input,.form-group select{padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;font-size:13px}
hr{margin:12px 0;border:none;border-top:1px solid #e2e8f0}
/* โโโ Main screenshot area โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
#mainArea{position:fixed;left:0;top:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;transition:margin-left .3s ease}
#mainArea.with-panel{margin-left:var(--panel-w)}
#screenshot{max-width:100%;max-height:100%;object-fit:contain;background:#000}
/* โโโ Log overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
#logOverlay{position:fixed;left:0;bottom:0;width:100%;height:20vh;background:rgba(30,41,59,0.75);color:#f1f5f9;font-size:12px;overflow-y:auto;padding:6px 10px;z-index:950;backdrop-filter:blur(3px)}
#logOverlay pre{margin:0;white-space:pre-wrap;word-break:break-word}
/* โโโ Session list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.session-item{display:flex;align-items:center;justify-content:space-between;padding:4px 6px;border-bottom:1px solid #e2e8f0;font-family:"Source Code Pro",monospace;font-size:12px}
.session-id{cursor:pointer;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.session-item.active{background:#dbeafe}
.session-close{background:none;border:none;color:#ef4444;cursor:pointer;font-size:20px;width:24px;height:24px;display:flex;align-items:right;justify-content:right}
/* โโโ Inspect overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
#inspectOverlay{position:fixed;right:0;top:0;width:300px;height:100%;background:rgba(15,23,42,0.95);color:#f8fafc;z-index:940;transform:translateX(100%);transition:transform .3s ease;overflow-y:auto;padding:10px;font-size:12px}
#inspectOverlay.open{transform:translateX(0)}
#inspectList li{padding:4px 6px;border-bottom:1px solid rgba(255,255,255,.08);cursor:pointer}
#inspectList li:hover{background:rgba(255,255,255,.08)}
#inspectDetail{margin-top:6px;font-size:11px;color:#cbd5e1;white-space:pre-wrap}
</style>
</head>
<body>
<!-- โญโโโโโโโโโโโโโโโ Side Panel โโโโโโโโโโโโโโโโโฎ -->
<aside id="sidePanel" class="side-panel open">
<button id="togglePanel" class="panel-toggle" title="Hide / Show panel">โฎ</button>
<div class="panel-content">
<!-- Browser Controls -->
<section>
<h3>Browser</h3>
<div class="form-group"><button class="btn btn-primary" onclick="launchBrowser()"><i class="lucide lucide-rocket"></i>Launch</button></div>
<div class="form-group"><input id="navUrl" placeholder="https://example.com"/></div>
<div class="form-group"><button class="btn btn-secondary" onclick="navigate()"><i class="lucide lucide-globe"></i>Navigate</button></div>
<div class="form-group" style="display:flex;gap:6px">
<button class="btn btn-secondary" onclick="captureScreenshot()"><i class="lucide lucide-camera"></i>Capture</button>
<button class="btn btn-secondary" onclick="downloadCurrentScreenshot()"><i class="lucide lucide-download"></i>Download</button>
</div>
<div class="form-group"><button class="btn btn-danger" onclick="closeAllSessions()"><i class="lucide lucide-trash-2"></i>Closeย All</button></div>
</section>
<hr/>
<!-- Element Interaction -->
<section>
<h3>Element</h3>
<div class="form-group"><input id="selector" placeholder="CSS selector"/></div>
<div class="form-group"><select id="action">
<option value="click">click</option>
<option value="type">type</option>
<option value="textContent">getText</option>
</select></div>
<div class="form-group" id="typeTextGroup" style="display:none"><input id="typeText" placeholder="text to type"/></div>
<button class="btn btn-primary" onclick="elementAction()"><i class="lucide lucide-mouse-pointer-click"></i>Run</button>
<button class="btn btn-secondary btn-sm" style="margin-left:6px" onclick="inspectPage()"><i class="lucide lucide-list"></i>Inspect</button>
</section>
<hr/>
<!-- Session Manager -->
<section>
<h3>Sessions</h3>
<button class="btn btn-secondary btn-sm" style="margin-bottom:6px" onclick="refreshSessions()"><i class="lucide lucide-refresh-ccw"></i>Refresh</button>
<ul id="sessionList"></ul>
</section>
</div>
</aside>
<!-- โญโโโโโโโโโโโโโโโ Main Area โโโโโโโโโโโโโโโโโฎ -->
<main id="mainArea" class="with-panel">
<img id="screenshot" alt="Browser Screenshot" />
</main>
<!-- โญโโโโโโโโโโโโโโโ Log Overlay โโโโโโโโโโโโโโโโโฎ -->
<div id="logOverlay"><pre id="logText"></pre></div>
<!-- โญโโโโโโโโโโโโโโโ Inspect Overlay โโโโโโโโโโโโโโโโโฎ -->
<aside id="inspectOverlay">
<h3 style="color:#38bdf8;margin-bottom:8px">Selectors</h3>
<ul id="inspectList"></ul>
<div id="inspectDetail"></div>
</aside>
<!-- โญโโโโโโโโโโโโโโโ Scripts โโโโโโโโโโโโโโโโโฎ -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
<script>
/* โโโโโโโโโโ Fetch helpers โโโโโโโโโโ */
const API = '/api';
const headers = {'Content-Type':'application/json'};
async function apiPost(path, body={}){
const res = await fetch(`${API}${path}`,{method:'POST',headers,body:JSON.stringify(body)});
if(!res.ok) throw new Error(await res.text());
return res.json();
}
async function apiGet(path){const res=await fetch(`${API}${path}`);if(!res.ok)throw new Error(await res.text());return res.json();}
/* โโโโโโโโโโ State & utils โโโโโโโโโโ */
let currentSessionId=null, lastScreenshot=null;
const logBox=document.getElementById('logText');
function log(msg){logBox.textContent+=`\n${new Date().toLocaleTimeString()} ${msg}`;logBox.parentElement.scrollTop=logBox.parentElement.scrollHeight;}
function handleErr(e){console.error(e);log(`โ ${e.message||e}`);alert(e.message||e);}
/* โโโโโโโโโโ UI helpers โโโโโโโโโโ */
document.getElementById('togglePanel').onclick=()=>{
const panel=document.getElementById('sidePanel');panel.classList.toggle('closed');
document.getElementById('mainArea').classList.toggle('with-panel');
document.getElementById('togglePanel').textContent=panel.classList.contains('closed')?'โฏ':'โฎ';};
document.getElementById('action').addEventListener('change',e=>{
document.getElementById('typeTextGroup').style.display=e.target.value==='type'?'block':'none';});
/* โโโโโโโโโโ Browser / session functions โโโโโโโโโโ */
async function launchBrowser(){try{
const res=await apiPost('/browser/launch',{});currentSessionId=res.session_id;log(`Launched ${currentSessionId}`);await refreshSessions();await captureScreenshot();}catch(e){handleErr(e)}}
async function navigate(){if(!currentSessionId)return alert('No session');const url=document.getElementById('navUrl').value.trim();if(!url)return;
try{await apiPost('/browser/navigate',{session_id:currentSessionId,url});log(`โก ${url}`);await captureScreenshot();}catch(e){handleErr(e)}}
async function captureScreenshot(){if(!currentSessionId)return;try{
const res=await apiPost('/browser/screenshot',{session_id:currentSessionId,full_page:false});lastScreenshot=res.screenshot;document.getElementById('screenshot').src=`data:image/png;base64,${lastScreenshot}`;log('๐ธ screenshot');}catch(e){handleErr(e)}}
function downloadCurrentScreenshot(){if(!lastScreenshot)return alert('No screenshot');const a=document.createElement('a');a.href=`data:image/png;base64,${lastScreenshot}`;a.download=`screenshot_${Date.now()}.png`;a.click();}
async function apiDelete(path) {
const r = await fetch('/api' + path, { method: 'DELETE' });
if (!r.ok) throw new Error(await r.text());
return r.json ? r.json() : {};
}
async function closeSession(id){
if(!confirm(`Close session\n${id}?`))return;
try{
await apiDelete(`/browser/close/${id}`);
log(`โ closed ${id}`);
if(id===currentSessionId){currentSessionId=null;document.getElementById('screenshot').src='';}
await refreshSessions();
}catch(e){
handleErr(e)
}
}
async function closeAllSessions(){
const list=await listSessions();
if(!list.length) return alert("No sessions");
if(!confirm("Close ALL sessions? This cannot be undone.")) return;
try{
for(const s of list){
await apiDelete(`/browser/close/${s.session_id}`);
}
sessionId=null; refreshSessions(); appendLog("All sessions closed");
}
catch(e){
appendLog(`โ close ${s.session_id}: ${e.message}`);
}
}
async function refreshSessions(){try{
const res=await apiGet('/sessions');const ul=document.getElementById('sessionList');ul.innerHTML='';(res.sessions||[]).forEach(s=>{
const li=document.createElement('li');li.className='session-item';li.dataset.id=s.session_id;
li.innerHTML=`<span class='session-id'>${s.session_id}</span><button class='session-close' title='Close'>×</button>`;
if(s.session_id===currentSessionId)li.classList.add('active');
li.querySelector('.session-id').onclick=()=>{currentSessionId=s.session_id;log(`๐ switched to ${s.session_id}`);refreshSessions();};
li.querySelector('.session-close').onclick=()=>closeSession(s.session_id);
ul.appendChild(li);});
}catch(e){handleErr(e)}}
/* โโโโโโโโโโ Element interaction โโโโโโโโโโ */
async function elementAction(){if(!currentSessionId)return;const sel=document.getElementById('selector').value.trim();if(!sel)return alert('No selector');
const action=document.getElementById('action').value;const value=document.getElementById('typeText').value;
try{const res=await apiPost('/elements/action',{session_id:currentSessionId,selector:sel,action,value});
log(`โ
${action} on ${sel}`);if(action!=='textContent')await captureScreenshot();else alert(`Element text:\n${res.text}`);}catch(e){handleErr(e)}}
/* โโโโโโโโโโ Inspect overlay โโโโโโโโโโ */
async function inspectPage(){if(!currentSessionId)return;const ov=document.getElementById('inspectOverlay');ov.classList.add('open');ov.querySelector('#inspectList').innerHTML='<li>Loading...</li>';
try{const res=await apiGet(`/elements/inspect/${currentSessionId}`);const list=ov.querySelector('#inspectList');list.innerHTML='';
(res.elements||[]).forEach(el=>{const li=document.createElement('li');li.textContent=el.selector;li.onmouseover=()=>{document.getElementById('inspectDetail').textContent=`<${el.tag}> ${el.text}\n`+JSON.stringify(el.attributes,null,2)};
li.onclick=()=>{document.getElementById('selector').value=el.selector;document.getElementById('inspectOverlay').classList.remove('open');};list.appendChild(li);});
}catch(e){handleErr(e)}}
document.addEventListener('keydown',e=>{if(e.key==='Escape')document.getElementById('inspectOverlay').classList.remove('open');});
/* โโโโโโโโโโ Initial UI setup โโโโโโโโโโ */
refreshSessions();log('UI ready');
</script>
</body>
</html>
|