pi-remote-control/extensions/remote-control/spike-client.html

132 lines
3.5 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Phase 0 Spike Client</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.min.css" />
<style>
body {
margin: 0;
padding: 20px;
background: #1e1e1e;
color: #d4d4d4;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#header {
margin-bottom: 20px;
}
#status {
padding: 10px;
background: #252526;
border-radius: 4px;
margin-bottom: 10px;
}
#terminal {
background: #000;
border: 1px solid #3c3c3c;
border-radius: 4px;
}
.connected { color: #4ec9b0; }
.disconnected { color: #f48771; }
.info { color: #6a9955; }
</style>
</head>
<body>
<div id="header">
<h1>Phase 0 Spike — tmux Stream Client</h1>
<div id="status">
Status: <span id="status-text" class="disconnected">Not connected</span>
</div>
<div id="stats" style="font-size: 12px; color: #858585;">
Frames: <span id="frame-count">0</span> |
Bytes: <span id="byte-count">0</span> |
Latency: <span id="latency"></span>
</div>
</div>
<div id="terminal"></div>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.min.js"></script>
<script>
// Initialize xterm.js
const term = new Terminal({
cols: 120,
rows: 40,
cursorBlink: true,
theme: {
background: '#000000',
foreground: '#ffffff',
},
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
});
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();
// Stats tracking
let frameCount = 0;
let byteCount = 0;
let lastFrameTime = Date.now();
function updateStats(bytes) {
frameCount++;
byteCount += bytes;
const now = Date.now();
const latency = now - lastFrameTime;
lastFrameTime = now;
document.getElementById('frame-count').textContent = frameCount;
document.getElementById('byte-count').textContent = byteCount.toLocaleString();
document.getElementById('latency').textContent = `${latency}ms`;
}
function setStatus(text, className) {
const statusEl = document.getElementById('status-text');
statusEl.textContent = text;
statusEl.className = className;
}
// Connect to WebSocket
const wsUrl = 'ws://127.0.0.1:7799/spike';
setStatus('Connecting...', 'info');
const ws = new WebSocket(wsUrl);
ws.binaryType = 'arraybuffer';
ws.onopen = () => {
setStatus('Connected', 'connected');
console.log('[spike-client] Connected to', wsUrl);
};
ws.onmessage = (event) => {
if (event.data instanceof ArrayBuffer) {
const bytes = new Uint8Array(event.data);
term.write(bytes);
updateStats(bytes.length);
}
};
ws.onerror = (error) => {
console.error('[spike-client] WebSocket error:', error);
setStatus('Error', 'disconnected');
};
ws.onclose = () => {
setStatus('Disconnected', 'disconnected');
console.log('[spike-client] Connection closed');
};
// Handle window resize
window.addEventListener('resize', () => {
fitAddon.fit();
});
</script>
</body>
</html>