diff --git a/server.js b/server.js index 9bdbca41..d1401c47 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,8 @@ const os = require('os'); require('./src/utils/ConfigValidator'); +const pingUrl = require('./services/ping'); + const isDocker = !!process.env.IS_DOCKER; /* Checks env var for port. If undefined, will use Port 80 for Docker, or 4000 for metal */ @@ -64,8 +66,16 @@ const printWelcomeMessage = () => { try { connect() - .use(serveStatic(`${__dirname}/dist`)) - .use(serveStatic(`${__dirname}/public`, { index: 'default.html' })) + .use(serveStatic(`${__dirname}/dist`)) /* Serves up the main built application to the root */ + .use(serveStatic(`${__dirname}/public`, { index: 'default.html' })) /* During build, a custom page will be served */ + .use('/ping', (req, res) => { /* This root returns the status of a given service - used for uptime monitoring */ + try { + pingUrl(req.url, async (results) => { + await res.end(results); + }); + // next(); + } catch (e) { console.warn(`Error running ping check for ${req.url}\n`, e); } + }) .listen(port, () => { try { printWelcomeMessage(); } catch (e) { console.log('Dashy is Starting...'); } }); diff --git a/services/ping.js b/services/ping.js new file mode 100644 index 00000000..43e5caa4 --- /dev/null +++ b/services/ping.js @@ -0,0 +1,66 @@ +/** + * This file contains the Node.js code, used for the optional status check feature + * It accepts a single url parameter, and will make an empty GET request to that + * endpoint, and then resolve the response status code, time taken, and short message + */ +const axios = require('axios').default; + +/* Determines if successful from the HTTP response code */ +const getResponseType = (code) => { + if (Number.isNaN(code)) return false; + const numericCode = parseInt(code, 10); + return (numericCode >= 200 && numericCode <= 302); +}; + +/* Makes human-readable response text for successful check */ +const makeMessageText = (data) => `${data.successStatus ? '✅' : '⚠️'} ` + + `${data.serverName || 'Server'} responded with ` + + `${data.statusCode} - ${data.statusText}. ` + + `\n⏱️Took ${data.timeTaken} ms`; + +/* Makes human-readable response text for failed check */ +const makeErrorMessage = (data) => `❌ Service Unavailable: ${data.hostname || 'Server'} ` + + `resulted in ${data.code || 'a fatal error'} ${data.errno ? `(${data.errno})` : ''}`; + +const makeErrorMessage2 = (data) => `❌ Service Error - ` + + `${data.status} - ${data.statusText}`; + +/* Kicks of a HTTP request, then formats and renders results */ +const makeRequest = (url, render) => { + const startTime = new Date(); + axios.get(url) + .then((response) => { + const statusCode = response.status; + const { statusText } = response; + const successStatus = getResponseType(statusCode); + const serverName = response.request.socket.servername; + const timeTaken = (new Date() - startTime); + const results = { + statusCode, statusText, serverName, successStatus, timeTaken, + }; + const messageText = makeMessageText(results); + results.message = messageText; + return results; + }) + .catch((error) => { + render(JSON.stringify({ + successStatus: false, + message: error.response ? makeErrorMessage2(error.response) : makeErrorMessage(error), + })); + }).then((results) => { + render(JSON.stringify(results)); + }); +}; + +/* Main function, will check if a URL present, and call function */ +module.exports = (params, render) => { + if (!params || !params.includes('=')) { + render(JSON.stringify({ + success: false, + message: '❌ Malformed URL', + })); + } else { + const url = params.split('=')[1]; + makeRequest(url, render); + } +};