tahamajs/IE / Projects /scripts /docker_monitor.py
tahamajs's picture
download
raw
21.6 kB
#!/usr/bin/env python3
import subprocess, json, threading, time, glob, os
from http.server import HTTPServer, BaseHTTPRequestHandler
HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Docker & Build Monitor</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0d1117; color: #e6edf3; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
header { background: #161b22; border-bottom: 1px solid #30363d; padding: 16px 24px; display: flex; align-items: center; gap: 12px; }
header h1 { font-size: 1.2rem; color: #58a6ff; font-weight: 600; }
.badge { background: #238636; color: #fff; font-size: 11px; padding: 2px 8px; border-radius: 12px; font-weight: bold; }
.grid { display: grid; grid-template-columns: 320px 1fr; height: calc(100vh - 57px); }
.sidebar { background: #161b22; border-right: 1px solid #30363d; overflow-y: auto; padding: 16px; }
.sidebar h2 { font-size: 11px; color: #8b949e; text-transform: uppercase; margin-bottom: 10px; margin-top: 15px; letter-spacing: 0.5px; }
.sidebar h2:first-of-type { margin-top: 0; }
.container-card { background: #21262d; border: 1px solid #30363d; border-radius: 8px; padding: 10px 12px; margin-bottom: 8px; cursor: pointer; transition: all .2s; }
.container-card:hover, .container-card.active { border-color: #58a6ff; background: #30363d; }
.container-card .name { font-size: 13px; font-weight: 600; color: #c9d1d9; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.container-card .status { font-size: 11px; margin-top: 4px; display: flex; align-items: center; gap: 6px; }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot.up { background: #3fb950; box-shadow: 0 0 6px #3fb950; }
.dot.down { background: #f85149; }
.dot.other { background: #d29922; }
.stats { font-size: 11px; color: #8b949e; margin-top: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.main { display: flex; flex-direction: column; overflow: hidden; }
.tabs { background: #161b22; border-bottom: 1px solid #30363d; display: flex; }
.tab { padding: 12px 24px; font-size: 13px; cursor: pointer; color: #8b949e; border-bottom: 2px solid transparent; transition: all .2s; font-weight: 500; }
.tab.active { color: #e6edf3; border-bottom-color: #58a6ff; font-weight: 600; }
.tab:hover { color: #e6edf3; }
.panel { flex: 1; overflow: hidden; display: none; flex-direction: column; }
.panel.active { display: flex; }
.log-box { flex: 1; background: #0d1117; padding: 16px; overflow-y: auto; font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; font-size: 12px; line-height: 1.6; }
.log-line { border-bottom: 1px solid #161b22; padding: 2px 0; word-break: break-all; white-space: pre-wrap; }
.log-line:hover { background: #161b22; }
.log-controls { background: #161b22; border-top: 1px solid #30363d; padding: 12px 16px; display: flex; gap: 16px; align-items: center; }
.log-controls label { font-size: 12px; color: #8b949e; display: flex; align-items: center; gap: 6px; cursor: pointer; }
.log-controls input[type=checkbox] { accent-color: #58a6ff; }
.btn { background: #21262d; border: 1px solid #30363d; color: #c9d1d9; padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; font-weight: 500; }
.btn:hover { background: #30363d; border-color: #8b949e; }
.stats-grid { padding: 20px; display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; overflow-y: auto; }
.stat-card { background: #21262d; border: 1px solid #30363d; border-radius: 8px; padding: 20px; }
.stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; margin-bottom: 8px; font-weight: 600; letter-spacing: 0.5px; }
.stat-card .value { font-size: 24px; font-weight: 700; color: #58a6ff; }
.stat-card .sub { font-size: 11px; color: #8b949e; margin-top: 6px; }
.no-select { color: #8b949e; text-align: center; padding: 40px; font-size: 14px; }
.toolbar { background: #161b22; border-bottom: 1px solid #30363d; padding: 8px 20px; display: flex; align-items: center; gap: 12px; }
.toolbar span { font-size: 12px; color: #8b949e; }
.toolbar strong { color: #58a6ff; font-size: 14px; font-weight: 600; }
.warn { color: #d29922; } .err { color: #f85149; } .info { color: #58a6ff; } .ok { color: #3fb950; }
</style>
</head>
<body>
<header>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2.5"/><rect x="2" y="14" width="20" height="8" rx="2.5"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
<h1>Docker & Build Monitor</h1>
<span class="badge" id="live-badge">● LIVE</span>
</header>
<div class="grid">
<div class="sidebar">
<h2>Active CLI Tasks / Builds</h2>
<div id="task-list">Loading...</div>
<h2>Docker Containers</h2>
<div id="container-list">Loading...</div>
</div>
<div class="main">
<div id="no-select" class="no-select" style="display:flex;align-items:center;justify-content:center;height:100%">
<div>← Select a build task or container from the sidebar</div>
</div>
<div id="container-detail" style="display:none;flex-direction:column;height:100%">
<div class="toolbar">
<strong id="detail-name">—</strong>
<span id="detail-status">—</span>
<span style="flex:1"></span>
<span id="detail-image" style="font-size:11px;color:#8b949e"></span>
</div>
<div class="tabs">
<div class="tab active" onclick="switchTab('logs')">📜 Logs</div>
<div class="tab" onclick="switchTab('stats')">📊 Stats & Info</div>
</div>
<div class="panel active" id="panel-logs">
<div class="log-box" id="log-box">
<div style="color:#8b949e">Select an item...</div>
</div>
<div class="log-controls">
<label><input type="checkbox" id="auto-scroll" checked> Auto-scroll</label>
<label><input type="checkbox" id="auto-refresh" checked> Live refresh (2s)</label>
<button class="btn" onclick="clearLogs()">Clear</button>
<span id="log-count" style="font-size:11px;color:#8b949e;margin-left:auto"></span>
</div>
</div>
<div class="panel" id="panel-stats">
<div class="stats-grid" id="stats-grid">
<div style="color:#8b949e;padding:20px">Loading...</div>
</div>
</div>
</div>
</div>
</div>
<script>
let selectedId = null;
let refreshTimer = null;
let statsTimer = null;
let logLines = [];
function colorize(line) {
if (/ERROR|FATAL|Exception|failed|FAILED|refused/.test(line)) return `<span class="err">${escHtml(line)}</span>`;
if (/WARN|WARNING/.test(line)) return `<span class="warn">${escHtml(line)}</span>`;
if (/INFO|Started|Running|SUCCESS|BUILD SUCCESS|SUCCESSFUL/.test(line)) return `<span class="ok">${escHtml(line)}</span>`;
if (/Downloading|Downloaded|Sending build context/.test(line)) return `<span class="info">${escHtml(line)}</span>`;
return escHtml(line);
}
function escHtml(s) { return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
async function loadSidebar() {
try {
const r = await fetch('/api/containers');
const data = await r.json();
const tasks = data.filter(c => c.is_task);
const containers = data.filter(c => !c.is_task);
// Render Tasks
const taskList = document.getElementById('task-list');
if (tasks.length === 0) {
taskList.innerHTML = '<div style="font-size:12px;color:#8b949e;padding:8px 4px;">No recent tasks found</div>';
} else {
taskList.innerHTML = tasks.map(t => {
const active = t.id === selectedId ? 'active' : '';
const isRunning = t.status === 'Running';
return `<div class="container-card ${active}" onclick="selectItem('${escHtml(t.id)}','${escHtml(t.name)}')">
<div class="name" style="color:#58a6ff">${escHtml(t.name)}</div>
<div class="status"><span class="dot ${isRunning?'up':'other'}"></span><span style="color:${isRunning?'#3fb950':'#8b949e'}">${escHtml(t.status)}</span></div>
<div class="stats">${escHtml(t.image)}</div>
</div>`;
}).join('');
}
// Render Containers
const containerList = document.getElementById('container-list');
if (containers.length === 0) {
containerList.innerHTML = '<div style="font-size:12px;color:#8b949e;padding:8px 4px;">No containers found</div>';
} else {
containerList.innerHTML = containers.map(c => {
const running = c.status.startsWith('Up');
const dotClass = running ? 'up' : c.status.startsWith('Exited') ? 'down' : 'other';
const active = c.id === selectedId ? 'active' : '';
return `<div class="container-card ${active}" onclick="selectItem('${escHtml(c.id)}','${escHtml(c.name)}')">
<div class="name">${escHtml(c.name)}</div>
<div class="status"><span class="dot ${dotClass}"></span><span style="color:${running?'#3fb950':dotClass==='down'?'#f85149':'#d29922'}">${escHtml(c.status)}</span></div>
<div class="stats">${escHtml(c.image.substring(0,40))}</div>
</div>`;
}).join('');
}
} catch(e) {}
}
async function selectItem(id, name) {
selectedId = id;
document.getElementById('no-select').style.display = 'none';
document.getElementById('container-detail').style.display = 'flex';
document.getElementById('detail-name').textContent = name;
logLines = [];
document.getElementById('log-box').innerHTML = '<div style="color:#8b949e">Loading logs...</div>';
loadSidebar();
loadLogs();
loadStats();
clearInterval(refreshTimer);
clearInterval(statsTimer);
refreshTimer = setInterval(() => { if(document.getElementById('auto-refresh').checked) loadLogs(); }, 2000);
statsTimer = setInterval(loadStats, 3000);
}
async function loadLogs() {
if (!selectedId) return;
try {
const r = await fetch('/api/logs?id=' + encodeURIComponent(selectedId) + '&tail=300');
const text = await r.text();
const lines = text.split('\\n');
logLines = lines.filter(l => l.trim() || l === '');
renderLogs();
} catch(e) {}
}
function renderLogs() {
const box = document.getElementById('log-box');
box.innerHTML = logLines.map(l => `<div class="log-line">${colorize(l)}</div>`).join('');
document.getElementById('log-count').textContent = logLines.length + ' lines';
if (document.getElementById('auto-scroll').checked) box.scrollTop = box.scrollHeight;
}
function clearLogs() { logLines = []; renderLogs(); }
async function loadStats() {
if (!selectedId) return;
try {
const r = await fetch('/api/stats?id=' + encodeURIComponent(selectedId));
const data = await r.json();
document.getElementById('detail-status').textContent = data.status || '';
document.getElementById('detail-image').textContent = (data.image||'').substring(0,60);
if (selectedId.startsWith('/')) {
// CLI Task Stats View
document.getElementById('stats-grid').innerHTML = `
<div class="stat-card"><div class="label">Task Status</div><div class="value">${data.status||'—'}</div><div class="sub">running / finished</div></div>
<div class="stat-card"><div class="label">Log Size</div><div class="value">${data.mem_usage||'—'}</div><div class="sub">log size on disk</div></div>
<div class="stat-card"><div class="label">Log File Path</div><div style="font-size:11px;word-break:break-all;color:#58a6ff;margin-top:8px">${data.image||'—'}</div></div>
`;
} else {
// Docker Container Stats View
document.getElementById('stats-grid').innerHTML = `
<div class="stat-card"><div class="label">CPU Usage</div><div class="value">${data.cpu||'—'}</div><div class="sub">of host CPU</div></div>
<div class="stat-card"><div class="label">Memory</div><div class="value">${data.mem_usage||'—'}</div><div class="sub">limit: ${data.mem_limit||'—'}</div></div>
<div class="stat-card"><div class="label">Memory %</div><div class="value">${data.mem_pct||'—'}</div><div class="sub"> </div></div>
<div class="stat-card"><div class="label">Network IN</div><div class="value">${data.net_in||'—'}</div><div class="sub">received</div></div>
<div class="stat-card"><div class="label">Network OUT</div><div class="value">${data.net_out||'—'}</div><div class="sub">sent</div></div>
<div class="stat-card"><div class="label">Block I/O Read</div><div class="value">${data.block_in||'—'}</div><div class="sub"> </div></div>
<div class="stat-card"><div class="label">Block I/O Write</div><div class="value">${data.block_out||'—'}</div><div class="sub"> </div></div>
<div class="stat-card"><div class="label">PIDs</div><div class="value">${data.pids||'—'}</div><div class="sub">processes</div></div>
`;
}
} catch(e) {}
}
function switchTab(tab) {
document.querySelectorAll('.tab').forEach((t,i) => t.classList.toggle('active', ['logs','stats'][i]===tab));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.getElementById('panel-'+tab).classList.add('active');
}
loadSidebar();
setInterval(loadSidebar, 5000);
</script>
</body>
</html>"""
def get_recent_tasks():
files = glob.glob('/Users/tahamajs/.gemini/antigravity-cli/brain/*/.system_generated/tasks/task-*.log')
files.sort(key=os.path.getmtime, reverse=True)
tasks = []
for f in files[:8]:
mtime = os.path.getmtime(f)
size = os.path.getsize(f)
desc = "Background Process"
try:
with open(f, 'r', errors='replace') as fh:
first_lines = [fh.readline() for _ in range(15)]
for line in first_lines:
line_strip = line.strip()
if line_strip:
if not any(x in line_strip for x in ['██', 'IE Research', '══', '✔', 'ℹ', '⚠']):
if "Task Description:" in line_strip:
desc = line_strip.replace("Task Description:", "").strip()
break
elif "Running command" in line_strip or "CommandLine" in line_strip:
desc = line_strip
break
else:
desc = line_strip[:80]
break
except Exception:
pass
is_active = (time.time() - mtime) < 15
status = "Running" if is_active else "Finished"
desc = desc.replace('cd /Users/tahamajs/Documents/uni/IE/Projects && ', '')
desc = desc.strip('"\'')
tasks.append({
"id": f,
"name": desc[:50] + ("..." if len(desc) > 50 else ""),
"status": status,
"image": f"Size: {size/1024:.1f} KB | Path: {os.path.basename(f)}",
"is_task": True
})
return tasks
class Handler(BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
from urllib.parse import urlparse, parse_qs
p = urlparse(self.path)
qs = parse_qs(p.query)
if p.path == '/':
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(HTML.encode())
elif p.path == '/api/containers':
items = get_recent_tasks()
try:
out = subprocess.check_output(
['docker', 'ps', '-a', '--format',
'{"id":"{{.ID}}","name":"{{.Names}}","status":"{{.Status}}","image":"{{.Image}}"}'],
stderr=subprocess.DEVNULL).decode()
containers = [json.loads(l) for l in out.strip().split('\n') if l.strip()]
for c in containers:
c['is_task'] = False
items.extend(containers)
except Exception:
pass
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(json.dumps(items).encode())
elif p.path == '/api/logs':
cid = qs.get('id', [''])[0]
tail = qs.get('tail', ['300'])[0]
if cid.startswith('/'):
try:
with open(cid, 'r', errors='replace') as fh:
lines = fh.readlines()
try:
t = int(tail)
lines = lines[-t:]
except ValueError:
lines = lines[-300:]
out = "".join(lines)
except Exception as e:
out = f"Error reading log file: {str(e)}"
else:
try:
out = subprocess.check_output(
['docker', 'logs', '--tail', tail, cid],
stderr=subprocess.STDOUT).decode(errors='replace')
except Exception:
try:
driver = subprocess.check_output(
['docker', 'inspect', cid, '--format', '{{.HostConfig.LogConfig.Type}}'],
stderr=subprocess.DEVNULL).decode().strip()
if driver == 'none':
out = ("(This container's log driver is set to 'none' by Docker, usually "
"during image build steps. Please look at the corresponding "
"running task log in the 'Active CLI Tasks / Builds' section above!)")
else:
out = f"(could not read logs — driver is {driver})"
except Exception:
out = "(could not read logs — container may not support log driver)"
self.send_response(200)
self.send_header('Content-Type', 'text/plain; charset=utf-8')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(out.encode())
elif p.path == '/api/stats':
cid = qs.get('id', [''])[0]
if cid.startswith('/'):
try:
mtime = os.path.getmtime(cid)
size = os.path.getsize(cid)
is_active = (time.time() - mtime) < 15
status = "Running" if is_active else "Finished"
data = {
'status': status,
'mem_usage': f"{size/1024:.1f} KB",
'image': cid,
}
except Exception as e:
data = {'error': str(e)}
else:
try:
raw = subprocess.check_output(
['docker', 'stats', '--no-stream', '--format',
'{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}|{{.NetIO}}|{{.BlockIO}}|{{.PIDs}}', cid],
stderr=subprocess.DEVNULL).decode().strip()
parts = raw.split('|')
mem = parts[1].split(' / ') if len(parts) > 1 else ['?', '?']
net = parts[3].split(' / ') if len(parts) > 3 else ['?', '?']
blk = parts[4].split(' / ') if len(parts) > 4 else ['?', '?']
info = subprocess.check_output(
['docker', 'inspect', cid, '--format', '{{.State.Status}}|{{.Config.Image}}'],
stderr=subprocess.DEVNULL).decode().strip().split('|')
data = {
'cpu': parts[0] if parts else '?',
'mem_usage': mem[0].strip() if mem else '?',
'mem_limit': mem[1].strip() if len(mem) > 1 else '?',
'mem_pct': parts[2] if len(parts) > 2 else '?',
'net_in': net[0].strip() if net else '?',
'net_out': net[1].strip() if len(net) > 1 else '?',
'block_in': blk[0].strip() if blk else '?',
'block_out': blk[1].strip() if len(blk) > 1 else '?',
'pids': parts[5] if len(parts) > 5 else '?',
'status': info[0] if info else '?',
'image': info[1] if len(info) > 1 else '?',
}
except Exception as e:
data = {'error': str(e)}
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
self.wfile.write(json.dumps(data).encode())
else:
self.send_response(404)
self.end_headers()
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8888), Handler)
print("🐳 Docker & Build Monitor running at http://localhost:8888")
server.serve_forever()

Xet Storage Details

Size:
21.6 kB
·
Xet hash:
1c3726b7369c989554de1ec31d8309fa42c30fb3932bd6ad7023599b0b7140a7

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.