swactor/crates/wasm/www/index.html
Developer 548396ba4a feat: in-browser runtime, WebSocket gateway, dashboard improvements
- NodeAddr abstraction replacing SocketAddr across distribution crate (~30 files)
- Core wasm32 compatibility: web-time, cfg-gated threads, wire encoding extraction
- WebSocket transport (browser) with GatewayControl protocol
- WebSocket gateway (native) with session routing and control protocol
- BrowserRuntime API: JS actor support, connect/spawn/send/tick
- Dashboard: extracted HTML to static files, gossip protocol panel with
  membership event log, visual fixes, click-to-filter, worker-colored rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:51:36 +00:00

153 lines
5.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Swactor Browser Runtime</title>
<style>
body { font-family: monospace; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
h1 { color: #00d4ff; }
.panel { background: #16213e; border: 1px solid #0f3460; border-radius: 4px; padding: 1rem; margin: 1rem 0; }
button { background: #0f3460; color: #e0e0e0; border: 1px solid #00d4ff; padding: 0.5rem 1rem; cursor: pointer; border-radius: 3px; margin: 0.25rem; }
button:hover { background: #00d4ff; color: #1a1a2e; }
input { background: #16213e; color: #e0e0e0; border: 1px solid #0f3460; padding: 0.5rem; border-radius: 3px; width: 300px; }
#log { max-height: 400px; overflow-y: auto; white-space: pre-wrap; font-size: 0.85rem; line-height: 1.4; }
.info { color: #00d4ff; }
.sent { color: #e94560; }
.recv { color: #0cca4a; }
.error { color: #ff6b6b; }
.status { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; }
.status.connected { background: #0cca4a; }
.status.disconnected { background: #ff6b6b; }
</style>
</head>
<body>
<h1>Swactor Browser Runtime</h1>
<div class="panel">
<h3>Connection</h3>
<span id="status-dot" class="status disconnected"></span>
<span id="status-text">Disconnected</span>
<br><br>
<input id="gateway-url" value="ws://localhost:9000" placeholder="Gateway URL">
<button id="btn-connect">Connect</button>
</div>
<div class="panel">
<h3>Actors</h3>
<button id="btn-spawn">Spawn JS Actor</button>
<span id="actor-count">Actors: 0</span>
<div id="actors-list" style="margin-top: 0.5rem; font-size: 0.85rem;"></div>
</div>
<div class="panel">
<h3>Send Message</h3>
<input id="dest-addr" placeholder="Destination hex address (64 chars)">
<input id="msg-payload" placeholder="JSON payload" value='{"hello": "world"}'>
<button id="btn-send">Send</button>
</div>
<div class="panel">
<h3>Log</h3>
<div id="log"></div>
</div>
<script type="module">
// The wasm-pack output will be at ./pkg/swactor_wasm.js
// Build with: cd crates/wasm && wasm-pack build --target web
import init, { BrowserRuntime } from './pkg/swactor_wasm.js';
let rt = null;
const logEl = document.getElementById('log');
function log(msg, cls = 'info') {
const line = document.createElement('div');
line.className = cls;
line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
logEl.appendChild(line);
logEl.scrollTop = logEl.scrollHeight;
}
async function main() {
try {
await init();
log('WASM module loaded');
rt = new BrowserRuntime();
log('BrowserRuntime created');
// Tick loop
setInterval(() => {
if (rt) {
rt.tick();
document.getElementById('actor-count').textContent = `Actors: ${rt.actor_count()}`;
const connected = rt.is_connected();
const dot = document.getElementById('status-dot');
const text = document.getElementById('status-text');
dot.className = `status ${connected ? 'connected' : 'disconnected'}`;
text.textContent = connected ? 'Connected' : 'Disconnected';
}
}, 16); // ~60fps
} catch (e) {
log(`Init error: ${e}`, 'error');
}
}
// Connect button
document.getElementById('btn-connect').addEventListener('click', () => {
if (!rt) { log('Runtime not ready', 'error'); return; }
const url = document.getElementById('gateway-url').value;
try {
rt.connect(url);
log(`Connecting to ${url}...`);
} catch (e) {
log(`Connect error: ${e}`, 'error');
}
});
// Spawn JS actor
document.getElementById('btn-spawn').addEventListener('click', () => {
if (!rt) { log('Runtime not ready', 'error'); return; }
const addr = rt.spawn_js_actor((ctx, typeTag, msgJson) => {
log(`Actor ${ctx.self_addr().substring(0, 16)}... received [${typeTag}]: ${msgJson}`, 'recv');
});
log(`Spawned JS actor: ${addr.substring(0, 16)}...`, 'info');
const list = document.getElementById('actors-list');
const item = document.createElement('div');
item.textContent = addr;
item.style.cursor = 'pointer';
item.title = 'Click to copy';
item.addEventListener('click', () => {
navigator.clipboard.writeText(addr);
log(`Copied address to clipboard`);
});
list.appendChild(item);
});
// Send message
document.getElementById('btn-send').addEventListener('click', () => {
if (!rt) { log('Runtime not ready', 'error'); return; }
const dest = document.getElementById('dest-addr').value;
const payload = document.getElementById('msg-payload').value;
if (dest.length !== 64) {
log('Destination must be a 64-character hex address', 'error');
return;
}
const ok = rt.send_json(dest, 'js', payload);
if (ok) {
log(`Sent to ${dest.substring(0, 16)}...: ${payload}`, 'sent');
} else {
log('Send failed', 'error');
}
});
main();
</script>
</body>
</html>