Spaces:
No application file
No application file
Update browser_automation_ui.html
Browse files- browser_automation_ui.html +221 -165
browser_automation_ui.html
CHANGED
|
@@ -1,186 +1,242 @@
|
|
| 1 |
<!-- ========================= Browser Automation UI =========================
|
| 2 |
Fullโscreen screenshot โข floating logs โข collapsible sideโpanel controls
|
| 3 |
-
|
| 4 |
-
inspect overlay, confirm dialogs, log accumulation
|
| 5 |
============================================================================ -->
|
| 6 |
<!DOCTYPE html>
|
| 7 |
<html lang="en">
|
| 8 |
<head>
|
| 9 |
<meta charset="UTF-8" />
|
| 10 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 11 |
-
<title>Headless Browserย Console</title>
|
| 12 |
-
<!-- Fonts & Icons -->
|
| 13 |
<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" />
|
| 14 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
|
|
|
|
| 15 |
<style>
|
| 16 |
-
:root {
|
| 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 |
</style>
|
| 52 |
</head>
|
| 53 |
<body>
|
| 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 |
-
<aside id="inspectOverlay">
|
| 104 |
-
<h3 style="color:#38bdf8;margin-bottom:8px">Selectors</h3>
|
| 105 |
-
<ul id="inspectList"></ul>
|
| 106 |
-
<div id="inspectDetail"></div>
|
| 107 |
-
</aside>
|
| 108 |
-
|
| 109 |
-
<!-- โญโโโโโโโโโโโโโโโ Scripts โโโโโโโโโโโโโโโโโฎ -->
|
| 110 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
|
| 111 |
<script>
|
| 112 |
-
/
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
const
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
return res.json();
|
| 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 |
-
async function captureScreenshot(){
|
| 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 |
</script>
|
| 185 |
</body>
|
| 186 |
</html>
|
|
|
|
| 1 |
<!-- ========================= Browser Automation UI =========================
|
| 2 |
Fullโscreen screenshot โข floating logs โข collapsible sideโpanel controls
|
| 3 |
+
v2.1 โ fix 405 errors: use correct HTTP verbs (GET /sessions, DELETE /close)
|
|
|
|
| 4 |
============================================================================ -->
|
| 5 |
<!DOCTYPE html>
|
| 6 |
<html lang="en">
|
| 7 |
<head>
|
| 8 |
<meta charset="UTF-8" />
|
| 9 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 10 |
+
<title>Headless Browserย โ Console</title>
|
|
|
|
| 11 |
<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" />
|
| 12 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
|
| 13 |
+
|
| 14 |
<style>
|
| 15 |
+
:root {
|
| 16 |
+
--bg: #f8fafc;
|
| 17 |
+
--fg: #1e293b;
|
| 18 |
+
--primary: #2563eb;
|
| 19 |
+
--danger: #dc2626;
|
| 20 |
+
--surface: #ffffff;
|
| 21 |
+
--radius: 8px;
|
| 22 |
+
}
|
| 23 |
+
* { box-sizing: border-box; }
|
| 24 |
+
body {
|
| 25 |
+
margin: 0; font-family: Inter, sans-serif; background: var(--bg); color: var(--fg);
|
| 26 |
+
height: 100vh; overflow: hidden;
|
| 27 |
+
}
|
| 28 |
+
/* screenshot fills the viewport */
|
| 29 |
+
#mainArea { position: absolute; inset: 0; }
|
| 30 |
+
#screenshot {
|
| 31 |
+
width: 100%; height: 100%; object-fit: contain; background:#0f172a;
|
| 32 |
+
}
|
| 33 |
+
/* floating log overlay */
|
| 34 |
+
#logOverlay {
|
| 35 |
+
position: fixed; left: 0; right: 0; bottom: 0; height: 20vh;
|
| 36 |
+
background: rgba(0,0,0,.6); color:#e2e8f0; font-size:12px; font-family: "Source Code Pro", monospace;
|
| 37 |
+
overflow-y: auto; padding:4px 8px; pointer-events: none;
|
| 38 |
+
}
|
| 39 |
+
#logOverlay .entry { white-space: pre-wrap; }
|
| 40 |
+
/* side panel */
|
| 41 |
+
#sidePanel {
|
| 42 |
+
position: fixed; top:0; left:0; width: 320px; height:100vh; background:var(--surface);
|
| 43 |
+
border-right:1px solid #e2e8f0; box-shadow:2px 0 6px rgba(0,0,0,.05); overflow-y:auto;
|
| 44 |
+
transition: transform .3s ease; padding:12px;
|
| 45 |
+
}
|
| 46 |
+
#sidePanel.closed { transform: translateX(-100%); }
|
| 47 |
+
.panel-toggle { position:fixed; top:12px; left:12px; z-index:9; }
|
| 48 |
+
/* buttons */
|
| 49 |
+
.btn { cursor:pointer; border:none; border-radius:var(--radius); padding:6px 12px; margin:2px 0; font:500 14px Inter; }
|
| 50 |
+
.btn-primary { background:var(--primary); color:#fff; }
|
| 51 |
+
.btn-secondary { background:#cbd5e1; color:#334155; }
|
| 52 |
+
.btn-danger { background:var(--danger); color:#fff; }
|
| 53 |
+
/* session list */
|
| 54 |
+
#sessionList { list-style:none; padding:0; margin:4px 0; max-height:160px; overflow-y:auto; font-family:"Source Code Pro", monospace; font-size:12px; border:1px solid #e2e8f0; border-radius:var(--radius); }
|
| 55 |
+
.session-item { display:flex; align-items:center; justify-content:space-between; padding:4px 6px; cursor:pointer; }
|
| 56 |
+
.session-item.active { background:var(--primary); color:#fff; }
|
| 57 |
+
.session-item:hover { background:#e2e8f0; }
|
| 58 |
+
.session-item .close { color:var(--danger); cursor:pointer; margin-left:6px; }
|
| 59 |
+
/* inspect overlay */
|
| 60 |
+
#inspectOverlay { position:fixed; right:0; top:0; width:320px; height:100vh; background:var(--surface); border-left:1px solid #e2e8f0; box-shadow:-2px 0 6px rgba(0,0,0,.05); overflow-y:auto; transform: translateX(100%); transition:transform .3s ease; padding:12px; }
|
| 61 |
+
#inspectOverlay.open { transform:translateX(0); }
|
| 62 |
+
#selectorList { list-style:none; padding:0; margin:0; max-height:70vh; overflow-y:auto; font-size:12px; font-family:"Source Code Pro", monospace; border:1px solid #e2e8f0; border-radius:var(--radius); }
|
| 63 |
+
#selectorList li { padding:4px 6px; border-bottom:1px solid #f1f5f9; cursor:pointer; }
|
| 64 |
+
#selectorList li:hover { background:#e2e8f0; }
|
| 65 |
</style>
|
| 66 |
</head>
|
| 67 |
<body>
|
| 68 |
+
<!-- panel toggle -->
|
| 69 |
+
<button class="btn btn-secondary panel-toggle" onclick="togglePanel()"><i class="lucide" data-lucide="menu"></i></button>
|
| 70 |
+
|
| 71 |
+
<!-- side panel -->
|
| 72 |
+
<div id="sidePanel">
|
| 73 |
+
<h3>Controls</h3>
|
| 74 |
+
<button class="btn btn-primary" onclick="launchBrowser()">Launch Browser</button>
|
| 75 |
+
<button class="btn btn-secondary" onclick="navigate()">Navigate</button>
|
| 76 |
+
<input id="urlInput" placeholder="https://example.com" style="width:100%;margin-bottom:8px;" />
|
| 77 |
+
|
| 78 |
+
<hr/>
|
| 79 |
+
<h4>Screenshot</h4>
|
| 80 |
+
<button class="btn btn-secondary" onclick="captureScreenshot()">Capture</button>
|
| 81 |
+
<button class="btn btn-secondary" onclick="downloadCurrentScreenshot()">Download PNG</button>
|
| 82 |
+
|
| 83 |
+
<hr/>
|
| 84 |
+
<h4>Element Action</h4>
|
| 85 |
+
<input id="selectorInput" placeholder="CSS selector" style="width:100%;margin-bottom:4px;" />
|
| 86 |
+
<select id="actionSelect" style="width:100%;margin-bottom:4px;">
|
| 87 |
+
<option value="click">click</option>
|
| 88 |
+
<option value="type">type</option>
|
| 89 |
+
<option value="textContent">getText</option>
|
| 90 |
+
</select>
|
| 91 |
+
<input id="valueInput" placeholder="value for type" style="width:100%;margin-bottom:4px;" />
|
| 92 |
+
<button class="btn btn-secondary" onclick="elementAction()">Run</button>
|
| 93 |
+
|
| 94 |
+
<hr/>
|
| 95 |
+
<h4>Session Manager</h4>
|
| 96 |
+
<button class="btn btn-secondary" onclick="refreshSessions()">Refresh Sessions</button>
|
| 97 |
+
<button class="btn btn-danger" onclick="closeAllSessions()">Close All</button>
|
| 98 |
+
<ul id="sessionList"></ul>
|
| 99 |
+
|
| 100 |
+
<hr/>
|
| 101 |
+
<button class="btn btn-secondary" onclick="inspectPage()">Inspect Page</button>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<!-- inspect overlay -->
|
| 105 |
+
<div id="inspectOverlay">
|
| 106 |
+
<h4>Selectors <button class="btn btn-secondary" onclick="closeInspect()" style="float:right;padding:2px 6px;"><i class="lucide" data-lucide="x"></i></button></h4>
|
| 107 |
+
<ul id="selectorList"></ul>
|
| 108 |
+
<pre id="selectorDetails" style="font-size:12px;background:#f1f5f9;padding:4px;border-radius:var(--radius);"></pre>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
+
<!-- main area (screenshot) -->
|
| 112 |
+
<div id="mainArea"><img id="screenshot" alt="Screenshot will appear here" /></div>
|
| 113 |
+
|
| 114 |
+
<!-- log overlay -->
|
| 115 |
+
<div id="logOverlay"></div>
|
| 116 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
|
| 118 |
<script>
|
| 119 |
+
// โญโโโโโโโโโโโโโโโโโโโโโโโโ helpers โโโโโโโโโโโโโโโโโโโโโโโโฎ
|
| 120 |
+
let sessionId = null; let currentScreenshot = null;
|
| 121 |
+
|
| 122 |
+
function appendLog(msg, level="info") {
|
| 123 |
+
const log = document.createElement("div"); log.className="entry";
|
| 124 |
+
log.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
| 125 |
+
document.getElementById("logOverlay").append(log);
|
| 126 |
+
document.getElementById("logOverlay").scrollTop = 1e6;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
async function api(method, path, body) {
|
| 130 |
+
const res = await fetch(`/api${path}`, {
|
| 131 |
+
method,
|
| 132 |
+
headers: {"Content-Type":"application/json"},
|
| 133 |
+
body: body ? JSON.stringify(body) : undefined
|
| 134 |
+
});
|
| 135 |
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
| 136 |
return res.json();
|
| 137 |
}
|
| 138 |
+
const apiGet = path => api("GET", path);
|
| 139 |
+
const apiPost = (path, body) => api("POST", path, body);
|
| 140 |
+
const apiDelete = path => api("DELETE", path);
|
| 141 |
+
|
| 142 |
+
function togglePanel() {
|
| 143 |
+
document.getElementById("sidePanel").classList.toggle("closed");
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// โญโโโโโโโโโโโโโโโโโโโโโโโ browser ops โโโโโโโโโโโโโโโโโโโโโโฎ
|
| 147 |
+
async function launchBrowser() {
|
| 148 |
+
try {
|
| 149 |
+
const data = await apiPost("/browser/launch", {headless:true,width:1280,height:720});
|
| 150 |
+
sessionId = data.session_id; appendLog(`Launched session ${sessionId}`);
|
| 151 |
+
await refreshSessions();
|
| 152 |
+
} catch(e){ appendLog(`โ launch failed: ${e.message}`); }
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
async function navigate() {
|
| 156 |
+
if(!sessionId) return alert("No session");
|
| 157 |
+
const url=document.getElementById("urlInput").value.trim(); if(!url) return;
|
| 158 |
+
try { await apiPost("/browser/navigate", {session_id:sessionId, url}); appendLog(`Navigate โ ${url}`); }
|
| 159 |
+
catch(e){ appendLog(`โ navigate failed: ${e.message}`); }
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
async function captureScreenshot() {
|
| 163 |
+
if(!sessionId) return alert("No session");
|
| 164 |
+
try{
|
| 165 |
+
const data = await apiPost("/browser/screenshot", {session_id:sessionId, full_page:true});
|
| 166 |
+
currentScreenshot = `data:image/png;base64,${data.screenshot}`;
|
| 167 |
+
document.getElementById("screenshot").src = currentScreenshot;
|
| 168 |
+
appendLog("Screenshot captured");
|
| 169 |
+
}catch(e){ appendLog(`โ screenshot failed: ${e.message}`); }
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
function downloadCurrentScreenshot() {
|
| 173 |
+
if(!currentScreenshot) return alert("Take a screenshot first");
|
| 174 |
+
const a=document.createElement("a"); a.href=currentScreenshot; a.download=`screenshot_${Date.now()}.png`; a.click();
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
async function elementAction() {
|
| 178 |
+
if(!sessionId) return alert("No session");
|
| 179 |
+
const sel=document.getElementById("selectorInput").value.trim(); if(!sel) return;
|
| 180 |
+
const act=document.getElementById("actionSelect").value;
|
| 181 |
+
const val=document.getElementById("valueInput").value;
|
| 182 |
+
try{
|
| 183 |
+
const data = await apiPost("/elements/action", {session_id:sessionId, selector:sel, action:act, value:val});
|
| 184 |
+
appendLog(`Action ${act} on ${sel}`);
|
| 185 |
+
if(data.text) appendLog(`Returned text: ${data.text}`);
|
| 186 |
+
}catch(e){ appendLog(`โ action failed: ${e.message}`); }
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
// โญโโโโโโโโโโโโโโโโโโโโโโ session manager โโโโโโโโโโโโโโโโโโโโฎ
|
| 190 |
+
async function listSessions() {
|
| 191 |
+
try { return (await apiGet("/sessions")).sessions || []; }
|
| 192 |
+
catch(e){ appendLog(`โ listSessions: ${e.message}`); return []; }
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
async function refreshSessions() {
|
| 196 |
+
const list = await listSessions();
|
| 197 |
+
const ul=document.getElementById("sessionList"); ul.innerHTML="";
|
| 198 |
+
list.forEach(s=>{
|
| 199 |
+
const li=document.createElement("li"); li.className="session-item"; li.textContent=s.session_id;
|
| 200 |
+
if(s.session_id===sessionId) li.classList.add("active");
|
| 201 |
+
li.onclick=()=>{ sessionId=s.session_id; refreshSessions(); };
|
| 202 |
+
const close=document.createElement("span"); close.className="close"; close.innerHTML="×";
|
| 203 |
+
close.onclick=ev=>{ ev.stopPropagation(); closeSession(s.session_id); };
|
| 204 |
+
li.append(close); ul.append(li);
|
| 205 |
+
});
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
async function closeSession(id){
|
| 209 |
+
if(!confirm(`Close session ${id}?`)) return;
|
| 210 |
+
try{ await apiDelete(`/browser/close/${id}`); appendLog(`Closed ${id}`); if(id===sessionId) sessionId=null; refreshSessions(); }
|
| 211 |
+
catch(e){ appendLog(`โ close ${id}: ${e.message}`); }
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
async function closeAllSessions(){
|
| 215 |
+
const list=await listSessions(); if(!list.length) return alert("No sessions");
|
| 216 |
+
if(!confirm("Close ALL sessions? This cannot be undone.")) return;
|
| 217 |
+
for(const s of list){ try{ await apiDelete(`/browser/close/${s.session_id}`);}catch(e){ appendLog(`โ close ${s.session_id}: ${e.message}`);} }
|
| 218 |
+
sessionId=null; refreshSessions(); appendLog("All sessions closed");
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// โญโโโโโโโโโโโโโโโโโโโโโโโโ inspect overlay โโโโโโโโโโโโโโโโโโโฎ
|
| 222 |
+
async function inspectPage(){
|
| 223 |
+
if(!sessionId) return alert("No session");
|
| 224 |
+
try{
|
| 225 |
+
const data=await apiGet(`/elements/inspect/${sessionId}`);
|
| 226 |
+
const ul=document.getElementById("selectorList"); ul.innerHTML="";
|
| 227 |
+
data.elements.forEach(el=>{
|
| 228 |
+
const li=document.createElement("li"); li.textContent=el.selector; li.title=el.text;
|
| 229 |
+
li.onmouseover=()=>{ document.getElementById("selectorDetails").textContent=`${el.selector}\n${JSON.stringify(el.attributes,null,2)}`; };
|
| 230 |
+
li.onclick=()=>{ document.getElementById("selectorInput").value=el.selector; closeInspect(); };
|
| 231 |
+
ul.append(li);
|
| 232 |
+
});
|
| 233 |
+
document.getElementById("inspectOverlay").classList.add("open");
|
| 234 |
+
}catch(e){ appendLog(`โ inspect failed: ${e.message}`); }
|
| 235 |
+
}
|
| 236 |
+
function closeInspect(){ document.getElementById("inspectOverlay").classList.remove("open"); }
|
| 237 |
+
|
| 238 |
+
// init
|
| 239 |
+
lucide.createIcons(); refreshSessions();
|
| 240 |
</script>
|
| 241 |
</body>
|
| 242 |
</html>
|