feat: add security headers + SSL Labs checks; compact layout

- checker.js: checkSecurityHeaders() graded A+-F, checkSSL() via SSL Labs API
- Both run in parallel with sitespeed.io to minimise wait time
- DB: auto-migrate headers_json + ssl_json columns
- Layout: coach scores + CWV side-by-side, headers + SSL below
- Scorecard + CWV made compact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-07 12:10:22 +02:00
parent bf08b6e9af
commit bb2b3384f0
8 changed files with 197 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
import { runTest } from './runner.js';
import { parseResults } from './parser.js';
import { updateJobStatus, updateJobMetrics } from './db.js';
import { checkSecurityHeaders, checkSSL } from './checker.js';
// SSE subscribers: jobId -> Set of send functions
const subscribers = new Map();
@@ -45,6 +46,10 @@ async function processQueue() {
updateJobStatus(job.id, 'running');
emit(job.id, 'status', { message: 'Test starting...', phase: 'running' });
// Start SSL + headers checks in parallel with sitespeed.io
const sslPromise = checkSSL(job.url).catch(() => null);
const headersPromise = checkSecurityHeaders(job.url).catch(() => null);
const outputFolder = await runTest(job, (line) => {
emit(job.id, 'log', { line });
});
@@ -60,6 +65,14 @@ async function processQueue() {
emit(job.id, 'log', { line: `[parser warning] ${err.message}` });
}
// Collect security check results (may still be running)
emit(job.id, 'status', { message: 'Finalising security checks...', phase: 'security' });
const [sslResult, headersResult] = await Promise.all([sslPromise, headersPromise]);
updateJobMetrics(job.id, {
ssl_json: sslResult ? JSON.stringify(sslResult) : null,
headers_json: headersResult ? JSON.stringify(headersResult) : null,
});
updateJobStatus(job.id, 'done', { report_folder: outputFolder });
emit(job.id, 'status', { message: 'Done!', phase: 'done' });
emit(job.id, 'done', { jobId: job.id });