diff --git a/.gitignore b/.gitignore index 6e370a3..bfbf87d 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,4 @@ jsdoc/ # personnal personal/ dist/ +views/template.html diff --git a/index.js b/index.js index 355dc66..1d62a76 100644 --- a/index.js +++ b/index.js @@ -4,9 +4,12 @@ import Addon from "@slimio/addon"; // Require Internal Dependencies import createServer from "./src/httpServer.js"; +import polka from "polka"; // CONSTANTS & Variables const PORT = process.env.PORT || 1338; + +/** @type {polka.Polka} */ let httpServer = null; const ihm = new Addon("ihm", { verbose: true }) diff --git a/package.json b/package.json index d3b38aa..230cafe 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "doc": "jsdoc -c ./jsdoc.json -r -R ./README.md -P ./package.json --verbose", "coverage": "nyc npm test", "report": "nyc report --reporter=html", + "postinstall": "npm run build", "build": "webpack --config webpack.config.cjs" }, "keywords": [ @@ -34,6 +35,7 @@ "@polka/send-type": "^0.5.2", "@slimio/addon": "^0.22.1", "combine-async-iterators": "^1.1.2", + "flatstr": "^1.0.12", "polka": "^1.0.0-next.11", "sirv": "^0.4.2", "zup": "0.0.1" diff --git a/src/bodyParser.js b/src/bodyParser.js index 2c9118b..0979d33 100644 --- a/src/bodyParser.js +++ b/src/bodyParser.js @@ -1,6 +1,9 @@ // Require Node.js Dependencies import queryString from "querystring"; +// Import Third-party Dependencies +import flatstr from "flatstr"; + /** * @async * @function bodyParser @@ -12,6 +15,7 @@ export default async function bodyParser(req) { for await (const chunk of req) { rawBody += chunk; } + flatstr(rawBody); switch (req.headers["content-type"]) { case "application/x-www-form-urlencoded": diff --git a/src/httpServer.js b/src/httpServer.js index e23f80f..8842133 100644 --- a/src/httpServer.js +++ b/src/httpServer.js @@ -4,13 +4,14 @@ import { fileURLToPath } from "url"; import { join } from "path"; import { createRequire } from "module"; import { pipeline } from "stream"; -const { readFile } = fs; +const { readFile, writeFile } = fs; // Require Third-Party dependencies import polka from "polka"; import send from "@polka/send-type"; import sirv from "sirv"; import zup from "zup"; +import flatstr from "flatstr"; import combineAsyncIterators from "combine-async-iterators"; // Node.js CJS constants @@ -23,6 +24,7 @@ const i18n = require("../i18n/english.json"); import bodyParser from "./bodyParser.js"; import { getAllHTMLComponents, + getActivityOverview, DIST_DIR, VIEWS_DIR, @@ -31,37 +33,33 @@ import { DASHBOARD_JSON } from "./utils.js"; -/** - * @async - * @function getActivityOverview - * @param {Addon} ihm ihm addon - * @returns {Promise} - */ -async function getActivityOverview(ihm) { - const [entity, desc, summary] = await Promise.all([ - ihm.sendOne("events.get_entity_by_id", [1]), - ihm.sendOne("events.get_descriptors", [1]), - ihm.sendOne("events.summary_stats") - ]); - - const descriptors = desc.reduce((prev, curr) => { - prev[curr.key] = curr.value; - - return prev; - }, Object.create(null)); - - return { - serverName: entity.name, - descriptors, - summary - }; -} +// CONSTANTS +const kDefaultMenuTemplate = { + dashboard: { + title: i18n.keys.menu.dashboard, + icon: "icon-home" + }, + alarmconsole: { + title: i18n.keys.menu.alarmconsole, + icon: "icon-bell-alt" + }, + alerting: { + title: i18n.keys.menu.alerting, + icon: "icon-eye" + }, + metrics: { + title: i18n.keys.menu.metrics, + icon: "icon-chart-line" + } +}; +const kAvailableModules = new Set(["alarmconsole", "alerting", "dashboard", "metrics"]); +let homePageNeedUpdate = true; /** * @function exportServer * @description Export Polka HTTP Server to the ihm Addon * @param {!Addon} ihm ihm addon - * @returns {any} + * @returns {polka.Polka} */ export default function exportServer(ihm) { const httpServer = polka(); @@ -73,41 +71,34 @@ export default function exportServer(ihm) { httpServer.get("/", async(req, res) => { try { console.time("get_home"); - const [views, data] = await Promise.all([ + const [homePageTemplate, activityData] = await Promise.all([ readFile(join(VIEWS_DIR, "index.html"), "utf-8"), getActivityOverview(ihm) ]); - const menu = { - dashboard: { - title: i18n.keys.menu.dashboard, - icon: "icon-home" - }, - alarmconsole: { - title: i18n.keys.menu.alarmconsole, - icon: "icon-bell-alt" - }, - alerting: { - title: i18n.keys.menu.alerting, - icon: "icon-eye" - }, - metrics: { - title: i18n.keys.menu.metrics, - icon: "icon-chart-line" - } - }; + let renderedHTML = zup(homePageTemplate)(Object.assign(activityData, { menu: kDefaultMenuTemplate, i18n })); + if (homePageNeedUpdate) { + let templateHTML = ""; - let renderedHTML = zup(views)(Object.assign(data, { menu, i18n })); + const HTMLFilesIterators = combineAsyncIterators( + getAllHTMLComponents(), + getAllHTMLComponents(SLIMIO_MODULES_DIR) + ); + for await (const path of HTMLFilesIterators) { + const html = await readFile(path, "utf8"); - const HTMLFilesIterators = combineAsyncIterators( - getAllHTMLComponents(), - getAllHTMLComponents(SLIMIO_MODULES_DIR) - ); - for await (const path of HTMLFilesIterators) { - const html = await readFile(path, "utf8"); + templateHTML += zup(html)({ i18n }); + } + renderedHTML += templateHTML; + homePageNeedUpdate = false; - renderedHTML += zup(html)({ i18n }); + await writeFile(join(VIEWS_DIR, "template.html"), templateHTML); + } + else { + const templateHTML = await readFile(join(VIEWS_DIR, "template.html"), "utf-8"); + renderedHTML += templateHTML; } + flatstr(renderedHTML); send(res, 200, renderedHTML, { "Content-Type": "text/html" }); console.timeEnd("get_home"); @@ -120,6 +111,9 @@ export default function exportServer(ihm) { httpServer.get("/module/:name", async(req, res) => { try { const moduleName = req.params.name; + if (!kAvailableModules.has(moduleName)) { + return send(res, 404, `Unable to found any module with the name '${moduleName}'`); + } const buf = await readFile(join(VIEWS_DIR, "modules", `${moduleName}.html`)); send(res, 200, buf.toString(), { "Content-Type": "text/html" }); @@ -130,17 +124,20 @@ export default function exportServer(ihm) { }); httpServer.get("/alarms", async(req, res) => { - console.time("getAlarms"); - const alarms = await ihm.sendOne("events.get_alarms"); + try { + const alarms = await ihm.sendOne("events.get_alarms"); - // TODO: do this asynchronously - for (const alarm of alarms) { - const entity = await ihm.sendOne("events.get_entity_by_id", [alarm.entity_id]); - alarm.entity_name = entity.name; - } + // TODO: do this asynchronously + for (const alarm of alarms) { + const entity = await ihm.sendOne("events.get_entity_by_id", [alarm.entity_id]); + alarm.entity_name = entity.name; + } - send(res, 200, alarms); - console.timeEnd("getAlarms"); + send(res, 200, alarms); + } + catch (err) { + send(res, 500, err.message); + } }); httpServer.get("/addons", async(req, res) => { @@ -175,15 +172,19 @@ export default function exportServer(ihm) { httpServer.get("/config/:name", async(req, res) => { try { const addonName = req.params.name; + /** @type {Set} */ + const addons = new Set(await ihm.sendOne("gate.list_addons")); + if (!addons.has(addonName)) { + return send(res, 404, `Unable to found any addon with name '${addonName}'`); + } const callbackDescriptorPath = join(CONFIG_DIR, `${addonName}CallbackDescriptor.json`); - const callbackDescriptor = require(callbackDescriptorPath); + const callbackDescriptor = await readFile(callbackDescriptorPath, "utf-8"); - send(res, 200, callbackDescriptor, { "Content-Type": "application/json" }); + return send(res, 200, JSON.parse(callbackDescriptor), { "Content-Type": "application/json" }); } catch (err) { - console.error(err); - send(res, 500, err.message); + return send(res, 500, err.message); } }); @@ -196,7 +197,6 @@ export default function exportServer(ihm) { send(res, 200, response); } catch (err) { - console.error(err); send(res, 500, err.message); } }); diff --git a/src/utils.js b/src/utils.js index b3ce1de..50a9043 100644 --- a/src/utils.js +++ b/src/utils.js @@ -36,3 +36,30 @@ export async function* getAllHTMLComponents(directoryLocation = COMPONENTS_DIR) } } } + +/** + * @async + * @function getActivityOverview + * @description get all the stats for the IHM home page... like the number of entities and mic. + * @param {Addon} ihm ihm addon + * @returns {Promise} + */ +export async function getActivityOverview(ihm) { + const [entity, desc, summary] = await Promise.all([ + ihm.sendOne("events.get_entity_by_id", [1]), + ihm.sendOne("events.get_descriptors", [1]), + ihm.sendOne("events.summary_stats") + ]); + + const descriptors = desc.reduce((prev, curr) => { + prev[curr.key] = curr.value; + + return prev; + }, Object.create(null)); + + return { + serverName: entity.name, + descriptors, + summary + }; +}