From 2a6d98ff01cd8fd4d43a1380b129df96c3371a17 Mon Sep 17 00:00:00 2001 From: Joseph <40335314+JRedOW@users.noreply.github.com> Date: Thu, 2 Feb 2023 23:33:48 -0500 Subject: [PATCH] Feat: Expand and Simplify Badge Functionality (#2211) * [expanding badges] added new configs * [expanding badges] recieve ping in getPreviousHeartbeat() * [expanding badges] re-added original new badges * [expanding badges] recreate parity between old and new badges * [expanding badges] fix linting --- server/config.js | 6 + server/model/monitor.js | 2 +- server/routers/api-router.js | 246 ++++++++++++++++++++++++++++++++++- 3 files changed, 248 insertions(+), 6 deletions(-) diff --git a/server/config.js b/server/config.js index 0523e7078..43a40f672 100644 --- a/server/config.js +++ b/server/config.js @@ -4,6 +4,7 @@ const demoMode = args["demo"] || false; const badgeConstants = { naColor: "#999", defaultUpColor: "#66c20a", + defaultWarnColor: "#eed202", defaultDownColor: "#c2290a", defaultPendingColor: "#f8a306", defaultMaintenanceColor: "#1747f5", @@ -13,6 +14,11 @@ const badgeConstants = { defaultPingLabelSuffix: "h", defaultUptimeValueSuffix: "%", defaultUptimeLabelSuffix: "h", + defaultCertExpValueSuffix: " days", + defaultCertExpLabelSuffix: "h", + // Values Come From Default Notification Times + defaultCertExpireWarnDays: "14", + defaultCertExpireDownDays: "7" }; module.exports = { diff --git a/server/model/monitor.js b/server/model/monitor.js index c3e91f935..4cbb56e1a 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1255,7 +1255,7 @@ class Monitor extends BeanModel { */ static async getPreviousHeartbeat(monitorID) { return await R.getRow(` - SELECT status, time FROM heartbeat + SELECT ping, status, time FROM heartbeat WHERE id = (select MAX(id) from heartbeat where monitor_id = ?) `, [ monitorID diff --git a/server/routers/api-router.js b/server/routers/api-router.js index e95fd045e..665163aee 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -145,7 +145,7 @@ router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response const heartbeat = await Monitor.getPreviousHeartbeat(requestedMonitorId); const state = overrideValue !== undefined ? overrideValue : heartbeat.status; - badgeValues.label = label ?? ""; + badgeValues.label = label ?? "Status"; switch (state) { case DOWN: badgeValues.color = downColor; @@ -212,7 +212,7 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques const badgeValues = { style }; if (!publicMonitor) { - // return a "N/A" badge in naColor (grey), if monitor is not public / not available / non exsitant + // return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent badgeValues.message = "N/A"; badgeValues.color = badgeConstants.naColor; } else { @@ -228,8 +228,11 @@ router.get("/api/badge/:id/uptime/:duration?", cache("5 minutes"), async (reques badgeValues.color = color ?? percentageToColor(uptime); // use a given, custom labelColor or use the default badge label color (defined by badge-maker) badgeValues.labelColor = labelColor ?? ""; - // build a lable string. If a custom label is given, override the default one (requestedDuration) - badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]); + // build a label string. If a custom label is given, override the default one (requestedDuration) + badgeValues.label = filterAndJoin([ + labelPrefix, + label ?? `Uptime (${requestedDuration}${labelSuffix})`, + ]); badgeValues.message = filterAndJoin([ prefix, `${cleanUptime * 100}`, suffix ]); } @@ -290,7 +293,7 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request, // use a given, custom labelColor or use the default badge label color (defined by badge-maker) badgeValues.labelColor = labelColor ?? ""; // build a lable string. If a custom label is given, override the default one (requestedDuration) - badgeValues.label = filterAndJoin([ labelPrefix, label ?? requestedDuration, labelSuffix ]); + badgeValues.label = filterAndJoin([ labelPrefix, label ?? `Avg. Ping (${requestedDuration}${labelSuffix})` ]); badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]); } @@ -304,4 +307,237 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request, } }); +router.get("/api/badge/:id/avg-response/:duration?", cache("5 minutes"), async (request, response) => { + allowAllOrigin(response); + + const { + label, + labelPrefix, + labelSuffix, + prefix, + suffix = badgeConstants.defaultPingValueSuffix, + color = badgeConstants.defaultPingColor, + labelColor, + style = badgeConstants.defaultStyle, + value, // for demo purpose only + } = request.query; + + try { + const requestedMonitorId = parseInt(request.params.id, 10); + + // Default duration is 24 (h) if not defined in queryParam, limited to 720h (30d) + const requestedDuration = Math.min( + request.params.duration + ? parseInt(request.params.duration, 10) + : 24, + 720 + ); + const overrideValue = value && parseFloat(value); + + const publicAvgPing = parseInt(await R.getCell(` + SELECT AVG(ping) FROM monitor_group, \`group\`, heartbeat + WHERE monitor_group.group_id = \`group\`.id + AND heartbeat.time > DATETIME('now', ? || ' hours') + AND heartbeat.ping IS NOT NULL + AND public = 1 + AND heartbeat.monitor_id = ? + `, + [ -requestedDuration, requestedMonitorId ] + )); + + const badgeValues = { style }; + + if (!publicAvgPing) { + // return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent + + badgeValues.message = "N/A"; + badgeValues.color = badgeConstants.naColor; + } else { + const avgPing = parseInt(overrideValue ?? publicAvgPing); + + badgeValues.color = color; + // use a given, custom labelColor or use the default badge label color (defined by badge-maker) + badgeValues.labelColor = labelColor ?? ""; + // build a label string. If a custom label is given, override the default one (requestedDuration) + badgeValues.label = filterAndJoin([ + labelPrefix, + label ?? `Avg. Response (${requestedDuration}h)`, + labelSuffix, + ]); + badgeValues.message = filterAndJoin([ prefix, avgPing, suffix ]); + } + + // build the SVG based on given values + const svg = makeBadge(badgeValues); + + response.type("image/svg+xml"); + response.send(svg); + } catch (error) { + send403(response, error.message); + } +}); + +router.get("/api/badge/:id/cert-exp", cache("5 minutes"), async (request, response) => { + allowAllOrigin(response); + + const date = request.query.date; + + const { + label, + labelPrefix, + labelSuffix, + prefix, + suffix = date ? "" : badgeConstants.defaultCertExpValueSuffix, + upColor = badgeConstants.defaultUpColor, + warnColor = badgeConstants.defaultWarnColor, + downColor = badgeConstants.defaultDownColor, + warnDays = badgeConstants.defaultCertExpireWarnDays, + downDays = badgeConstants.defaultCertExpireDownDays, + labelColor, + style = badgeConstants.defaultStyle, + value, // for demo purpose only + } = request.query; + + try { + const requestedMonitorId = parseInt(request.params.id, 10); + + const overrideValue = value && parseFloat(value); + + let publicMonitor = await R.getRow(` + SELECT monitor_group.monitor_id FROM monitor_group, \`group\` + WHERE monitor_group.group_id = \`group\`.id + AND monitor_group.monitor_id = ? + AND public = 1 + `, + [ requestedMonitorId ] + ); + + const badgeValues = { style }; + + if (!publicMonitor) { + // return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent + + badgeValues.message = "N/A"; + badgeValues.color = badgeConstants.naColor; + } else { + const tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ + requestedMonitorId, + ]); + + if (!tlsInfoBean) { + // return a "No/Bad Cert" badge in naColor (grey), if no cert saved (does not save bad certs?) + badgeValues.message = "No/Bad Cert"; + badgeValues.color = badgeConstants.naColor; + } else { + const tlsInfo = JSON.parse(tlsInfoBean.info_json); + + if (!tlsInfo.valid) { + // return a "Bad Cert" badge in naColor (grey), when cert is not valid + badgeValues.message = "Bad Cert"; + badgeValues.color = badgeConstants.downColor; + } else { + const daysRemaining = parseInt(overrideValue ?? tlsInfo.certInfo.daysRemaining); + + if (daysRemaining > warnDays) { + badgeValues.color = upColor; + } else if (daysRemaining > downDays) { + badgeValues.color = warnColor; + } else { + badgeValues.color = downColor; + } + // use a given, custom labelColor or use the default badge label color (defined by badge-maker) + badgeValues.labelColor = labelColor ?? ""; + // build a label string. If a custom label is given, override the default one + badgeValues.label = filterAndJoin([ + labelPrefix, + label ?? "Cert Exp.", + labelSuffix, + ]); + badgeValues.message = filterAndJoin([ prefix, date ? tlsInfo.certInfo.validTo : daysRemaining, suffix ]); + } + } + } + + // build the SVG based on given values + const svg = makeBadge(badgeValues); + + response.type("image/svg+xml"); + response.send(svg); + } catch (error) { + send403(response, error.message); + } +}); + +router.get("/api/badge/:id/response", cache("5 minutes"), async (request, response) => { + allowAllOrigin(response); + + const { + label, + labelPrefix, + labelSuffix, + prefix, + suffix = badgeConstants.defaultPingValueSuffix, + color = badgeConstants.defaultPingColor, + labelColor, + style = badgeConstants.defaultStyle, + value, // for demo purpose only + } = request.query; + + try { + const requestedMonitorId = parseInt(request.params.id, 10); + + const overrideValue = value && parseFloat(value); + + let publicMonitor = await R.getRow(` + SELECT monitor_group.monitor_id FROM monitor_group, \`group\` + WHERE monitor_group.group_id = \`group\`.id + AND monitor_group.monitor_id = ? + AND public = 1 + `, + [ requestedMonitorId ] + ); + + const badgeValues = { style }; + + if (!publicMonitor) { + // return a "N/A" badge in naColor (grey), if monitor is not public / not available / non existent + + badgeValues.message = "N/A"; + badgeValues.color = badgeConstants.naColor; + } else { + const heartbeat = await Monitor.getPreviousHeartbeat( + requestedMonitorId + ); + + if (!heartbeat.ping) { + // return a "N/A" badge in naColor (grey), if previous heartbeat has no ping + + badgeValues.message = "N/A"; + badgeValues.color = badgeConstants.naColor; + } else { + const ping = parseInt(overrideValue ?? heartbeat.ping); + + badgeValues.color = color; + // use a given, custom labelColor or use the default badge label color (defined by badge-maker) + badgeValues.labelColor = labelColor ?? ""; + // build a label string. If a custom label is given, override the default one + badgeValues.label = filterAndJoin([ + labelPrefix, + label ?? "Response", + labelSuffix, + ]); + badgeValues.message = filterAndJoin([ prefix, ping, suffix ]); + } + } + + // build the SVG based on given values + const svg = makeBadge(badgeValues); + + response.type("image/svg+xml"); + response.send(svg); + } catch (error) { + send403(response, error.message); + } +}); + module.exports = router;