simoncck commited on
Commit
cf2de85
·
verified ·
1 Parent(s): 11e53fd

Update browser_automation_ui.html

Browse files
Files changed (1) hide show
  1. browser_automation_ui.html +236 -267
browser_automation_ui.html CHANGED
@@ -1,270 +1,239 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>🌐 Web Scraping Server UI</title>
7
- <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">
8
- <link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet">
9
- <style>
10
- * {
11
- margin: 0;
12
- padding: 0;
13
- box-sizing: border-box;
14
- }
15
-
16
- body {
17
- font-family: 'Inter', sans-serif;
18
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
- min-height: 100vh;
20
- color: #2d3748;
21
- overflow-x: hidden;
22
- }
23
-
24
- .container {
25
- max-width: 1400px;
26
- margin: 0 auto;
27
- padding: 20px;
28
- }
29
-
30
- .header {
31
- text-align: center;
32
- margin-bottom: 30px;
33
- color: white;
34
- }
35
-
36
- .header h1 {
37
- font-size: 2.5rem;
38
- font-weight: 700;
39
- margin-bottom: 10px;
40
- text-shadow: 0 2px 4px rgba(0,0,0,0.1);
41
- }
42
-
43
- .header p {
44
- font-size: 1.1rem;
45
- opacity: 0.9;
46
- }
47
-
48
- .status-bar {
49
- background: rgba(255, 255, 255, 0.95);
50
- backdrop-filter: blur(10px);
51
- border-radius: 12px;
52
- padding: 16px 24px;
53
- margin-bottom: 24px;
54
- display: flex;
55
- justify-content: space-between;
56
- align-items: center;
57
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
58
- border: 1px solid rgba(255, 255, 255, 0.2);
59
- }
60
-
61
- .status-item {
62
- display: flex;
63
- align-items: center;
64
- gap: 8px;
65
- }
66
-
67
- .status-indicator {
68
- width: 12px;
69
- height: 12px;
70
- border-radius: 50%;
71
- animation: pulse 2s infinite;
72
- }
73
-
74
- .status-online {
75
- background: #10b981;
76
- }
77
-
78
- .status-offline {
79
- background: #ef4444;
80
- }
81
-
82
- @keyframes pulse {
83
- 0%, 100% { opacity: 1; }
84
- 50% { opacity: 0.5; }
85
- }
86
-
87
- .main-content {
88
- background: rgba(255, 255, 255, 0.95);
89
- backdrop-filter: blur(10px);
90
- border-radius: 16px;
91
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
92
- border: 1px solid rgba(255, 255, 255, 0.2);
93
- overflow: hidden;
94
- }
95
-
96
- .tab-nav {
97
- display: flex;
98
- background: rgba(248, 250, 252, 0.8);
99
- border-bottom: 1px solid #e2e8f0;
100
- }
101
-
102
- .tab-button {
103
- flex: 1;
104
- padding: 16px 24px;
105
- background: none;
106
- border: none;
107
- cursor: pointer;
108
- font-family: 'Inter', sans-serif;
109
- font-size: 1rem;
110
- font-weight: 500;
111
- color: #64748b;
112
- transition: all 0.3s ease;
113
- position: relative;
114
- }
115
-
116
- .tab-button.active {
117
- color: #4f46e5;
118
- background: rgba(79, 70, 229, 0.05);
119
- }
120
-
121
- .tab-button.active::after {
122
- content: '';
123
- position: absolute;
124
- bottom: 0;
125
- left: 0;
126
- right: 0;
127
- height: 3px;
128
- background: #4f46e5;
129
- border-radius: 3px 3px 0 0;
130
- }
131
-
132
- .tab-button:hover {
133
- background: rgba(79, 70, 229, 0.05);
134
- color: #4f46e5;
135
- }
136
-
137
- .tab-content {
138
- padding: 32px;
139
- min-height: 600px;
140
- }
141
-
142
- .tab-pane {
143
- display: none;
144
- }
145
-
146
- .tab-pane.active {
147
- display: block;
148
- animation: fadeIn 0.3s ease;
149
- }
150
-
151
- @keyframes fadeIn {
152
- from { opacity: 0; transform: translateY(10px); }
153
- to { opacity: 1; transform: translateY(0); }
154
- }
155
-
156
- .api-section {
157
- margin-bottom: 40px;
158
- background: #f8fafc;
159
- border-radius: 12px;
160
- border: 1px solid #e2e8f0;
161
- overflow: hidden;
162
- }
163
-
164
- .api-header {
165
- background: linear-gradient(135deg, #4f46e5, #7c3aed);
166
- color: white;
167
- padding: 20px 24px;
168
- display: flex;
169
- align-items: center;
170
- gap: 12px;
171
- }
172
-
173
- .api-header i {
174
- font-size: 1.2rem;
175
- }
176
-
177
- .api-header h3 {
178
- font-size: 1.25rem;
179
- font-weight: 600;
180
- }
181
-
182
- .api-body {
183
- padding: 24px;
184
- }
185
-
186
- .form-group {
187
- margin-bottom: 20px;
188
- }
189
-
190
- .form-label {
191
- display: block;
192
- margin-bottom: 8px;
193
- font-weight: 500;
194
- color: #374151;
195
- }
196
-
197
- .form-input, .form-textarea, .form-select {
198
- width: 100%;
199
- padding: 12px 16px;
200
- border: 2px solid #e5e7eb;
201
- border-radius: 8px;
202
- font-family: 'Inter', sans-serif;
203
- font-size: 0.95rem;
204
- transition: all 0.3s ease;
205
- }
206
-
207
- .form-input:focus, .form-textarea:focus, .form-select:focus {
208
- outline: none;
209
- border-color: #4f46e5;
210
- box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
211
- }
212
-
213
- .form-textarea {
214
- min-height: 120px;
215
- resize: vertical;
216
- font-family: 'Source Code Pro', monospace;
217
- }
218
-
219
- .btn {
220
- padding: 12px 24px;
221
- border: none;
222
- border-radius: 8px;
223
- cursor: pointer;
224
- font-family: 'Inter', sans-serif;
225
- font-size: 0.95rem;
226
- font-weight: 500;
227
- transition: all 0.3s ease;
228
- display: inline-flex;
229
- align-items: center;
230
- gap: 8px;
231
- }
232
-
233
- .btn-primary {
234
- background: linear-gradient(135deg, #4f46e5, #7c3aed);
235
- color: white;
236
- box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
237
- }
238
-
239
- .btn-primary:hover {
240
- transform: translateY(-2px);
241
- box-shadow: 0 6px 20px rgba(79, 70, 229, 0.4);
242
- }
243
-
244
- .btn-secondary {
245
- background: #f1f5f9;
246
- color: #475569;
247
- border: 1px solid #e2e8f0;
248
- }
249
-
250
- .btn-secondary:hover {
251
- background: #e2e8f0;
252
- }
253
-
254
- .response-area {
255
- margin-top: 24px;
256
- background: #1e293b;
257
- border-radius: 8px;
258
- padding: 20px;
259
- font-family: 'Source Code Pro', monospace;
260
- color: #e2e8f0;
261
- white-space: pre-wrap;
262
- max-height: 400px;
263
- overflow-y: auto;
264
- border: 1px solid #334155;
265
- }
266
-
267
- .element-inspector {
268
- background: #f8fafc;
269
- border-radius: 12px;
270
- border
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>🌐 Headless Browser API UI</title>
7
+ <!-- Fonts & Icons -->
8
+ <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" />
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.css" rel="stylesheet" />
10
+ <style>
11
+ * { margin: 0; padding: 0; box-sizing: border-box; }
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
+ min-height: 100vh; color: #1e293b; overflow-x: hidden;
16
+ }
17
+ .container { max-width: 1400px; margin: 0 auto; padding: 32px 20px; }
18
+ .header { text-align: center; color: #fff; margin-bottom: 32px; }
19
+ .header h1 { font-size: 2.5rem; font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,.15); }
20
+ .header p { opacity: .9; }
21
+ /* Status */
22
+ .status-bar { display:flex; gap:16px; flex-wrap:wrap; justify-content:center; background:rgba(255,255,255,.95); padding:16px 24px; border-radius:12px; box-shadow:0 8px 24px rgba(0,0,0,.1); margin-bottom:28px; }
23
+ .status-item { display:flex; align-items:center; gap:8px; }
24
+ .status-indicator { width:12px; height:12px; border-radius:50%; animation:pulse 2s infinite; }
25
+ .status-online { background:#10b981; }
26
+ .status-offline { background:#ef4444; }
27
+ @keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:.4;} }
28
+ /* Tabs */
29
+ .main-card { background:rgba(255,255,255,.95); border-radius:16px; overflow:hidden; box-shadow:0 20px 40px rgba(0,0,0,.1); }
30
+ .tab-nav { display:flex; border-bottom:1px solid #e2e8f0; backdrop-filter:blur(10px); }
31
+ .tab-btn {
32
+ flex:1; padding:16px 24px; background:none; border:none; cursor:pointer; font-size:1rem; font-weight:500; color:#64748b; transition:.25s; position:relative;
33
+ }
34
+ .tab-btn.active { color:#4f46e5; background:rgba(79,70,229,.05); }
35
+ .tab-btn.active::after { content:""; position:absolute;left:0;right:0;bottom:0;height:3px;background:#4f46e5; border-radius:3px 3px 0 0; }
36
+ .tab-btn:hover { background:rgba(79,70,229,.05); color:#4f46e5; }
37
+ .tab-pane { display:none; padding:32px; min-height:600px; }
38
+ .tab-pane.active { display:block; animation:fadeIn .3s ease; }
39
+ @keyframes fadeIn { from{opacity:0;transform:translateY(10px);} to{opacity:1;transform:translateY(0);} }
40
+ /* Sections */
41
+ .api-section { background:#f8fafc; border:1px solid #e2e8f0; border-radius:12px; overflow:hidden; margin-bottom:40px; }
42
+ .api-header { display:flex; align-items:center; gap:10px; padding:18px 24px; background:linear-gradient(135deg,#4f46e5,#7c3aed); color:#fff; }
43
+ .api-header i { font-size:20px; }
44
+ .api-body { padding:24px; }
45
+ .form-group { margin-bottom:20px; }
46
+ label { display:block; margin-bottom:6px; font-weight:500; }
47
+ input, textarea, select { width:100%; padding:12px 14px; border:2px solid #e5e7eb; border-radius:8px; font-size:.95rem; transition:.25s; }
48
+ input:focus, textarea:focus, select:focus { border-color:#4f46e5; outline:none; box-shadow:0 0 0 3px rgba(79,70,229,.12); }
49
+ textarea { min-height:110px; resize:vertical; font-family:'Source Code Pro',monospace; }
50
+ .btn { display:inline-flex; align-items:center; gap:6px; padding:12px 24px; border:none; border-radius:8px; font-weight:500; cursor:pointer; transition:.25s; }
51
+ .btn-primary { background:linear-gradient(135deg,#4f46e5,#7c3aed); color:#fff; box-shadow:0 4px 12px rgba(79,70,229,.3); }
52
+ .btn-primary:hover { transform:translateY(-2px); box-shadow:0 6px 20px rgba(79,70,229,.4); }
53
+ .response-area { margin-top:20px; background:#1e293b; color:#e2e8f0; font-family:'Source Code Pro',monospace; padding:16px; border-radius:8px; white-space:pre-wrap; max-height:350px; overflow-y:auto; border:1px solid #334155; }
54
+ .selector-list { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }
55
+ .selector-chip { background:#e0e7ff; color:#3730a3; padding:4px 8px; border-radius:6px; cursor:pointer; font-size:.8rem; }
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="container">
60
+ <header class="header">
61
+ <h1>Headless Browser API UI</h1>
62
+ <p>Playwright · Selenium · Screenshot · Scraping</p>
63
+ </header>
64
+
65
+ <!-- Status Bar -->
66
+ <div class="status-bar" id="statusBar">
67
+ <div class="status-item"><span class="status-indicator status-offline" id="poolDot"></span> Pool</div>
68
+ <div class="status-item"><span class="status-indicator status-offline" id="playwrightDot"></span> Playwright</div>
69
+ <div class="status-item"><span class="status-indicator status-offline" id="seleniumDot"></span> Selenium</div>
70
+ <button class="btn btn-primary" onclick="checkStatus()"><i class="lucide lucide-refresh-ccw"></i>Refresh</button>
71
+ </div>
72
+
73
+ <!-- Main card with Tabs -->
74
+ <div class="main-card">
75
+ <nav class="tab-nav">
76
+ <button class="tab-btn active" data-tab="manualTab">Manual API</button>
77
+ <button class="tab-btn" data-tab="docTab">Documentation</button>
78
+ </nav>
79
+
80
+ <div class="tab-pane active" id="manualTab">
81
+ <!-- Browser Control -->
82
+ <section class="api-section">
83
+ <div class="api-header"><i class="lucide lucide-monitor-play"></i><h3>Browser Control</h3></div>
84
+ <div class="api-body">
85
+ <div class="form-group">
86
+ <label for="navUrl">Navigate URL</label>
87
+ <input id="navUrl" placeholder="https://example.com" />
88
+ </div>
89
+ <button class="btn btn-primary" onclick="launchBrowser()"><i class="lucide lucide-power"></i>Launch / Reuse</button>
90
+ <button class="btn btn-primary" onclick="navigate()"><i class="lucide lucide-link"></i>Navigate</button>
91
+ <div class="response-area" id="browserResp"></div>
92
+ </div>
93
+ </section>
94
+
95
+ <!-- Screenshot -->
96
+ <section class="api-section">
97
+ <div class="api-header"><i class="lucide lucide-camera"></i><h3>Screenshot</h3></div>
98
+ <div class="api-body">
99
+ <button class="btn btn-primary" onclick="takeScreenshot()"><i class="lucide lucide-camera"></i>Capture</button>
100
+ <div id="shotPreview" style="margin-top:16px;"></div>
101
+ <div class="response-area" id="shotResp"></div>
102
+ </div>
103
+ </section>
104
+
105
+ <!-- Element Interaction -->
106
+ <section class="api-section">
107
+ <div class="api-header"><i class="lucide lucide-mouse-pointer-click"></i><h3>Element Interaction</h3></div>
108
+ <div class="api-body">
109
+ <div class="form-group">
110
+ <label for="selector">CSS / XPath Selector</label>
111
+ <input id="selector" placeholder="input[name=q]" />
112
+ </div>
113
+ <div class="form-group">
114
+ <label for="action">Action</label>
115
+ <select id="action">
116
+ <option value="click">click</option>
117
+ <option value="type">type</option>
118
+ <option value="textContent">getText</option>
119
+ </select>
120
+ </div>
121
+ <div class="form-group" id="typeTextGroup" style="display:none;">
122
+ <label for="typeText">Text to type</label>
123
+ <input id="typeText" placeholder="Hello World" />
124
+ </div>
125
+ <button class="btn btn-primary" onclick="elementAction()"><i class="lucide lucide-play"></i>Run</button>
126
+ <div class="response-area" id="elementResp"></div>
127
+ </div>
128
+ </section>
129
+
130
+ <!-- Inspector -->
131
+ <section class="api-section">
132
+ <div class="api-header"><i class="lucide lucide-layout-grid"></i><h3>Element Inspector</h3></div>
133
+ <div class="api-body">
134
+ <button class="btn btn-primary" onclick="inspectPage()"><i class="lucide lucide-search"></i>List selectors</button>
135
+ <div class="selector-list" id="selectorList"></div>
136
+ <div class="response-area" id="inspectResp"></div>
137
+ </div>
138
+ </section>
139
+ </div>
140
+
141
+ <!-- Documentation Tab -->
142
+ <div class="tab-pane" id="docTab">
143
+ <h2 style="margin-bottom:12px;">API Reference</h2>
144
+ <pre class="response-area" style="background:#f8fafc;color:#1e293b;border:1px solid #e2e8f0;">
145
+ POST /api/browser/launch → { sessionId }
146
+ POST /api/browser/navigate {"url": "https://..."}
147
+ POST /api/browser/screenshot → { b64 }
148
+ POST /api/browser/eval {"selector":"...","action":"click|type|textContent","text":"..."}
149
+ POST /api/browser/inspect → { selectors:["#id",".class",...] }
150
+
151
+ All endpoints return { ok: true/false, data, error }
152
+ </pre>
153
+ </div>
154
+ </div>
155
+ </div>
156
+
157
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/lucide.min.js"></script>
158
+ <script>
159
+ // --- State ---
160
+ let sessionId = null;
161
+
162
+ // Tab switching
163
+ document.querySelectorAll('.tab-btn').forEach(btn => {
164
+ btn.addEventListener('click', () => {
165
+ document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
166
+ document.querySelectorAll('.tab-pane').forEach(p=>p.classList.remove('active'));
167
+ btn.classList.add('active');
168
+ document.getElementById(btn.dataset.tab).classList.add('active');
169
+ });
170
+ });
171
+
172
+ // Show/hide text field based on action
173
+ document.getElementById('action').addEventListener('change', e => {
174
+ document.getElementById('typeTextGroup').style.display = e.target.value === 'type' ? 'block':'none';
175
+ });
176
+
177
+ // Helpers
178
+ async function api(path, body={}) {
179
+ const res = await fetch(`/api${path}`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ sessionId, ...body }) });
180
+ return res.json();
181
+ }
182
+ function setDot(id, ok){ const el=document.getElementById(id); el.classList.toggle('status-online',ok); el.classList.toggle('status-offline',!ok);}
183
+
184
+ // --- Actions ---
185
+ async function checkStatus(){
186
+ const out = await api('/status',{}).catch(()=>({ok:false}));
187
+ setDot('poolDot',out.ok);
188
+ setDot('playwrightDot',out.ok && out.playwright);
189
+ setDot('seleniumDot',out.ok && out.selenium);
190
+ }
191
+
192
+ async function launchBrowser(){
193
+ const out = await api('/browser/launch');
194
+ sessionId = out.sessionId || sessionId;
195
+ document.getElementById('browserResp').textContent = JSON.stringify(out,null,2);
196
+ checkStatus();
197
+ }
198
+
199
+ async function navigate(){
200
+ const url=document.getElementById('navUrl').value.trim();
201
+ if(!url) return alert('Enter URL');
202
+ const out = await api('/browser/navigate',{url});
203
+ document.getElementById('browserResp').textContent = JSON.stringify(out,null,2);
204
+ }
205
+
206
+ async function takeScreenshot(){
207
+ const out = await api('/browser/screenshot');
208
+ document.getElementById('shotResp').textContent = JSON.stringify(out,null,2);
209
+ if(out.b64){ document.getElementById('shotPreview').innerHTML = `<img src="data:image/png;base64,${out.b64}" style="max-width:100%;border:1px solid #e2e8f0;border-radius:8px;"/>`; }
210
+ }
211
+
212
+ async function elementAction(){
213
+ const selector=document.getElementById('selector').value.trim();
214
+ if(!selector) return alert('Enter selector');
215
+ const action=document.getElementById('action').value;
216
+ const text=document.getElementById('typeText').value;
217
+ const out = await api('/browser/eval',{selector,action,text});
218
+ document.getElementById('elementResp').textContent = JSON.stringify(out,null,2);
219
+ }
220
+
221
+ async function inspectPage(){
222
+ const out = await api('/browser/inspect');
223
+ document.getElementById('inspectResp').textContent = JSON.stringify(out,null,2);
224
+ const list=document.getElementById('selectorList');
225
+ list.innerHTML='';
226
+ (out.selectors||[]).slice(0,50).forEach(sel=>{
227
+ const chip=document.createElement('span');
228
+ chip.className='selector-chip';
229
+ chip.textContent=sel;
230
+ chip.onclick=()=>{ document.getElementById('selector').value=sel; document.getElementById('action').focus(); };
231
+ list.appendChild(chip);
232
+ });
233
+ }
234
+
235
+ // Init icons & status
236
+ window.addEventListener('DOMContentLoaded',()=>{ lucide.createIcons(); checkStatus(); });
237
+ </script>
238
+ </body>
239
+ </html>