feat: initial Speedboard implementation
sitespeed.io web UI with Express/Pug/SQLite — port 3132. Includes job queue, SSE live log, full metrics dashboard, site history, CO2/axe/CWV sections, and Docker support. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
73
views/running.pug
Normal file
73
views/running.pug
Normal file
@@ -0,0 +1,73 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
div(class='max-w-3xl mx-auto')
|
||||
div(class='flex items-center justify-between mb-4')
|
||||
div
|
||||
h1(class='text-2xl font-bold') Testing URL
|
||||
p(class='text-sm text-gray-500 truncate max-w-xl')= job.url
|
||||
span#status-badge(class='text-xs px-3 py-1 rounded-full bg-yellow-100 text-yellow-800 font-semibold') Queued
|
||||
|
||||
if error
|
||||
div(class='bg-red-50 border border-red-300 text-red-700 rounded-lg p-4 mb-4')
|
||||
strong Test failed:
|
||||
= error
|
||||
|
||||
div(class='bg-gray-900 text-green-400 rounded-xl p-4 h-96 overflow-y-auto text-xs font-mono' id='log-box')
|
||||
p(class='text-gray-500') Waiting for output...
|
||||
|
||||
div(class='mt-4 text-sm text-gray-500 flex gap-4')
|
||||
span Browser:
|
||||
strong= job.browser
|
||||
span Runs:
|
||||
strong= job.runs
|
||||
span Mode:
|
||||
strong= job.mobile ? 'Mobile' : 'Desktop'
|
||||
|
||||
script.
|
||||
const jobId = '!{job.id}';
|
||||
const logBox = document.getElementById('log-box');
|
||||
const badge = document.getElementById('status-badge');
|
||||
let firstLine = true;
|
||||
|
||||
const es = new EventSource(`/status/${jobId}/stream`);
|
||||
|
||||
es.addEventListener('log', (e) => {
|
||||
const d = JSON.parse(e.data);
|
||||
if (firstLine) { logBox.innerHTML = ''; firstLine = false; }
|
||||
const el = document.createElement('div');
|
||||
el.className = 'log-line';
|
||||
el.textContent = d.line;
|
||||
logBox.appendChild(el);
|
||||
logBox.scrollTop = logBox.scrollHeight;
|
||||
});
|
||||
|
||||
es.addEventListener('status', (e) => {
|
||||
const d = JSON.parse(e.data);
|
||||
badge.textContent = d.message;
|
||||
badge.className = 'text-xs px-3 py-1 rounded-full font-semibold ' +
|
||||
(d.phase === 'done' ? 'bg-green-100 text-green-800' :
|
||||
d.phase === 'error' ? 'bg-red-100 text-red-800' :
|
||||
d.phase === 'parsing' ? 'bg-blue-100 text-blue-800' :
|
||||
'bg-yellow-100 text-yellow-800');
|
||||
});
|
||||
|
||||
es.addEventListener('done', (e) => {
|
||||
es.close();
|
||||
badge.textContent = 'Done! Redirecting...';
|
||||
badge.className = 'text-xs px-3 py-1 rounded-full bg-green-100 text-green-800 font-semibold';
|
||||
setTimeout(() => { window.location.href = `/results/${jobId}`; }, 800);
|
||||
});
|
||||
|
||||
es.addEventListener('error', (e) => {
|
||||
es.close();
|
||||
badge.textContent = 'Error';
|
||||
badge.className = 'text-xs px-3 py-1 rounded-full bg-red-100 text-red-800 font-semibold';
|
||||
try {
|
||||
const d = JSON.parse(e.data);
|
||||
const el = document.createElement('div');
|
||||
el.className = 'text-red-400 log-line';
|
||||
el.textContent = '[ERROR] ' + d.message;
|
||||
logBox.appendChild(el);
|
||||
} catch {}
|
||||
});
|
||||
Reference in New Issue
Block a user