webgpu-cluster / src /pages /clusterMonitor.ts
apssouza22's picture
Deploy GPU detection cluster (Docker Space)
4bdf735 verified
Raw
History Blame Contribute Delete
6.86 kB
const API_BASE = import.meta.env.VITE_API_BASE ?? '';
const REFRESH_MS = 3000;
const SAMPLE_IMAGE_URL =
'https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/coco_sample.png';
interface HostInfo {
id: string;
model: string;
online: boolean;
registeredAt: number;
lastHeartbeat: number;
}
const brokerStatusEl = document.getElementById('broker-status')!;
const onlineCountEl = document.getElementById('online-count')!;
const totalCountEl = document.getElementById('total-count')!;
const hostsEl = document.getElementById('hosts')!;
const errorEl = document.getElementById('error')!;
const refreshNoteEl = document.getElementById('refresh-note')!;
const expandedCurlHosts = new Set<string>();
function syncExpandedCurlFromDom(): void {
hostsEl.querySelectorAll<HTMLDetailsElement>('details.curl-details').forEach((details) => {
const id = details.closest('[data-host-id]')?.getAttribute('data-host-id');
if (!id) {
return;
}
if (details.open) {
expandedCurlHosts.add(id);
} else {
expandedCurlHosts.delete(id);
}
});
}
function detectOrigin(): string {
return window.location.origin;
}
function buildCurlExample(hostId: string, modelId: string): string {
const origin = detectOrigin();
if (modelId === 'smolvlm-500m') {
return `curl -X POST '${origin}/v1/describe' \\
-H 'Content-Type: application/json' \\
-d '{
"host": "${hostId}",
"image_url": "${SAMPLE_IMAGE_URL}",
"instruction": "What do you see?",
"max_new_tokens": 100
}'`;
}
return `curl -X POST '${origin}/v1/detect' \\
-H 'Content-Type: application/json' \\
-d '{
"host": "${hostId}",
"image_url": "${SAMPLE_IMAGE_URL}",
"threshold": 0.5
}'`;
}
function formatTime(ms: number): string {
return new Date(ms).toLocaleString();
}
function relativeTime(ms: number): string {
const seconds = Math.floor((Date.now() - ms) / 1000);
if (seconds < 60) {
return `${seconds}s ago`;
}
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return `${minutes}m ago`;
}
const hours = Math.floor(minutes / 60);
return `${hours}h ago`;
}
function showError(message: string): void {
errorEl.textContent = message;
errorEl.style.display = 'block';
}
function clearError(): void {
errorEl.style.display = 'none';
}
async function fetchBrokerHealth(): Promise<boolean> {
const res = await fetch(`${API_BASE}/health`);
if (!res.ok) {
return false;
}
const json = (await res.json()) as {status?: string};
return json.status === 'ok';
}
async function fetchHosts(): Promise<HostInfo[]> {
const res = await fetch(`${API_BASE}/v1/hosts`);
if (!res.ok) {
throw new Error(`GET /v1/hosts failed (${res.status})`);
}
const json = (await res.json()) as {hosts: HostInfo[]};
return json.hosts ?? [];
}
function renderHosts(hosts: HostInfo[]): void {
const online = hosts.filter((h) => h.online).length;
onlineCountEl.textContent = String(online);
onlineCountEl.className = `stat-value ${online > 0 ? 'ok' : 'warn'}`;
totalCountEl.textContent = String(hosts.length);
if (hosts.length === 0) {
hostsEl.innerHTML = `
<div class="empty">
<p>No hosts registered yet.</p>
<p><a href="/public">Open the host page</a>, pick an id, and click “Start hosting”.</p>
</div>`;
return;
}
const sorted = [...hosts].sort((a, b) => {
if (a.online !== b.online) {
return a.online ? -1 : 1;
}
return b.lastHeartbeat - a.lastHeartbeat;
});
syncExpandedCurlFromDom();
hostsEl.innerHTML = sorted
.map((host) => {
const curl = buildCurlExample(host.id, host.model);
const escapedCurl = curl.replace(/'/g, '&#39;');
return `
<article class="host-card" data-host-id="${escapeHtml(host.id)}">
<div class="host-header">
<span class="host-id">${escapeHtml(host.id)}</span>
<span class="badge ${host.online ? 'online' : 'offline'}">
${host.online ? 'Online' : 'Offline'}
</span>
</div>
<div class="host-meta">
<span>Model: ${escapeHtml(host.model)}</span>
<span>Registered: ${formatTime(host.registeredAt)}</span>
<span>Last heartbeat: ${relativeTime(host.lastHeartbeat)}</span>
</div>
<details class="curl-details"${expandedCurlHosts.has(host.id) ? ' open' : ''}>
<summary>
<span class="summary-show">Show curl example</span>
<span class="summary-hide">Hide curl example</span>
</summary>
<div class="curl-body">
<button type="button" class="copy" data-curl="${escapedCurl}">Copy curl</button>
<pre>${escapeHtml(curl)}</pre>
</div>
</details>
</article>`;
})
.join('');
hostsEl.querySelectorAll('button.copy').forEach((btn) => {
btn.addEventListener('click', async () => {
const curl = btn.getAttribute('data-curl')?.replace(/&#39;/g, "'") ?? '';
await navigator.clipboard.writeText(curl);
const label = btn.textContent;
btn.textContent = 'Copied!';
setTimeout(() => {
btn.textContent = label;
}, 1500);
});
});
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
async function refresh(): Promise<void> {
try {
const healthy = await fetchBrokerHealth();
brokerStatusEl.textContent = healthy ? 'Healthy' : 'Unreachable';
brokerStatusEl.className = `stat-value ${healthy ? 'ok' : 'err'}`;
if (!healthy) {
showError(
`Broker at ${detectOrigin()} is not responding. Run \`npm run dev\` or \`npm run dev:api\`.`,
);
hostsEl.innerHTML = '';
onlineCountEl.textContent = '—';
totalCountEl.textContent = '—';
return;
}
clearError();
const hosts = await fetchHosts();
renderHosts(hosts);
refreshNoteEl.textContent = `Last updated ${new Date().toLocaleTimeString()} · refreshing every ${REFRESH_MS / 1000}s`;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
showError(message);
brokerStatusEl.textContent = 'Error';
brokerStatusEl.className = 'stat-value err';
}
}
hostsEl.addEventListener('toggle', (event) => {
const details = event.target;
if (!(details instanceof HTMLDetailsElement) || !details.classList.contains('curl-details')) {
return;
}
const id = details.closest('[data-host-id]')?.getAttribute('data-host-id');
if (!id) {
return;
}
if (details.open) {
expandedCurlHosts.add(id);
} else {
expandedCurlHosts.delete(id);
}
});
void refresh();
setInterval(() => {
void refresh();
}, REFRESH_MS);