selfapi-v2 / monitor /index.html
akashyadav758
Dockerize stack, strip watermarking, add navigator monitor
c6e6dac
Raw
History Blame Contribute Delete
12.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chrome Navigator — GPT · Gemini · Flow</title>
<style>
:root { --accent: #007acc; }
* { box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #1e1e1e; color: #d4d4d4; margin: 0; padding: 16px;
display: flex; flex-direction: column; align-items: center; min-height: 100vh;
}
.dashboard { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; justify-content: center; }
.card { background: #252526; border-radius: 8px; padding: 8px 15px; display: flex; align-items: center; gap: 12px; box-shadow: 0 4px 6px rgba(0,0,0,.3); }
.card-header { font-size: .72rem; color: #888; text-transform: uppercase; }
.card-value { font-size: 1.1rem; font-weight: 600; color: #fff; }
.progress-bg { background: #333; height: 6px; border-radius: 3px; overflow: hidden; width: 60px; }
.progress-fill { background: var(--accent); height: 100%; width: 0%; transition: width .5s ease; }
.tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; justify-content: center; max-width: 95vw; }
.tab-btn {
background: #252526; color: #d4d4d4; border: 1px solid #333; border-radius: 8px;
padding: 7px 10px 7px 14px; font-size: .9rem; font-weight: 600; cursor: pointer;
transition: all .15s; display: inline-flex; align-items: center; gap: 8px; max-width: 220px;
}
.tab-btn:hover { border-color: var(--accent); }
.tab-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); box-shadow: 0 0 14px rgba(0,122,204,.55); }
.tab-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tab-close {
border: none; background: transparent; color: inherit; opacity: .6;
font-size: 1rem; line-height: 1; cursor: pointer; padding: 0 2px; border-radius: 4px;
}
.tab-close:hover { opacity: 1; background: rgba(255,255,255,.15); }
.tab-new { font-weight: 700; padding: 7px 13px; }
.navbar { display: flex; gap: 6px; margin-bottom: 12px; width: min(95vw, 900px); }
.nav-btn {
background: #252526; color: #d4d4d4; border: 1px solid #333; border-radius: 8px;
width: 38px; min-width: 38px; font-size: 1rem; cursor: pointer; transition: all .15s;
}
.nav-btn:hover { border-color: var(--accent); color: #fff; }
#address {
flex: 1; background: #1b1b1b; color: #e8e8e8; border: 1px solid #333; border-radius: 8px;
padding: 8px 12px; font-size: .9rem; font-family: ui-monospace, Menlo, Consolas, monospace;
}
#address:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 10px rgba(0,122,204,.4); }
.nav-go { width: auto; padding: 0 16px; font-weight: 600; }
#screen-container {
border: 2px solid var(--accent); box-shadow: 0 0 20px rgba(0,122,204,.5);
background: #000; border-radius: 8px; overflow: hidden; line-height: 0; position: relative;
}
img { max-width: 95vw; max-height: 72vh; width: auto; height: auto; display: block; }
#hint { color: #777; font-size: .75rem; margin-top: 8px; }
</style>
</head>
<body>
<div class="dashboard">
<div class="card"><div class="card-header">Memory</div>
<div style="display:flex;align-items:center;gap:10px">
<div class="card-value" id="memVal">...</div>
<div class="progress-bg"><div class="progress-fill" id="memBar"></div></div>
</div>
</div>
<div class="card"><div class="card-header">CPU Load</div><div class="card-value" id="cpuVal">...</div></div>
<div class="card"><div class="card-header">Uptime</div><div class="card-value" id="uptimeVal">...</div></div>
</div>
<div class="tabs" id="tabs"></div>
<div class="navbar">
<button class="nav-btn" id="backBtn" title="Back"></button>
<button class="nav-btn" id="fwdBtn" title="Forward"></button>
<button class="nav-btn" id="reloadBtn" title="Reload"></button>
<input id="address" type="text" placeholder="Enter URL and press Enter…" spellcheck="false" autocomplete="off" />
<button class="nav-btn nav-go" id="goBtn">Go</button>
</div>
<div id="screen-container">
<img id="monitor" alt="Live Stream" />
</div>
<div id="hint">Click and type directly on the page · use the bar above to navigate the active tab anywhere.</div>
<script>
let activeTab = null; // active CDP target id
let tabList = []; // last /api/tabs payload
const img = document.getElementById('monitor');
const tabsEl = document.getElementById('tabs');
const address = document.getElementById('address');
const urlOf = id => (tabList.find(t => t.id === id) || {}).url || '';
// Build the tab strip from the server's live target list.
async function buildTabs() {
try {
const res = await fetch('/api/tabs');
tabList = await res.json();
if (!tabList.length) { tabsEl.innerHTML = ''; return; }
if (!tabList.some(t => t.id === activeTab)) activeTab = tabList[0].id;
tabsEl.innerHTML = '';
tabList.forEach(t => {
const b = document.createElement('button');
b.className = 'tab-btn' + (t.id === activeTab ? ' active' : '');
b.dataset.key = t.id;
b.title = t.url;
const label = document.createElement('span');
label.className = 'tab-label';
label.textContent = t.title;
b.appendChild(label);
const x = document.createElement('button');
x.className = 'tab-close';
x.textContent = '×';
x.title = 'Close tab';
x.onclick = e => { e.stopPropagation(); closeTab(t.id); };
b.appendChild(x);
b.onclick = () => switchTab(t.id);
tabsEl.appendChild(b);
});
// "+ new" button
const plus = document.createElement('button');
plus.className = 'tab-btn tab-new';
plus.textContent = '+';
plus.title = 'New tab';
plus.onclick = newTab;
tabsEl.appendChild(plus);
syncAddress();
} catch (e) { console.error(e); }
}
function syncAddress() {
if (document.activeElement !== address) address.value = urlOf(activeTab);
}
function switchTab(id) {
activeTab = id;
document.querySelectorAll('.tab-btn').forEach(b =>
b.classList.toggle('active', b.dataset.key === id));
syncAddress();
refreshImage();
}
async function post(path) {
try { await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); }
catch (e) { console.error(e); }
}
async function navigate() {
const url = address.value.trim();
if (!url || !activeTab) return;
try {
await fetch('/api/navigate?tab=' + activeTab, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url })
});
address.blur();
setTimeout(() => { buildTabs(); refreshImage(); }, 600);
} catch (e) { console.error(e); }
}
async function reload() { await post('/api/reload?tab=' + activeTab); setTimeout(refreshImage, 500); }
async function goBack() { await post('/api/back?tab=' + activeTab); setTimeout(() => { buildTabs(); refreshImage(); }, 500); }
async function goForward() { await post('/api/forward?tab=' + activeTab); setTimeout(() => { buildTabs(); refreshImage(); }, 500); }
async function newTab() {
const url = prompt('Open URL in a new tab:', 'https://');
if (url === null) return;
try {
const res = await fetch('/api/newtab', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url.trim() })
});
const { id } = await res.json();
if (id) activeTab = id;
await buildTabs();
refreshImage();
} catch (e) { console.error(e); }
}
async function closeTab(id) {
try {
await fetch('/api/closetab?tab=' + id, { method: 'POST' });
if (id === activeTab) activeTab = null;
await buildTabs();
refreshImage();
} catch (e) { console.error(e); }
}
document.getElementById('goBtn').onclick = navigate;
document.getElementById('reloadBtn').onclick = reload;
document.getElementById('backBtn').onclick = goBack;
document.getElementById('fwdBtn').onclick = goForward;
address.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); navigate(); } });
async function sendInput(data) {
if (!activeTab) return;
try {
await fetch('/api/input?tab=' + activeTab, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
setTimeout(refreshImage, 120);
} catch (e) { console.error(e); }
}
function coords(e) {
const rect = img.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left) * (img.naturalWidth / rect.width)),
y: Math.round((e.clientY - rect.top) * (img.naturalHeight / rect.height))
};
}
img.addEventListener('mousedown', e => sendInput({ type: 'mousedown', ...coords(e), button: 'left' }));
img.addEventListener('mouseup', e => sendInput({ type: 'mouseup', ...coords(e), button: 'left' }));
document.addEventListener('keydown', e => {
if (document.activeElement === address) return; // typing in the URL bar
if (e.key === 'r' && (e.metaKey || e.ctrlKey)) return;
if (e.key.length === 1) sendInput({ type: 'keydown', text: e.key });
else if (e.key === 'Enter') sendInput({ type: 'keydown', text: '\r' });
else if (e.key === 'Backspace') sendInput({ type: 'keydown', text: '\b' });
});
function refreshImage() {
if (!activeTab) return;
const src = `/api/screen?tab=${activeTab}&t=${Date.now()}`;
const tmp = new Image();
tmp.onload = () => { img.src = src; };
tmp.src = src;
}
setInterval(refreshImage, 1000);
setInterval(buildTabs, 5000); // pick up new/closed tabs and url changes
async function updateStats() {
try {
const d = await (await fetch('/api/stats')).json();
document.getElementById('memVal').innerText = `${d.memUsedGB}/${d.memTotalGB}GB`;
document.getElementById('memBar').style.width = `${d.memPct}%`;
document.getElementById('cpuVal').innerText = `${Math.round(d.cpu)}%`;
const h = Math.floor(d.uptime / 3600), m = Math.floor((d.uptime % 3600) / 60);
document.getElementById('uptimeVal').innerText = `${h}h ${m}m`;
} catch (e) { console.error(e); }
}
setInterval(updateStats, 2000);
buildTabs().then(refreshImage);
updateStats();
</script>
</body>
</html>