diff --git a/server.js b/server.js index 4da6dafb..f5321ccb 100644 --- a/server.js +++ b/server.js @@ -22,6 +22,7 @@ require('./services/config-validator'); // Include and kicks off the config file /* Include route handlers for API endpoints */ const statusCheck = require('./services/status-check'); // Used by the status check feature, uses GET +const actionButton = require('./services/action-buttons-call'); // Used by action buttons, to execute users web hooks const saveConfig = require('./services/save-config'); // Saves users new conf.yml to file-system const rebuild = require('./services/rebuild-app'); // A script to programmatically trigger a build const systemInfo = require('./services/system-info'); // Basic system info, for resource widget @@ -84,6 +85,16 @@ const app = express() printWarning(`Error running status check for ${req.url}\n`, e); } }) + // GET endpoint for executing web-hooks for action buttons + .use(ENDPOINTS.actionButtonCall, (req, res) => { + try { + actionButton(req.url, async (results) => { + await res.end(results); + }); + } catch (e) { + printWarning(`Error executing web hook for ${req.url}\n`, e); + } + }) // POST Endpoint used to save config, by writing conf.yml to disk .use(ENDPOINTS.save, method('POST', (req, res) => { try { diff --git a/services/action-buttons-call.js b/services/action-buttons-call.js new file mode 100644 index 00000000..d36d9134 --- /dev/null +++ b/services/action-buttons-call.js @@ -0,0 +1,82 @@ +/** + * Endpoint called from the client, to execute action buttons, + * by making GET request to web hook URL, and returning response + */ +const axios = require('axios').default; +const https = require('https'); + +/* 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})` : ''}`; + +/* Kicks of a HTTP request, then formats and renders results */ +const makeRequest = (url, options, render) => { + const { + headers, enableInsecure, maxRedirects, + } = options; + const startTime = new Date(); + // Create HTTPS agent for request + const requestMaker = axios.create({ + httpsAgent: new https.Agent({ + rejectUnauthorized: !enableInsecure, + }), + }); + // Make request, with params + requestMaker.request({ + url, + headers, + maxRedirects, + }).then((response) => ({ + statusCode: response.status, + responseText: response.statusText, + successStatus: true, + timeTaken: (new Date() - startTime), + })).catch((error) => ({ + successStatus: false, + message: makeErrorMessage(error), + })).then((results) => { + // Request completed (either successfully, or failed) - render results + render(JSON.stringify(results)); + }); +}; + +const decodeHeaders = (maybeHeaders) => { + if (!maybeHeaders) return {}; + const decodedHeaders = decodeURIComponent(maybeHeaders); + let parsedHeaders = {}; + try { + parsedHeaders = JSON.parse(decodedHeaders); + } catch (e) { /* Not valid JSON, will just return false */ } + return parsedHeaders; +}; + +/* Returned if the URL param is not present or correct */ +const immediateError = (render) => { + render(JSON.stringify({ + successStatus: false, + message: '❌ Missing URL or Malformed Options', + })); +}; + +/* Main function, will check if a URL present, and call function */ +module.exports = (paramStr, render) => { + // If no parameters passed, then fail + if (!paramStr || !paramStr.includes('=')) { + immediateError(render); + return; + } + // Prepare the parameters, which are got from the URL + const params = new URLSearchParams(paramStr); + const url = decodeURIComponent(params.get('url')); + const maxRedirects = decodeURIComponent(params.get('maxRedirects')) || 0; + const headers = decodeHeaders(params.get('headers')); + const enableInsecure = !!params.get('enableInsecure'); + // Check target URL is present + if (!url || url === 'undefined') immediateError(render); + // Put options together + const options = { + headers, enableInsecure, maxRedirects, + }; + // Make the request + makeRequest(url, options, render); +};