#!/usr/bin/env node 'use strict'; /** * Raise, clear or list ProdDash alerts from the command line — for trying a * dashboard's channels and the admin colours, or from a script on the * machine running ProdDash (anything that can POST JSON can raise one). * * node tools/alert.js "Service over by 5:00" # low, channel 1, 15 s * node tools/alert.js -c 2 -l critical -d 0 "LTC lost" # held until cleared * node tools/alert.js -c 2 --clear * node tools/alert.js --list * * Options: -c/--channel 1–6 (default 1), -l/--level low|medium|critical * (default low), -d/--duration seconds, 0 to hold until cleared (default the * server's 15), --url http://host:port (default http://127.0.0.1:24500, or * $PRODDASH_URL), --clear, --list. Node 18+, no dependencies. */ const args = process.argv.slice(2); const opts = { channel: 1, level: 'low', duration: undefined, url: process.env.PRODDASH_URL || 'http://127.0.0.1:24500', clear: false, list: false }; const words = []; for (let i = 0; i < args.length; i += 1) { const a = args[i]; const next = () => args[++i]; if (a === '-c' || a === '--channel') opts.channel = Number(next()); else if (a === '-l' || a === '--level') opts.level = String(next() || ''); else if (a === '-d' || a === '--duration') opts.duration = Number(next()); else if (a === '--url') opts.url = String(next() || opts.url); else if (a === '--clear') opts.clear = true; else if (a === '--list') opts.list = true; else if (a === '-h' || a === '--help') { console.log('usage: node tools/alert.js [-c 1-6] [-l low|medium|critical] [-d seconds] [--url http://host:port] "text"\n' + ' node tools/alert.js -c --clear\n' + ' node tools/alert.js --list'); process.exit(0); } else words.push(a); } const base = opts.url.replace(/\/+$/, ''); async function call(path, init) { const res = await fetch(base + path, init); const body = await res.json().catch(() => ({})); if (!res.ok) throw new Error(body.error || `HTTP ${res.status}`); return body; } (async () => { if (opts.list) { const body = await call('/api/alerts'); const active = body.active || []; console.log(`${active.length} active alert${active.length === 1 ? '' : 's'}; colours:`, Object.entries(body.colors || {}).map(([k, v]) => `${k} ${v.join('→')}`).join(', ')); for (const a of active) { console.log(` channel ${a.channel} ${a.level.padEnd(8)} "${a.text}" from ${a.source}` + (a.remainingMs === null ? ' (until cleared)' : ` (${Math.ceil(a.remainingMs / 1000)} s left)`)); } return; } if (opts.clear) { const body = await call(`/api/alerts/${opts.channel}`, { method: 'DELETE' }); console.log(body.cleared ? `cleared channel ${opts.channel}` : `nothing was on channel ${opts.channel}`); return; } const text = words.join(' ').trim(); if (!text) { console.error('say what happened: node tools/alert.js -c 1 -l critical "LTC lost"'); process.exit(2); } const payload = { channel: opts.channel, level: opts.level, text, source: 'cli' }; if (opts.duration !== undefined) payload.duration = opts.duration; const body = await call('/api/alerts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const a = body.alert; console.log(`raised ${a.level} on channel ${a.channel}: "${a.text}"` + (a.remainingMs === null ? ' (until cleared)' : ` (${a.duration} s)`)); })().catch((err) => { console.error(err.message || String(err)); process.exit(1); });