132 lines
3.5 KiB
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>
|