diff --git a/src/receipts.mjs b/src/receipts.mjs index 262281b..9987588 100644 --- a/src/receipts.mjs +++ b/src/receipts.mjs @@ -16,4 +16,4 @@ export function createReceiptStore(db,config=''){ } const escapeHtml=value=>String(value??'').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"'); -export function renderEventsPage(db){const rows=db.prepare('SELECT * FROM deployment_events ORDER BY occurred_at DESC LIMIT 100').all();const counts=db.prepare('SELECT event_type,result,count(*) count FROM deployment_events GROUP BY event_type,result ORDER BY event_type,result').all();return`operational events · trace
trace

Operational events

Builds say what CI produced. Deployments say what a host actually ran.

${counts.map(x=>`${escapeHtml(x.event_type)} · ${escapeHtml(x.result)}: ${x.count}`).join('')}
${rows.map(row=>{const payload=JSON.parse(row.payload),artifacts=payload.artifacts||[];return`
${escapeHtml(row.project)} · ${escapeHtml(row.event_type)}

${escapeHtml(row.source_commit.slice(0,12))} ${escapeHtml(row.source_branch)} ${row.environment?`· ${escapeHtml(row.environment)}`:''}

${escapeHtml(row.result)}

${escapeHtml(row.occurred_at)} · received ${escapeHtml(row.received_at)}

${artifacts.map(x=>`

${escapeHtml(x.component)} ${escapeHtml(x.digest||'digest unavailable')}

`).join('')}
raw private receipt
${escapeHtml(JSON.stringify(payload,null,2))}
`}).join('')||'

No operational events received yet.

'}
`} +export function renderEventsPage(db){const rows=db.prepare('SELECT * FROM deployment_events ORDER BY occurred_at DESC LIMIT 100').all();const counts=db.prepare('SELECT event_type,result,count(*) count FROM deployment_events GROUP BY event_type,result ORDER BY event_type,result').all();return`operational events · trace
trace

Operational events

Builds say what CI produced. Deployments say what a host actually ran.

${counts.map(x=>`${escapeHtml(x.event_type)} · ${escapeHtml(x.result)}: ${x.count}`).join('')}
${rows.map(row=>{const payload=JSON.parse(row.payload),artifacts=payload.artifacts||[];return`
${escapeHtml(row.project)} · ${escapeHtml(row.event_type)}

${escapeHtml(row.source_commit.slice(0,12))} ${escapeHtml(row.source_branch)} ${row.environment?`· ${escapeHtml(row.environment)}`:''}

${escapeHtml(row.result)}

${escapeHtml(row.occurred_at)} · received ${escapeHtml(row.received_at)}

${artifacts.map(x=>`

${escapeHtml(x.component)} ${escapeHtml(x.digest||'digest unavailable')}

`).join('')}
raw private receipt
${escapeHtml(JSON.stringify(payload,null,2))}
`}).join('')||'

No operational events received yet.

'}
`} diff --git a/src/server.mjs b/src/server.mjs index 7fc5dca..9f4312b 100644 --- a/src/server.mjs +++ b/src/server.mjs @@ -24,7 +24,7 @@ const giteaRepos=(process.env.TRACE_GITEA_REPOS||'').split(',').map(x=>x.trim()) const esc=s=>String(s??'').replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"'); const form=async req=>{let b='';for await(const c of req)b+=c;return Object.fromEntries(new URLSearchParams(b));}; function auth(req,res){if(!password)return true;const expected='Basic '+Buffer.from('trace:'+password).toString('base64');if(req.headers.authorization===expected)return true;res.writeHead(401,{'WWW-Authenticate':'Basic realm="trace"'}).end('Authentication required');return false;} -function layout(title,body){return `${esc(title)} · trace
trace
${body}
`;} +function layout(title,body){return `${esc(title)} · trace
trace
${body}
`;} const fields=['impact','root_cause','fix','verification','prevention','remaining_risk','lesson','public_summary']; const slugify=s=>String(s||'failure').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')||'failure'; const publicSeverity={annoyance:'paper-cut',degraded:'minor',unavailable:'significant','data-risk':'significant','security-risk':'significant'};