Spaces:
No application file
No application file
Update browser_automation_ui.html
Browse files- browser_automation_ui.html +178 -231
browser_automation_ui.html
CHANGED
|
@@ -1,263 +1,210 @@
|
|
| 1 |
<!-- ========================= Browser Automation UI =========================
|
| 2 |
Fullโscreen screenshot โข floating logs โข collapsible sideโpanel controls
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
โข all button handlers implemented
|
| 6 |
-
โข robust fetch helpers with logging
|
| 7 |
============================================================================ -->
|
| 8 |
<!DOCTYPE html>
|
| 9 |
<html lang="en">
|
| 10 |
<head>
|
| 11 |
<meta charset="UTF-8" />
|
| 12 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 13 |
-
<title>Headless Browser
|
| 14 |
-
|
| 15 |
-
<!-- Fonts -->
|
| 16 |
<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" />
|
| 17 |
-
|
| 18 |
-
<!-- Lucide CSS (icons) -->
|
| 19 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
|
| 20 |
-
<!-- Lucide JS (UMD build) MUST be before our script -->
|
| 21 |
-
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/umd/lucide.min.js" crossorigin="anonymous"></script>
|
| 22 |
|
| 23 |
<style>
|
| 24 |
-
:root {
|
| 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 |
-
.session-item
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
| 52 |
#inspectOverlay.open{transform:translateX(0)}
|
| 53 |
-
#
|
| 54 |
-
#
|
| 55 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
</style>
|
| 57 |
</head>
|
| 58 |
<body>
|
| 59 |
-
<!--
|
| 60 |
-
<
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
</div>
|
|
|
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
<
|
| 74 |
-
|
| 75 |
-
<h4>Element Action</h4>
|
| 76 |
-
<input id="selectorInput" placeholder="CSS selector" style="width:100%;padding:4px;margin-bottom:4px" />
|
| 77 |
-
<select id="actionSelect" style="width:100%;padding:4px;margin-bottom:4px">
|
| 78 |
-
<option value="click">click</option>
|
| 79 |
-
<option value="type">type</option>
|
| 80 |
-
<option value="textContent">getText</option>
|
| 81 |
-
</select>
|
| 82 |
-
<input id="valueInput" placeholder="value for type" style="width:100%;padding:4px;margin-bottom:4px" />
|
| 83 |
-
<button class="btn" onclick="elementAction()"><i class="lucide" data-lucide="play"></i>Run</button>
|
| 84 |
-
|
| 85 |
-
<h4>Sessions</h4>
|
| 86 |
-
<button class="btn" onclick="refreshSessions()"><i class="lucide" data-lucide="refresh-ccw"></i>Refresh</button>
|
| 87 |
-
<button class="btn btn-danger" onclick="closeAllSessions()"><i class="lucide" data-lucide="trash-2"></i>Closeย All</button>
|
| 88 |
-
<ul id="sessionList"></ul>
|
| 89 |
-
|
| 90 |
-
<button class="btn" onclick="inspectPage()"><i class="lucide" data-lucide="search"></i>Inspect Page</button>
|
| 91 |
-
</div>
|
| 92 |
-
|
| 93 |
-
<!-- inspect overlay -->
|
| 94 |
-
<div id="inspectOverlay">
|
| 95 |
-
<h4 style="margin-top:0">Selectors <button class="btn" style="padding:2px 6px" onclick="closeInspect()"><i class="lucide" data-lucide="x"></i></button></h4>
|
| 96 |
-
<ul id="selectorList"></ul>
|
| 97 |
-
<pre id="selectorDetails" style="font-size:12px;background:#f1f5f9;padding:4px;border-radius:var(--radius);white-space:pre-wrap"></pre>
|
| 98 |
-
</div>
|
| 99 |
-
|
| 100 |
-
<!-- main screenshot area -->
|
| 101 |
-
<div id="mainArea"><img id="screenshot" alt="Screenshot" /></div>
|
| 102 |
-
|
| 103 |
-
<!-- logs -->
|
| 104 |
-
<div id="logOverlay"></div>
|
| 105 |
-
|
| 106 |
-
<!-- Application script (executes after Lucide) -->
|
| 107 |
-
<script defer>
|
| 108 |
-
// Wait until Lucide and DOM ready
|
| 109 |
-
document.addEventListener("DOMContentLoaded", () => {
|
| 110 |
-
// ensure lucide global exists
|
| 111 |
-
if (typeof lucide === "undefined") {
|
| 112 |
-
console.error("Lucide failed to load โ icons disabled");
|
| 113 |
-
} else {
|
| 114 |
-
lucide.createIcons();
|
| 115 |
-
}
|
| 116 |
-
initApp();
|
| 117 |
-
});
|
| 118 |
-
|
| 119 |
-
/* โโโโโโโโโโโโโโโโโโโโโโโโโ State โโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 120 |
-
let sessionId = null;
|
| 121 |
-
let currentScreenshot = null;
|
| 122 |
-
|
| 123 |
-
/* โโโโโโโโโโโโโโโโโโโโโโโโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโ */
|
| 124 |
-
function appendLog(msg, level="info") {
|
| 125 |
-
const div = document.createElement("div");
|
| 126 |
-
div.className="entry";
|
| 127 |
-
div.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
|
| 128 |
-
document.getElementById("logOverlay").append(div);
|
| 129 |
-
document.getElementById("logOverlay").scrollTop = 9e9;
|
| 130 |
-
}
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
const res = await fetch(`/api${path}`, {
|
| 135 |
-
method,
|
| 136 |
-
headers: {"Content-Type":"application/json"},
|
| 137 |
-
body: body ? JSON.stringify(body) : undefined
|
| 138 |
-
});
|
| 139 |
-
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
| 140 |
-
return await res.json();
|
| 141 |
-
} catch (err) {
|
| 142 |
-
appendLog(`โ ${method} ${path} โ ${err.message}`);
|
| 143 |
-
throw err;
|
| 144 |
-
}
|
| 145 |
-
}
|
| 146 |
-
const apiGet = path => api("GET", path);
|
| 147 |
-
const apiPost = (path, body) => api("POST", path, body);
|
| 148 |
-
const apiDelete = path => api("DELETE", path);
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
-
/* โโโโโโโโโโโโโโโโโโโโโโโโโ Sessions โโโโโโโโโโโโโโโโโโโโโโโ */
|
| 156 |
-
async function listSessions(){
|
| 157 |
-
const j = await apiGet("/sessions");
|
| 158 |
-
return j.sessions || [];
|
| 159 |
-
}
|
| 160 |
|
| 161 |
-
async function refreshSessions(){
|
| 162 |
-
const ul = document.getElementById("sessionList");
|
| 163 |
-
ul.innerHTML="";
|
| 164 |
-
const sessions = await listSessions();
|
| 165 |
-
sessions.forEach(s=>{
|
| 166 |
-
const li=document.createElement("li");
|
| 167 |
-
li.className="session-item";
|
| 168 |
-
if(s.session_id===sessionId) li.classList.add("active");
|
| 169 |
-
li.textContent = s.session_id;
|
| 170 |
-
li.onclick=()=>{sessionId=s.session_id;refreshSessions();appendLog(`Switched to ${sessionId}`)};
|
| 171 |
-
const close = document.createElement("span");
|
| 172 |
-
close.className="close"; close.innerHTML="×";
|
| 173 |
-
close.onclick = (e)=>{e.stopPropagation(); closeSession(s.session_id);} ;
|
| 174 |
-
li.append(close);
|
| 175 |
-
ul.append(li);
|
| 176 |
-
});
|
| 177 |
-
}
|
| 178 |
|
| 179 |
-
async function closeSession(id){
|
| 180 |
-
if(!confirm(`Close session ${id}?`)) return;
|
| 181 |
-
await apiDelete(`/browser/close/${id}`);
|
| 182 |
-
appendLog(`Closed session ${id}`);
|
| 183 |
-
if(sessionId===id) sessionId=null;
|
| 184 |
-
refreshSessions();
|
| 185 |
-
}
|
| 186 |
|
| 187 |
-
async function closeAllSessions(){
|
| 188 |
-
const sessions=await listSessions();
|
| 189 |
-
if(!sessions.length){alert("No sessions");return;}
|
| 190 |
-
if(!confirm("Close ALL sessions?")) return;
|
| 191 |
-
for(const s of sessions){ await apiDelete(`/browser/close/${s.session_id}`);}ย
|
| 192 |
-
sessionId=null;
|
| 193 |
-
appendLog("Closed all sessions");
|
| 194 |
-
refreshSessions();
|
| 195 |
-
}
|
| 196 |
|
| 197 |
-
/* โโโโโโโโโโโโโโโโโโโ Browser operations โโโโโโโโโโโโโโโโโโโ */
|
| 198 |
-
async function launchBrowser(){
|
| 199 |
-
const res = await apiPost("/browser/launch", {headless:true,width:1920,height:1080});
|
| 200 |
-
sessionId = res.session_id;
|
| 201 |
-
appendLog(`Launched ${sessionId}`);
|
| 202 |
-
await refreshSessions();
|
| 203 |
-
}
|
| 204 |
|
| 205 |
-
async function navigate(){
|
| 206 |
-
if(!sessionId){alert("No session");return;}
|
| 207 |
-
const url=document.getElementById("urlInput").value.trim();
|
| 208 |
-
if(!url){alert("Enter URL");return;}
|
| 209 |
-
await apiPost("/browser/navigate", {session_id:sessionId,url});
|
| 210 |
-
appendLog(`Navigated to ${url}`);
|
| 211 |
-
}
|
| 212 |
|
| 213 |
-
async function captureScreenshot(){
|
| 214 |
-
if(!sessionId){alert("No session");return;}
|
| 215 |
-
const j = await apiPost("/browser/screenshot", {session_id:sessionId,full_page:false});
|
| 216 |
-
currentScreenshot = `data:image/png;base64,${j.screenshot}`;
|
| 217 |
-
document.getElementById("screenshot").src=currentScreenshot;
|
| 218 |
-
appendLog("Screenshot captured");
|
| 219 |
-
}
|
| 220 |
|
| 221 |
-
function downloadCurrentScreenshot(){
|
| 222 |
-
if(!currentScreenshot){alert("Capture a screenshot first");return;}
|
| 223 |
-
const a=document.createElement("a");
|
| 224 |
-
a.href=currentScreenshot; a.download=`screenshot_${Date.now()}.png`; a.click();
|
| 225 |
-
appendLog("Screenshot downloaded");
|
| 226 |
-
}
|
| 227 |
|
| 228 |
-
async function elementAction(){
|
| 229 |
-
if(!sessionId){alert("No session");return;}
|
| 230 |
-
const selector=document.getElementById("selectorInput").value.trim();
|
| 231 |
-
const action=document.getElementById("actionSelect").value;
|
| 232 |
-
const value=document.getElementById("valueInput").value;
|
| 233 |
-
if(!selector){alert("Enter selector");return;}
|
| 234 |
-
const body={session_id:sessionId,selector,action,value};
|
| 235 |
-
const j=await apiPost("/elements/action", body);
|
| 236 |
-
appendLog(`Action ${action} on ${selector} โ`);
|
| 237 |
-
if(j.text){appendLog(`โ textContent: ${j.text}`);}
|
| 238 |
-
}
|
| 239 |
|
| 240 |
-
/* โโโโโโโโโโโโโโโโโโโโโ Inspect overlay โโโโโโโโโโโโโโโโโโโโ */
|
| 241 |
-
function closeInspect(){document.getElementById("inspectOverlay").classList.remove("open")}
|
| 242 |
-
async function inspectPage(){
|
| 243 |
-
if(!sessionId){alert("No session");return;}
|
| 244 |
-
const j = await apiGet(`/elements/inspect/${sessionId}`);
|
| 245 |
-
const list=document.getElementById("selectorList"); list.innerHTML="";
|
| 246 |
-
j.elements.forEach(el=>{
|
| 247 |
-
const li=document.createElement("li");
|
| 248 |
-
li.textContent=el.selector;
|
| 249 |
-
li.onmouseenter=()=>{document.getElementById("selectorDetails").textContent=JSON.stringify(el,null,2)};
|
| 250 |
-
li.onclick=()=>{document.getElementById("selectorInput").value=el.selector;closeInspect();};
|
| 251 |
-
list.append(li);
|
| 252 |
-
});
|
| 253 |
-
document.getElementById("inspectOverlay").classList.add("open");
|
| 254 |
-
}
|
| 255 |
|
| 256 |
-
|
| 257 |
-
function initApp(){
|
| 258 |
-
refreshSessions();
|
| 259 |
-
appendLog("UI ready");
|
| 260 |
}
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<!-- ========================= Browser Automation UI =========================
|
| 2 |
Fullโscreen screenshot โข floating logs โข collapsible sideโpanel controls
|
| 3 |
+
Working version โ buttons fixed, complete JS, download, session manager,
|
| 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 |
|
| 16 |
<style>
|
| 17 |
+
:root { --panel-w: 280px; --accent: #2563eb; --bg: #f8fafc; --dark: #1e293b; }
|
| 18 |
+
*{box-sizing:border-box;font-family:'Inter',sans-serif;margin:0;padding:0}
|
| 19 |
+
body{height:100vh;overflow:hidden;background:#000;color:#1e293b}
|
| 20 |
+
/* โโโ Side panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 21 |
+
.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}
|
| 22 |
+
.side-panel.closed{transform:translateX(-100%)}
|
| 23 |
+
.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}
|
| 24 |
+
.panel-content{padding:14px 16px 40px 16px}
|
| 25 |
+
h3{font-size:16px;margin-bottom:6px;color:var(--dark)}
|
| 26 |
+
.btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:none;border-radius:6px;font-size:13px;cursor:pointer}
|
| 27 |
+
.btn-primary{background:var(--accent);color:#fff}
|
| 28 |
+
.btn-secondary{background:#e2e8f0;color:#334155}
|
| 29 |
+
.btn-danger{background:#ef4444;color:#fff}
|
| 30 |
+
.btn-sm{padding:4px 8px;font-size:11px}
|
| 31 |
+
.form-group{display:flex;flex-direction:column;margin-bottom:8px}
|
| 32 |
+
.form-group input,.form-group select{padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;font-size:13px}
|
| 33 |
+
hr{margin:12px 0;border:none;border-top:1px solid #e2e8f0}
|
| 34 |
+
/* โโโ Main screenshot area โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 35 |
+
#mainArea{position:fixed;left:0;top:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;transition:margin-left .3s ease}
|
| 36 |
+
#mainArea.with-panel{margin-left:var(--panel-w)}
|
| 37 |
+
#screenshot{max-width:100%;max-height:100%;object-fit:contain;background:#000}
|
| 38 |
+
/* โโโ Log overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 39 |
+
#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)}
|
| 40 |
+
#logOverlay pre{margin:0;white-space:pre-wrap;word-break:break-word}
|
| 41 |
+
/* โโโ Session list โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 42 |
+
.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}
|
| 43 |
+
.session-id{cursor:pointer;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 44 |
+
.session-item.active{background:#dbeafe}
|
| 45 |
+
.session-close{background:none;border:none;color:#ef4444;cursor:pointer;font-size:14px;width:18px;height:18px;display:flex;align-items:center;justify-content:center}
|
| 46 |
+
/* โโโ Inspect overlay โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
|
| 47 |
+
#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}
|
| 48 |
#inspectOverlay.open{transform:translateX(0)}
|
| 49 |
+
#inspectList li{padding:4px 6px;border-bottom:1px solid rgba(255,255,255,.08);cursor:pointer}
|
| 50 |
+
#inspectList li:hover{background:rgba(255,255,255,.08)}
|
| 51 |
+
#inspectDetail{margin-top:6px;font-size:11px;color:#cbd5e1;white-space:pre-wrap}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
|
| 67 |
</style>
|
| 68 |
</head>
|
| 69 |
<body>
|
| 70 |
+
<!-- โญโโโโโโโโโโโโโโโ Side Panel โโโโโโโโโโโโโโโโโฎ -->
|
| 71 |
+
<aside id="sidePanel" class="side-panel open">
|
| 72 |
+
<button id="togglePanel" class="panel-toggle" title="Hide / Show panel">โฎ</button>
|
| 73 |
+
<div class="panel-content">
|
| 74 |
+
<!-- Browser Controls -->
|
| 75 |
+
<section>
|
| 76 |
+
<h3>Browser</h3>
|
| 77 |
+
<div class="form-group"><button class="btn btn-primary" onclick="launchBrowser()"><i class="lucide lucide-rocket"></i>Launch</button></div>
|
| 78 |
+
<div class="form-group"><input id="navUrl" placeholder="https://example.com"/></div>
|
| 79 |
+
<div class="form-group"><button class="btn btn-secondary" onclick="navigate()"><i class="lucide lucide-globe"></i>Navigate</button></div>
|
| 80 |
+
<div class="form-group" style="display:flex;gap:6px">
|
| 81 |
+
<button class="btn btn-secondary" onclick="captureScreenshot()"><i class="lucide lucide-camera"></i>Capture</button>
|
| 82 |
+
<button class="btn btn-secondary" onclick="downloadCurrentScreenshot()"><i class="lucide lucide-download"></i>Download</button>
|
| 83 |
+
</div>
|
| 84 |
+
<div class="form-group"><button class="btn btn-danger" onclick="closeAllSessions()"><i class="lucide lucide-trash-2"></i>Close All</button></div>
|
| 85 |
+
</section>
|
| 86 |
+
<hr/>
|
| 87 |
+
<!-- Element Interaction -->
|
| 88 |
+
<section>
|
| 89 |
+
<h3>Element</h3>
|
| 90 |
+
<div class="form-group"><input id="selector" placeholder="CSS selector"/></div>
|
| 91 |
+
<div class="form-group"><select id="action">
|
| 92 |
+
<option value="click">click</option>
|
| 93 |
+
<option value="type">type</option>
|
| 94 |
+
<option value="textContent">getText</option>
|
| 95 |
+
</select></div>
|
| 96 |
+
<div class="form-group" id="typeTextGroup" style="display:none"><input id="typeText" placeholder="text to type"/></div>
|
| 97 |
+
<button class="btn btn-primary" onclick="elementAction()"><i class="lucide lucide-mouse-pointer-click"></i>Run</button>
|
| 98 |
+
<button class="btn btn-secondary btn-sm" style="margin-left:6px" onclick="inspectPage()"><i class="lucide lucide-list"></i>Inspect</button>
|
| 99 |
+
</section>
|
| 100 |
+
<hr/>
|
| 101 |
+
<!-- Session Manager -->
|
| 102 |
+
<section>
|
| 103 |
+
<h3>Sessions</h3>
|
| 104 |
+
<button class="btn btn-secondary btn-sm" style="margin-bottom:6px" onclick="refreshSessions()"><i class="lucide lucide-refresh-ccw"></i>Refresh</button>
|
| 105 |
+
<ul id="sessionList"></ul>
|
| 106 |
+
</section>
|
| 107 |
</div>
|
| 108 |
+
</aside>
|
| 109 |
|
| 110 |
+
<!-- โญโโโโโโโโโโโโโโโ Main Area โโโโโโโโโโโโโโโโโฎ -->
|
| 111 |
+
<main id="mainArea" class="with-panel">
|
| 112 |
+
<img id="screenshot" alt="Browser Screenshot" />
|
| 113 |
+
</main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
+
<!-- โญโโโโโโโโโโโโโโโ Log Overlay โโโโโโโโโโโโโโโโโฎ -->
|
| 116 |
+
<div id="logOverlay"><pre id="logText"></pre></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
+
<!-- โญโโโโโโโโโโโโโโโ Inspect Overlay โโโโโโโโโโโโโโโโโฎ -->
|
| 119 |
+
<aside id="inspectOverlay">
|
| 120 |
+
<h3 style="color:#38bdf8;margin-bottom:8px">Selectors</h3>
|
| 121 |
+
<ul id="inspectList"></ul>
|
| 122 |
+
<div id="inspectDetail"></div>
|
| 123 |
+
</aside>
|
| 124 |
+
|
| 125 |
+
<!-- โญโโโโโโโโโโโโโโโ Scripts โโโโโโโโโโโโโโโโโฎ -->
|
| 126 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
|
| 127 |
+
<script>
|
| 128 |
+
/* โโโโโโโโโโ Fetch helpers โโโโโโโโโโ */
|
| 129 |
+
const API = '/api';
|
| 130 |
+
const headers = {'Content-Type':'application/json'};
|
| 131 |
+
async function apiPost(path, body={}){
|
| 132 |
+
const res = await fetch(`${API}${path}`,{method:'POST',headers,body:JSON.stringify(body)});
|
| 133 |
+
if(!res.ok) throw new Error(await res.text());
|
| 134 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
+
return res.json();
|
|
|
|
|
|
|
|
|
|
| 146 |
}
|
| 147 |
+
async function apiGet(path){const res=await fetch(`${API}${path}`);if(!res.ok)throw new Error(await res.text());return res.json();}
|
| 148 |
+
|
| 149 |
+
/* โโโโโโโโโโ State & utils โโโโโโโโโโ */
|
| 150 |
+
let currentSessionId=null, lastScreenshot=null;
|
| 151 |
+
const logBox=document.getElementById('logText');
|
| 152 |
+
function log(msg){logBox.textContent+=`\n${new Date().toLocaleTimeString()} ${msg}`;logBox.parentElement.scrollTop=logBox.parentElement.scrollHeight;}
|
| 153 |
+
function handleErr(e){console.error(e);log(`โ ${e.message||e}`);alert(e.message||e);}
|
| 154 |
+
|
| 155 |
+
/* โโโโโโโโโโ UI helpers โโโโโโโโโโ */
|
| 156 |
+
document.getElementById('togglePanel').onclick=()=>{
|
| 157 |
+
const panel=document.getElementById('sidePanel');panel.classList.toggle('closed');
|
| 158 |
+
document.getElementById('mainArea').classList.toggle('with-panel');
|
| 159 |
+
document.getElementById('togglePanel').textContent=panel.classList.contains('closed')?'โฏ':'โฎ';};
|
| 160 |
+
|
| 161 |
+
document.getElementById('action').addEventListener('change',e=>{
|
| 162 |
+
document.getElementById('typeTextGroup').style.display=e.target.value==='type'?'block':'none';});
|
| 163 |
+
|
| 164 |
+
/* โโโโโโโโโโ Browser / session functions โโโโโโโโโโ */
|
| 165 |
+
async function launchBrowser(){try{
|
| 166 |
+
const res=await apiPost('/browser/launch',{});currentSessionId=res.session_id;log(`Launched ${currentSessionId}`);await refreshSessions();await captureScreenshot();}catch(e){handleErr(e)}}
|
| 167 |
+
|
| 168 |
+
async function navigate(){if(!currentSessionId)return alert('No session');const url=document.getElementById('navUrl').value.trim();if(!url)return;
|
| 169 |
+
try{await apiPost('/browser/navigate',{session_id:currentSessionId,url});log(`โก ${url}`);await captureScreenshot();}catch(e){handleErr(e)}}
|
| 170 |
+
|
| 171 |
+
async function captureScreenshot(){if(!currentSessionId)return;try{
|
| 172 |
+
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)}}
|
| 173 |
+
|
| 174 |
+
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();}
|
| 175 |
+
|
| 176 |
+
async function closeSession(id){if(!confirm(`Close session\n${id}?`))return;try{
|
| 177 |
+
await apiPost(`/browser/close/${id}`,{});log(`โ closed ${id}`);if(id===currentSessionId){currentSessionId=null;document.getElementById('screenshot').src='';}
|
| 178 |
+
await refreshSessions();}catch(e){handleErr(e)}}
|
| 179 |
+
|
| 180 |
+
async function closeAllSessions(){const list=document.querySelectorAll('.session-item');if(!list.length)return alert('No sessions');
|
| 181 |
+
if(!confirm(`Close ALL ${list.length} sessions?`))return;try{
|
| 182 |
+
for(const li of list){await apiPost(`/browser/close/${li.dataset.id}`,{});}currentSessionId=null;document.getElementById('screenshot').src='';lastScreenshot=null;await refreshSessions();log('โ all sessions closed');}catch(e){handleErr(e)}}
|
| 183 |
+
|
| 184 |
+
async function refreshSessions(){try{
|
| 185 |
+
const res=await apiGet('/sessions');const ul=document.getElementById('sessionList');ul.innerHTML='';(res.sessions||[]).forEach(s=>{
|
| 186 |
+
const li=document.createElement('li');li.className='session-item';li.dataset.id=s.session_id;
|
| 187 |
+
li.innerHTML=`<span class='session-id'>${s.session_id}</span><button class='session-close' title='Close'>×</button>`;
|
| 188 |
+
if(s.session_id===currentSessionId)li.classList.add('active');
|
| 189 |
+
li.querySelector('.session-id').onclick=()=>{currentSessionId=s.session_id;log(`๐ switched to ${s.session_id}`);refreshSessions();};
|
| 190 |
+
li.querySelector('.session-close').onclick=()=>closeSession(s.session_id);
|
| 191 |
+
ul.appendChild(li);});
|
| 192 |
+
}catch(e){handleErr(e)}}
|
| 193 |
+
|
| 194 |
+
/* โโโโโโโโโโ Element interaction โโโโโโโโโโ */
|
| 195 |
+
async function elementAction(){if(!currentSessionId)return;const sel=document.getElementById('selector').value.trim();if(!sel)return alert('No selector');
|
| 196 |
+
const action=document.getElementById('action').value;const value=document.getElementById('typeText').value;
|
| 197 |
+
try{const res=await apiPost('/elements/action',{session_id:currentSessionId,selector:sel,action,value});
|
| 198 |
+
log(`โ
${action} on ${sel}`);if(action!=='textContent')await captureScreenshot();else alert(`Element text:\n${res.text}`);}catch(e){handleErr(e)}}
|
| 199 |
+
|
| 200 |
+
/* โโโโโโโโโโ Inspect overlay โโโโโโโโโโ */
|
| 201 |
+
async function inspectPage(){if(!currentSessionId)return;const ov=document.getElementById('inspectOverlay');ov.classList.add('open');ov.querySelector('#inspectList').innerHTML='<li>Loading...</li>';
|
| 202 |
+
try{const res=await apiGet(`/elements/inspect/${currentSessionId}`);const list=ov.querySelector('#inspectList');list.innerHTML='';
|
| 203 |
+
(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)};
|
| 204 |
+
li.onclick=()=>{document.getElementById('selector').value=el.selector;document.getElementById('inspectOverlay').classList.remove('open');};list.appendChild(li);});
|
| 205 |
+
}catch(e){handleErr(e)}}
|
| 206 |
+
|
| 207 |
+
document.addEventListener('keydown',e=>{if(e.key==='Escape')document.getElementById('inspectOverlay').classList.remove('open');});
|
| 208 |
+
|
| 209 |
+
/* โโโโโโโโโโ Initial UI setup โโโโโโโโโโ */
|
| 210 |
+
refreshSessions();log('UI ready');
|