From e6332dffc9c59ca233ba696f12f50c185e583cb4 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 17:58:12 -0400 Subject: [PATCH 01/57] Update join command to accept a specific node ID --- Server/commands/join.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Server/commands/join.js b/Server/commands/join.js index 6dab9a7..e7f59b3 100644 --- a/Server/commands/join.js +++ b/Server/commands/join.js @@ -1,7 +1,7 @@ // Modules const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); -const { getMembersInRole, getAllClientIds, filterAutocompleteValues } = require("../utilities/utils"); +const { getMembersInRole, getAllClientIds, filterAutocompleteValues, getKeyByArrayValue } = require("../utilities/utils"); const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests"); const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId, getAllConnections } = require("../utilities/mysqlHandler"); @@ -14,15 +14,16 @@ const log = new DebugBuilder("server", "join"); * @param {*} presetName The preset name to listen to on the client * @param {*} channelId The channel ID to join the bot to * @param {*} connections EITHER A collection of clients that are currently connected OR a single discord client ID (NOT dev portal ID) that should be used to join the server with + * @param {number} nodeId [OPTIONAL] The node ID to join with (will join with another node if given node is not available) * @returns */ -async function joinServerWrapper(presetName, channelId, connections) { +async function joinServerWrapper(presetName, channelId, connections, nodeId = 0) { // Get nodes online var onlineNodes = await new Promise((recordResolve, recordReject) => { getOnlineNodes((nodeRows) => { recordResolve(nodeRows); }); - }); + }); // Check which nodes have the selected preset onlineNodes = onlineNodes.filter(node => node.presets.includes(presetName)); @@ -63,7 +64,10 @@ async function joinServerWrapper(presetName, channelId, connections) { selectedClientId = availableClientIds[0]; } - const selectedNode = nodesCurrentlyAvailable[0]; + let selectedNode; + if (nodeId > 0) selectedNode = getKeyByArrayValue(nodesCurrentlyAvailable, nodeId); + + if (!selectedNode) selectedNode = nodesCurrentlyAvailable[0]; const reqOptions = new requestOptions("/bot/join", "POST", selectedNode.ip, selectedNode.port); const postObject = { From c38bca414441c69f81f83b4431d4d1ca4ad07a54 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 17:58:35 -0400 Subject: [PATCH 02/57] Add jsDoc to leaveServerWrapper --- Server/commands/leave.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/commands/leave.js b/Server/commands/leave.js index c5ad0b2..e65de07 100644 --- a/Server/commands/leave.js +++ b/Server/commands/leave.js @@ -8,6 +8,11 @@ const { checkNodeConnectionByClientId, removeNodeConnectionByNodeId, getAllConne // Global Vars const log = new DebugBuilder("server", "leave"); +/** + * + * @param {*} clientIdObject The client ID object for the node to leave the server. Either 'clientId'||'name' can be set. + * @returns + */ async function leaveServerWrapper(clientIdObject) { if (!clientIdObject.clientId || !clientIdObject.name) return log.ERROR("Tried to leave server without client ID and/or Name"); From c35d3f3fa76420b28b414ccf15edb74bc26b138a Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:14:40 -0400 Subject: [PATCH 03/57] Update adminController to use join/leave command wrappers --- Server/controllers/adminController.js | 90 ++++++++------------------- 1 file changed, 27 insertions(+), 63 deletions(-) diff --git a/Server/controllers/adminController.js b/Server/controllers/adminController.js index 46b6b8c..fdef282 100644 --- a/Server/controllers/adminController.js +++ b/Server/controllers/adminController.js @@ -7,6 +7,9 @@ const log = new DebugBuilder("server", "adminController"); const mysqlHandler = require("../utilities/mysqlHandler"); const utils = require("../utilities/utils"); const requests = require("../utilities/httpRequests"); +const { leaveServerWrapper } = require("../commands/leave"); +const { joinServerWrapper } = require("../commands/join"); + /** Get the presets of all online nodes, can be used for functions * @@ -15,27 +18,10 @@ const requests = require("../utilities/httpRequests"); */ async function getPresetsOfOnlineNodes(callback) { mysqlHandler.getOnlineNodes((onlineNodes) => { - let systems = {}; - onlineNodes.forEach(onlineNode => { - systems[onlineNode.id] = utils.BufferToJson(onlineNode.nearbySystems); - }); - - return callback(systems); + return callback(onlineNodes); }); } -async function requestNodeListenToPreset(preset, nodeId, callback) { - mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ - reqOptions = new requests.requestOptions("/bot/join", "POST", nodeObject.ip, nodeObject.port); - requests.sendHttpRequest(reqOptions, JSON.stringify({ - "channelID": process.env.DEFAULT_VOICE_CHANNEL_ID, - "presetName": preset - }), (responseObject) => { - return callback(responseObject) - }); - }) -} - async function getNodeBotStatus(nodeId, callback) { mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); @@ -51,24 +37,6 @@ async function getNodeBotStatus(nodeId, callback) { }); } -async function requestNodeLeaveServer(nodeId, callback) { - getNodeBotStatus(nodeId, (responseObject) => { - if (responseObject === false) { - // Bot is joined - mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ - reqOptions = new requests.requestOptions("/bot/leave", "POST", nodeObject.ip, nodeObject.port); - requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { - return callback(responseObject); - }); - }); - } - else { - // Bot is free - return callback(false); - } - }) -} - /** Return to requests for the presets of all online nodes, cannot be used in functions * @@ -87,43 +55,39 @@ exports.getAvailablePresets = async (req, res) => { * * @param {*} req Express request parameter * @var {*} req.body.preset The preset to join (REQ) - * @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset) + * @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset) + * @var {*} req.body.clientId The ID of the client that we want to join with + * @var {*} req.body.channelId The channel Id of the discord channel to join * @param {*} res Express response parameter */ exports.joinPreset = async (req, res) => { - if (!req.body.preset) return res.status(400).json("No preset specified"); - await getPresetsOfOnlineNodes((systems) => { - const systemsWithSelectedPreset = Object.values(systems).filter(nodePresets => nodePresets.includes(req.body.preset)).length - if (!systemsWithSelectedPreset) return res.status(400).json("No system online with that preset"); - if (systemsWithSelectedPreset > 1) { - if (!req.body.nodeId) return res.status(175).json("Multiple locations with the selected channel, please specify a nodeID (nodeId)") - requestNodeListenToPreset(req.body.preset, req.body.nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Timeout reached"); - return res.sendStatus(responseObject.statusCode); - }); - } - else { - let nodeId; - if (!req.body.nodeId) nodeId = utils.getKeyByArrayValue(systems, req.body.preset); - else nodeId = req.body.nodeId; - requestNodeListenToPreset(req.body.preset, nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Timeout reached"); - return res.sendStatus(responseObject.statusCode); - }); - } - }); + if (!req.body.preset) return res.status(400).json("No preset specified"); + if (!req.body.nodeId) return res.status(400).json("No node ID specified"); + if (!req.body.clientId) return res.status(400).json("No client ID specified"); + if (!req.body.channelId) return res.status(400).json("No channel ID specified"); + + const preset = req.body.preset; + const nodeId = req.body.nodeId; + const clientId = req.body.clientId; + const channelId = req.body.channelId; + + const joinedClient = await joinServerWrapper(preset, channelId, clientId, nodeId); + if (!joinedClient) return res.send(400).json("No joined client"); + return res.send(200).json(joinedClient); } /** Request a node to join the server listening to a specific preset * * @param {*} req Express request parameter * @param {*} res Express response parameter + * @var {*} req.body.clientId The ID of the client to disconnect */ exports.leaveServer = async (req, res) => { - if (!req.body.nodeId) return res.status(400).json("No nodeID specified"); + if (!req.body.clientId) return res.status(400).json("No clientID specified"); - requestNodeLeaveServer(req.body.nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Bot not joined to server"); - return res.sendStatus(responseObject.statusCode); - }); + const clientId = req.body.clientId; + + await leaveServerWrapper(clientId) + + return res.send(200); } \ No newline at end of file From 5d6d86fa470602229957572ad6f081d0f17ee3fa Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:14:53 -0400 Subject: [PATCH 04/57] Update jsDoc in nodeController --- Server/controllers/nodesController.js | 42 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 9c9d274..45d26e8 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -7,13 +7,14 @@ const utils = require("../utilities/utils"); const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.js"); const { nodeObject } = require("../utilities/recordHelper.js"); const { joinServerWrapper } = require("../commands/join"); +const { leaveServerWrapper } = require("../commands/leave"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; /** * - * @param {*} req - * @param {*} res + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router */ exports.listAllNodes = async (req, res) => { getAllNodes((allNodes) => { @@ -23,7 +24,11 @@ exports.listAllNodes = async (req, res) => { }); } -// Add a new node to the storage +/** + * Add a new node to the storage + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.newNode = async (req, res) => { if (!req.body.name) return res.status(400).json("No name specified for new node"); @@ -53,7 +58,11 @@ exports.newNode = async (req, res) => { } } -// Get the known info for the node specified +/** Get the known info for the node specified + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.getNodeInfo = async (req, res) => { if (!req.query.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.query.id, (nodeInfo) => { @@ -61,7 +70,11 @@ exports.getNodeInfo = async (req, res) => { }) } -// Updates the information received from the client based on ID +/** Updates the information received from the client based on ID + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.nodeCheckIn = async (req, res) => { if (!req.body.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.body.id, (nodeInfo) => { @@ -126,6 +139,8 @@ exports.nodeCheckIn = async (req, res) => { /** * Request the node to join the specified server/channel and listen to the specified resource * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router * @param req.body.clientId The client ID to join discord with (NOT dev portal ID, right click user -> Copy ID) * @param req.body.channelId The Channel ID to join in Discord * @param req.body.presetName The preset name to listen to in Discord @@ -135,6 +150,17 @@ exports.requestNodeJoinServer = async (req, res) => { await joinServerWrapper(req.body.presetName, req.body.channelId, req.body.clientId); } +/** + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + * @param {*} req.body.clientId The client ID to request to leave the server + */ +exports.requestNodeLeaveServer = async (req, res) => { + if (!req.body.ClientId) return res.status(400).json("Missing client ID in request"); + await leaveServerWrapper({clientId: req.body.ClientId}); +} + /** * The node monitor service, this will periodically check in on the online nodes to make sure they are still online */ @@ -143,6 +169,9 @@ exports.nodeMonitorService = class nodeMonitorService { this.log = new DebugBuilder("server", "nodeMonitorService"); } + /** + * Start the node monitor service in the background + */ async start(){ // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start await new Promise(resolve => setTimeout(resolve, refreshInterval/10)); @@ -161,6 +190,9 @@ exports.nodeMonitorService = class nodeMonitorService { } } + /** + * Check in with all online nodes and mark any nodes that are actually offline + */ async checkInWithOnlineNodes(){ getOnlineNodes((nodes) => { this.log.DEBUG("Online Nodes: ", nodes); From 6b37a480610d20148832d143019db9e74a6dc498 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:15:17 -0400 Subject: [PATCH 05/57] Update jsDoc in utils --- Server/utilities/utils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Server/utilities/utils.js b/Server/utilities/utils.js index 2acef73..06aa75b 100644 --- a/Server/utilities/utils.js +++ b/Server/utilities/utils.js @@ -119,7 +119,12 @@ exports.getClientObjectByClientID = (clientId) => { return undefined } - +/** + * Wrapper to filter auto complete + * + * @param {*} interaction + * @param {*} options + */ exports.filterAutocompleteValues = async (interaction, options) => { // Get the command used const command = interaction.command; From 2e22fa66a614556478f69598ea37bad4bbada461 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:16:42 -0400 Subject: [PATCH 06/57] Initial bones for #37 --- Server/public/res/css/main.css | 142 ++++++++++++++++++++++++++ Server/public/res/js/controller.js | 3 + Server/routes/index.js | 27 ++++- Server/views/controller.ejs | 11 ++ Server/views/index.ejs | 144 +++++++++++++++++++++++++-- Server/views/node.ejs | 41 ++++++++ Server/views/partials/bodyEnd.ejs | 11 ++ Server/views/partials/head.ejs | 9 ++ Server/views/partials/htmlFooter.ejs | 1 + Server/views/partials/htmlHead.ejs | 6 ++ Server/views/partials/navbar.ejs | 40 ++++++++ Server/views/partials/nodeCard.ejs | 57 +++++++++++ Server/views/partials/sidebar.ejs | 83 +++++++++++++++ 13 files changed, 561 insertions(+), 14 deletions(-) create mode 100644 Server/public/res/css/main.css create mode 100644 Server/public/res/js/controller.js create mode 100644 Server/views/controller.ejs create mode 100644 Server/views/node.ejs create mode 100644 Server/views/partials/bodyEnd.ejs create mode 100644 Server/views/partials/head.ejs create mode 100644 Server/views/partials/htmlFooter.ejs create mode 100644 Server/views/partials/htmlHead.ejs create mode 100644 Server/views/partials/navbar.ejs create mode 100644 Server/views/partials/nodeCard.ejs create mode 100644 Server/views/partials/sidebar.ejs diff --git a/Server/public/res/css/main.css b/Server/public/res/css/main.css new file mode 100644 index 0000000..d057e2a --- /dev/null +++ b/Server/public/res/css/main.css @@ -0,0 +1,142 @@ +.node-card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #eff0f2; + border-radius: 1rem; + margin-bottom: 24px; + box-shadow: 0 2px 3px #e4e8f0; +} + +.avatar-md { + height: 4rem; + width: 4rem; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #f1f3f7; + border: 1px solid #eff0f2; + border-radius: 0.75rem; +} + +.avatar-title { + align-items: center; + background-color: #3b76e1; + color: #fff; + display: flex; + font-weight: 500; + height: 100%; + justify-content: center; + width: 100%; +} + +.bg-soft-primary { + background-color: rgba(59, 118, 225, .25) !important; +} + +a { + text-decoration: none !important; +} + +.badge-soft-danger { + color: #f56e6e !important; + background-color: rgba(245, 110, 110, .1); +} + +.badge-soft-success { + color: #63ad6f !important; + background-color: rgba(99, 173, 111, .1); +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.badge { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.75rem; +} + +/* Info Card Section */ +.info-card { + background-color: #fff; + border-radius: 10px; + border: none; + position: relative; + margin-bottom: 30px; + box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1); +} + +.info-card .card-statistic .card-icon-large .bi { + font-size: 110px; +} + +.info-card .card-statistic .card-icon { + text-align: center; + line-height: 50px; + margin-left: 15px; + color: #000; + position: absolute; + right: -5px; + top: 20px; + opacity: 0.1; +} + +/* Info Card Background Colors */ + +.l-bg-cherry { + background: linear-gradient(to right, #493240, #f09) !important; + color: #fff; +} + +.l-bg-blue-dark { + background: linear-gradient(to right, #373b44, #4286f4) !important; + color: #fff; +} + +.l-bg-green-dark { + background: linear-gradient(to right, #0a504a, #38ef7d) !important; + color: #fff; +} + +.l-bg-orange-dark { + background: linear-gradient(to right, #a86008, #ffba56) !important; + color: #fff; +} + +.l-bg-cyan { + background: linear-gradient(135deg, #289cf5, #84c0ec) !important; + color: #fff; +} + +.l-bg-green { + background: linear-gradient(135deg, #23bdb8 0%, #43e794 100%) !important; + color: #fff; +} + +.l-bg-orange { + background: linear-gradient(to right, #f9900e, #ffba56) !important; + color: #fff; +} + +/* Global Section */ +.sidebar-container { + min-height: 94.2vh; +} \ No newline at end of file diff --git a/Server/public/res/js/controller.js b/Server/public/res/js/controller.js new file mode 100644 index 0000000..04c0fd9 --- /dev/null +++ b/Server/public/res/js/controller.js @@ -0,0 +1,3 @@ +function sendNodeHeartbeat(nodeId) { + console.log(nodeId); +} \ No newline at end of file diff --git a/Server/routes/index.js b/Server/routes/index.js index a188406..1f43b2a 100644 --- a/Server/routes/index.js +++ b/Server/routes/index.js @@ -1,11 +1,12 @@ -const libCore = require("../libCore"); var express = require('express'); var router = express.Router(); +const { getAllNodes, getNodeInfoFromId } = require("../utilities/mysqlHandler"); + /* GET home page. */ router.get('/', (req, res) => { - var sources = libCore.getSources(); - //res.render('index', { "sources": sources }); + //var sources = libCore.getSources(); + return res.render('index'); var htmlOutput = ""; @@ -28,4 +29,24 @@ router.get('/', (req, res) => { res.send(htmlOutput); }); +/* GET node controller page. */ +router.get('/controller', async (req, res) => { + var nodes = await new Promise((recordResolve, recordReject) => { + getAllNodes((nodeRows) => { + recordResolve(nodeRows); + }); + }); + + //var sources = libCore.getSources(); + return res.render('controller', {'nodes' : nodes}); +}); + +/* GET individual node page. */ +router.get('/node/:id', async (req, res) => { + var node = await getNodeInfoFromId(req.params.id); + + //var sources = libCore.getSources(); + return res.render('node', {'node' : node}); +}); + module.exports = router; diff --git a/Server/views/controller.ejs b/Server/views/controller.ejs new file mode 100644 index 0000000..28099a9 --- /dev/null +++ b/Server/views/controller.ejs @@ -0,0 +1,11 @@ +<%- include('partials/htmlHead.ejs') %> +
+
+ <% for(const node of nodes) {%> + <%- include('partials/nodeCard.ejs', {'node': node}) %> + <% } %> +
+
+<%- include('partials/bodyEnd.ejs') %> + +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/index.ejs b/Server/views/index.ejs index 7b7a1d6..b6524a3 100644 --- a/Server/views/index.ejs +++ b/Server/views/index.ejs @@ -1,11 +1,133 @@ - - - - <%= title %> - - - -

<%= title %>

-

Welcome to <%= title %>

- - +<%- include('partials/htmlHead.ejs') %> +
+
+
+
+
+
+
+
New Orders
+
+
+
+

+ 3,243 +

+
+
+ 12.5% +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
New Orders
+
+
+
+

+ 3,243 +

+
+
+ 12.5% +
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
Phyllis Gatlin
+ Full Stack Developer +
+
+
+

070 2860 5375 +

+

+ PhyllisGatlin@spy.com

+

52 + Ilchester MYBSTER 9WX

+
+
+
+
+ +
+
+
+ +
+
+
+
Diana Owens
+ UI/UX Designer +
+
+
+

087 6321 3235 +

+

+ DianaOwens@spy.com

+

52 + Ilchester MYBSTER 9WX

+
+
+ + +
+
+
+
+ + +
+
+<%- include('partials/bodyEnd.ejs') %> + +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/node.ejs b/Server/views/node.ejs new file mode 100644 index 0000000..941e326 --- /dev/null +++ b/Server/views/node.ejs @@ -0,0 +1,41 @@ +<%- include('partials/htmlHead.ejs') %> +
+
+ Account Details + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
+
+
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+ + +
+
+
+<%- include('partials/bodyEnd.ejs') %> +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/bodyEnd.ejs b/Server/views/partials/bodyEnd.ejs new file mode 100644 index 0000000..2a347cf --- /dev/null +++ b/Server/views/partials/bodyEnd.ejs @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/Server/views/partials/head.ejs b/Server/views/partials/head.ejs new file mode 100644 index 0000000..3429389 --- /dev/null +++ b/Server/views/partials/head.ejs @@ -0,0 +1,9 @@ + + + + Bootstrap demo + + + + \ No newline at end of file diff --git a/Server/views/partials/htmlFooter.ejs b/Server/views/partials/htmlFooter.ejs new file mode 100644 index 0000000..62d09b8 --- /dev/null +++ b/Server/views/partials/htmlFooter.ejs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Server/views/partials/htmlHead.ejs b/Server/views/partials/htmlHead.ejs new file mode 100644 index 0000000..5a3ef3b --- /dev/null +++ b/Server/views/partials/htmlHead.ejs @@ -0,0 +1,6 @@ + + + <%- include('head.ejs') %> + + <%- include('navbar.ejs') %> + <%- include('sidebar.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs new file mode 100644 index 0000000..ccac10e --- /dev/null +++ b/Server/views/partials/navbar.ejs @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/Server/views/partials/nodeCard.ejs b/Server/views/partials/nodeCard.ejs new file mode 100644 index 0000000..b676560 --- /dev/null +++ b/Server/views/partials/nodeCard.ejs @@ -0,0 +1,57 @@ +
+
+
+ +
+
+ +
+
+
+ <%= node.name %> +
+ <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
+
+
+

+ + <%= node.location %> +

+

+ + + <%= node.ip %>:<%= node.port %> + +

+

+

+

+
+
+
+
\ No newline at end of file diff --git a/Server/views/partials/sidebar.ejs b/Server/views/partials/sidebar.ejs new file mode 100644 index 0000000..8eb5815 --- /dev/null +++ b/Server/views/partials/sidebar.ejs @@ -0,0 +1,83 @@ +
+
+
+ +
+
\ No newline at end of file From e522326576e1cb3164c186fc5adcb35e9818de7a Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 23:30:41 -0400 Subject: [PATCH 07/57] Additional changes for #37 - Updating side bar - Updating nav bar - Adding node details page - Adding controller page - Updating routes --- Server/controllers/nodesController.js | 144 ++++++++-------- Server/public/res/css/main.css | 36 +++- Server/public/res/js/node.js | 50 ++++++ Server/routes/nodes.js | 5 +- Server/utilities/recordHelper.js | 3 +- Server/views/node.ejs | 179 ++++++++++++++++---- Server/views/partials/bodyEnd.ejs | 5 +- Server/views/partials/modifySystemModal.ejs | 54 ++++++ Server/views/partials/navbar.ejs | 4 +- Server/views/partials/sidebar.ejs | 59 +------ 10 files changed, 376 insertions(+), 163 deletions(-) create mode 100644 Server/public/res/js/node.js create mode 100644 Server/views/partials/modifySystemModal.ejs diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 45d26e8..c1999ed 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -2,15 +2,61 @@ const { DebugBuilder } = require("../utilities/debugBuilder.js"); const log = new DebugBuilder("server", "nodesController"); // Utilities -const {getAllNodes, addNewNode, updateNodeInfo, getNodeInfoFromId, getOnlineNodes } = require("../utilities/mysqlHandler"); +const { getAllNodes, addNewNode, updateNodeInfo, getNodeInfoFromId, getOnlineNodes } = require("../utilities/mysqlHandler"); const utils = require("../utilities/utils"); const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.js"); const { nodeObject } = require("../utilities/recordHelper.js"); -const { joinServerWrapper } = require("../commands/join"); -const { leaveServerWrapper } = require("../commands/leave"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; +/** + * Check in with a singular node, mark it offline if it's offline and + * + * @param {*} node The node Object to check in with + */ +async function checkInWithNode(node) { + const reqOptions = new requestOptions("/client/requestCheckIn", "GET", node.ip, node.port) + sendHttpRequest(reqOptions, "", (responseObj) => { + if (responseObj) { + log.DEBUG("Response from: ", node.name, responseObj); + const onlineNode = new nodeObject({ _online: false, _id: node.id }); + log.DEBUG("Node update object: ", onlineNode); + updateNodeInfo(onlineNode, (sqlResponse) => { + if (!sqlResponse) this.log.ERROR("No response from SQL object"); + + log.DEBUG("Updated node: ", sqlResponse); + return true + }) + } + else { + log.DEBUG("No response from node, assuming it's offline"); + const offlineNode = new nodeObject({ _online: false, _id: node.id }); + log.DEBUG("Offline node update object: ", offlineNode); + updateNodeInfo(offlineNode, (sqlResponse) => { + if (!sqlResponse) this.log.ERROR("No response from SQL object"); + + log.DEBUG("Updated offline node: ", sqlResponse); + return false + }) + } + }) +} +exports.checkInWithNode = checkInWithNode; + +/** + * Check in with all online nodes and mark any nodes that are actually offline +*/ +async function checkInWithOnlineNodes() { + getOnlineNodes((nodes) => { + log.DEBUG("Online Nodes: ", nodes); + for (const node of nodes) { + checkInWithNode(node); + } + return; + }); +} +exports.checkInWithOnlineNodes = checkInWithOnlineNodes; + /** * * @param {*} req Default express req from router @@ -28,7 +74,7 @@ exports.listAllNodes = async (req, res) => { * Add a new node to the storage * @param {*} req Default express req from router * @param {*} res Defualt express res from router - */ + */ exports.newNode = async (req, res) => { if (!req.body.name) return res.status(400).json("No name specified for new node"); @@ -45,7 +91,7 @@ exports.newNode = async (req, res) => { addNewNode(newNode, (newNodeObject) => { // Send back a success if the user has been added and the ID for the client to keep track of - res.status(202).json({"nodeId": newNodeObject.id}); + res.status(202).json({ "nodeId": newNodeObject.id }); }) } catch (err) { @@ -62,7 +108,7 @@ exports.newNode = async (req, res) => { * * @param {*} req Default express req from router * @param {*} res Defualt express res from router - */ + */ exports.getNodeInfo = async (req, res) => { if (!req.query.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.query.id, (nodeInfo) => { @@ -75,7 +121,7 @@ exports.getNodeInfo = async (req, res) => { * @param {*} req Default express req from router * @param {*} res Defualt express res from router */ -exports.nodeCheckIn = async (req, res) => { +exports.nodeCheckIn = async (req, res) => { if (!req.body.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.body.id, (nodeInfo) => { let checkInObject = {}; @@ -87,7 +133,7 @@ exports.nodeCheckIn = async (req, res) => { if (req.body.name && req.body.name != nodeInfo.name) { checkInObject._name = req.body.name; isObjectUpdated = true; - } + } if (req.body.ip && req.body.ip != nodeInfo.ip) { checkInObject._ip = req.body.ip; @@ -115,9 +161,9 @@ exports.nodeCheckIn = async (req, res) => { } // If no changes are made tell the client - if (!isObjectUpdated) return res.status(200).json("No keys updated"); + if (!isObjectUpdated) return res.status(200).json("No keys updated"); - log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); + log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); checkInObject._id = req.body.id; checkInObject = new nodeObject(checkInObject); @@ -125,40 +171,28 @@ exports.nodeCheckIn = async (req, res) => { if (!nodeInfo) { log.WARN("No existing node found with this ID, adding node: ", checkInObject); addNewNode(checkInObject, (newNode) => { - return res.status(201).json({"updatedKeys": newNode}); + return res.status(201).json({ "updatedKeys": newNode }); }); } - else { + else { updateNodeInfo(checkInObject, () => { - return res.status(202).json({"updatedKeys": checkInObject}); + return res.status(202).json({ "updatedKeys": checkInObject }); }); - } + } }); } /** - * Request the node to join the specified server/channel and listen to the specified resource + * Requests a specific node to check in with the server, if it's online * * @param {*} req Default express req from router * @param {*} res Defualt express res from router - * @param req.body.clientId The client ID to join discord with (NOT dev portal ID, right click user -> Copy ID) - * @param req.body.channelId The Channel ID to join in Discord - * @param req.body.presetName The preset name to listen to in Discord */ -exports.requestNodeJoinServer = async (req, res) => { - if (!req.body.clientId || !req.body.channelId || !req.body.presetName) return res.status(400).json("Missing information in request, requires clientId, channelId, presetName"); - await joinServerWrapper(req.body.presetName, req.body.channelId, req.body.clientId); -} - -/** - * - * @param {*} req Default express req from router - * @param {*} res Defualt express res from router - * @param {*} req.body.clientId The client ID to request to leave the server - */ -exports.requestNodeLeaveServer = async (req, res) => { - if (!req.body.ClientId) return res.status(400).json("Missing client ID in request"); - await leaveServerWrapper({clientId: req.body.ClientId}); +exports.requestNodeCheckIn = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No Node ID supplied in request"); + const node = await getNodeInfoFromId(req.params.nodeId); + if (!node) return res.status(400).json("No Node with the ID given"); + checkInWithNode(node); } /** @@ -172,49 +206,21 @@ exports.nodeMonitorService = class nodeMonitorService { /** * Start the node monitor service in the background */ - async start(){ - // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start - await new Promise(resolve => setTimeout(resolve, refreshInterval/10)); + async start() { + // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start + await new Promise(resolve => setTimeout(resolve, refreshInterval / 10)); log.INFO("Starting Node Monitor Service"); // Check in before starting the infinite loop - await this.checkInWithOnlineNodes(); - - - while(true){ + await checkInWithOnlineNodes(); + + + while (true) { // Wait for the refresh interval, then wait for the posts to return, then wait a quarter of the refresh interval to make sure everything is cleared up await new Promise(resolve => setTimeout(resolve, refreshInterval)); - await this.checkInWithOnlineNodes(); + await checkInWithOnlineNodes(); await new Promise(resolve => setTimeout(resolve, refreshInterval / 4)); continue; - } - } - - /** - * Check in with all online nodes and mark any nodes that are actually offline - */ - async checkInWithOnlineNodes(){ - getOnlineNodes((nodes) => { - this.log.DEBUG("Online Nodes: ", nodes); - for (const node of nodes) { - const reqOptions = new requestOptions("/client/requestCheckIn", "GET", node.ip, node.port) - sendHttpRequest(reqOptions, "", (responseObj) => { - if (responseObj) { - this.log.DEBUG("Response from: ", node.name, responseObj); - } - else { - this.log.DEBUG("No response from node, assuming it's offline"); - const offlineNode = new nodeObject({ _online: 0, _id: node.id }); - this.log.DEBUG("Offline node update object: ", offlineNode); - updateNodeInfo(offlineNode, (sqlResponse) => { - if (!sqlResponse) this.log.ERROR("No response from SQL object"); - - this.log.DEBUG("Updated offline node: ", sqlResponse); - }) - } - }) - } - return; - }); + } } } diff --git a/Server/public/res/css/main.css b/Server/public/res/css/main.css index d057e2a..6384148 100644 --- a/Server/public/res/css/main.css +++ b/Server/public/res/css/main.css @@ -139,4 +139,38 @@ a { /* Global Section */ .sidebar-container { min-height: 94.2vh; -} \ No newline at end of file +} + +/* User table section */ + +.label { + border-radius: 3px; + font-size: 1.1em; + font-weight: 600; +} + +.user-list tbody td .user-subhead { + font-size: 1em; + font-style: italic; +} + +.table thead tr th { + text-transform: uppercase; + font-size: 0.875em; +} + +.table thead tr th { + border-bottom: 2px solid #e7ebee; +} + +.table tbody tr td:first-child { + font-size: 1.125em; + font-weight: 300; +} + +.table tbody tr td { + font-size: 0.875em; + vertical-align: middle; + border-top: 1px solid #e7ebee; + padding: 12px 8px; +} diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js new file mode 100644 index 0000000..a1530f4 --- /dev/null +++ b/Server/public/res/js/node.js @@ -0,0 +1,50 @@ +function addFrequencyInput(system){ + // Create new input + var icon = document.createElement('i'); + icon.classList.add('bi'); + icon.classList.add('bi-x-circle'); + icon.classList.add('text-black'); + + var remove = document.createElement('a'); + remove.classList.add('align-middle'); + remove.classList.add('float-left'); + remove.href = '#' + remove.appendChild(icon); + + var childColRemoveIcon = document.createElement('div'); + childColRemoveIcon.classList.add('col-2'); + childColRemoveIcon.appendChild(remove); + + var input = document.createElement('input'); + input.classList.add('form-control'); + input.id = 'nodeFreq'; + input.type = 'text'; + + var childColInput = document.createElement('div'); + childColInput.classList.add('col-10'); + childColInput.appendChild(input); + + var childRow = document.createElement('div'); + childRow.classList.add("row"); + childRow.classList.add("px-1"); + childRow.appendChild(childColInput); + childRow.appendChild(childColRemoveIcon); + + var colParent = document.createElement('div'); + colParent.classList.add("col-md-6"); + colParent.classList.add("mb-1"); + colParent.appendChild(childRow); + + document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); +} + +function checkInByNodeId(nodeId){ + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId; + Http.open("GET", url); + Http.send(); + + Http.onreadystatechange = (e) => { + console.log(Http.responseText) + } +} \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index b90e8db..773bc45 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -28,8 +28,7 @@ router.get('/nodeInfo', nodesController.getNodeInfo); // Client checkin with the server to update information router.post('/nodeCheckIn', nodesController.nodeCheckIn); -// TODO Need to authenticate this request -// Request a particular client to join a particular channel listening to a particular preset -router.post('/joinServer', nodesController.requestNodeJoinServer); +// Request a node to check in with the server +router.get('/:nodeId', nodesController.requestNodeCheckIn); module.exports = router; diff --git a/Server/utilities/recordHelper.js b/Server/utilities/recordHelper.js index 5464374..eb26ae3 100644 --- a/Server/utilities/recordHelper.js +++ b/Server/utilities/recordHelper.js @@ -118,8 +118,7 @@ class nodeObject { this.ip = _ip; this.port = _port; this.location = _location; - this.nearbySystems = _nearbySystems; - if (this.nearbySystems) this.presets = Object.keys(_nearbySystems); + this.nearbySystems = _nearbySystems; this.online = _online; } } diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 941e326..05cd447 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -1,41 +1,154 @@ <%- include('partials/htmlHead.ejs') %> -
-
- Account Details - <% if(node.online){%> Online - <% } else {%> Offline - <% } %> +
+
+
+

+ + Node Details + +

+
+
+
+
+
+ +
+
+ + +
+
+ + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
+ + Join Server + + Leave Server + + Check-in with Node +
+
+ +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + +
+

+ Nearby Systems +

+
+
+
+
+
+
+ + + + + + + + + + + <% for(const system in node.nearbySystems){ %> + + + + + + + <% // Update system modal %> + <%- include("partials/modifySystemModal.ejs", {'system': system, 'frequencies': node.nearbySystems[system].frequencies}) %> + <% } %> + +
System NameFrequenciesProtocol 
+ <%= system %> + + <% if(node.nearbySystems[system].frequencies.length> 1) { %> +
    + <% for(const frequency of + node.nearbySystems[system].frequencies) { %> +
  • + <%=frequency%> MHz +
  • + <% } %> +
+ <% } else { const + frequency=node.nearbySystems[system].frequencies[0] + %> + <%=frequency%> MHz + <% } %> +
+ + <%= node.nearbySystems[system].mode %> + + + "> + + + + + +
+
+
+
+
+
+ + + + +
+
+
-
-
-
- - -
-
-
- - -
-
-
-
- - + + <% // new System Modal %> + <%- include('partials/bodyEnd.ejs') %> + <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/bodyEnd.ejs b/Server/views/partials/bodyEnd.ejs index 2a347cf..992066b 100644 --- a/Server/views/partials/bodyEnd.ejs +++ b/Server/views/partials/bodyEnd.ejs @@ -7,5 +7,6 @@ integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"> - \ No newline at end of file + \ No newline at end of file diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs new file mode 100644 index 0000000..d24e83f --- /dev/null +++ b/Server/views/partials/modifySystemModal.ejs @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs index ccac10e..169185f 100644 --- a/Server/views/partials/navbar.ejs +++ b/Server/views/partials/navbar.ejs @@ -1,12 +1,13 @@
<%- include('partials/bodyEnd.ejs') %> - + <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 2a1bb49..9575b9f 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -30,7 +30,7 @@ Leave Server - Check-in with Node + Check-in with Node
From ef45cf6539a4c8eeaaaa633287c3ebfa6acc7022 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 01:51:25 -0400 Subject: [PATCH 10/57] Only show heartbeat toast once HTTP request is complete --- Server/public/res/js/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 68dedd6..b82d08a 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -86,7 +86,7 @@ function sendNodeHeartbeat(nodeId){ Http.open("GET", url); Http.send(); - Http.onreadystatechange = (e) => { + Http.onloadend = (e) => { console.log(Http.responseText) createToast(Http.responseText); } From c0927601b9660db1ec55017672c2049ede43a239 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 01:54:13 -0400 Subject: [PATCH 11/57] Update toast creator to display proper date string --- Server/public/res/js/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index b82d08a..24b784e 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -44,7 +44,7 @@ function createToast(notificationMessage){ toastTitle.appendChild(document.createTextNode("Server Notification")); const toastTime = document.createElement('small'); - toastTime.appendChild(document.createTextNode(Date.now().toLocaleString())); + toastTime.appendChild(document.createTextNode(new Date(Date.now()).toLocaleString())); const toastClose = document.createElement('button'); toastClose.type = 'button'; From e27dd9d9cb9dc43a836a072e5aeb574dd7e2e3ff Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 16:47:42 -0400 Subject: [PATCH 12/57] Updated NodeCard Design --- Server/views/partials/nodeCard.ejs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Server/views/partials/nodeCard.ejs b/Server/views/partials/nodeCard.ejs index b676560..61082be 100644 --- a/Server/views/partials/nodeCard.ejs +++ b/Server/views/partials/nodeCard.ejs @@ -12,10 +12,6 @@
-
- -
<%= node.name %> @@ -35,21 +31,16 @@ <%= node.ip %>:<%= node.port %> -

+

+

-

+
  • <%= system %>
  • + <% } %> +

    From 5428ac6144decfcbd046f0a1e8f574ec1f1b976e Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 18:56:47 -0400 Subject: [PATCH 13/57] Update API and add webapp saving --- Server/controllers/nodesController.js | 32 +++++++++++++---- Server/public/res/js/node.js | 49 ++++++++++++++++++++++++++- Server/routes/nodes.js | 15 ++++---- Server/views/node.ejs | 41 +++++++++++----------- 4 files changed, 103 insertions(+), 34 deletions(-) diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index fa7b622..3012067 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -110,8 +110,8 @@ exports.newNode = async (req, res) => { * @param {*} res Defualt express res from router */ exports.getNodeInfo = async (req, res) => { - if (!req.query.id) return res.status(400).json("No id specified"); - getNodeInfoFromId(req.query.id, (nodeInfo) => { + if (!req.params.id) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.id, (nodeInfo) => { res.status(200).json(nodeInfo); }) } @@ -121,9 +121,9 @@ exports.getNodeInfo = async (req, res) => { * @param {*} req Default express req from router * @param {*} res Defualt express res from router */ -exports.nodeCheckIn = async (req, res) => { - if (!req.body.id) return res.status(400).json("No id specified"); - getNodeInfoFromId(req.body.id, (nodeInfo) => { +exports.updateExistingNode = async = (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.nodeId, (nodeInfo) => { let checkInObject = {}; // Convert the online status to a boolean to be worked with log.DEBUG("REQ Body: ", req.body); @@ -163,9 +163,9 @@ exports.nodeCheckIn = async (req, res) => { // If no changes are made tell the client if (!isObjectUpdated) return res.status(200).json("No keys updated"); - log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); + log.INFO("Updating the following keys for ID: ", req.params.nodeId, checkInObject); - checkInObject._id = req.body.id; + checkInObject._id = req.params.nodeId; checkInObject = new nodeObject(checkInObject); if (!nodeInfo) { @@ -182,6 +182,24 @@ exports.nodeCheckIn = async (req, res) => { }); } +/** Allows the bots to check in and get any updates from the server + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ +exports.nodeCheckIn = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.nodeId, (nodeInfo) => { + if (!nodeInfo.online) { + nodeInfo.online = true; + updateNodeInfo(nodeInfo, () => { + return res.status(200).json(nodeInfo); + }) + } + else return res.status(200).json(nodeInfo); + }); +} + /** * Requests a specific node to check in with the server, if it's online * diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 24b784e..e439c30 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -78,11 +78,12 @@ function createToast(notificationMessage){ document.getElementById("toastZone").appendChild(wrapperDiv); $('.toast').toast('show'); + return $('.toast'); } function sendNodeHeartbeat(nodeId){ const Http = new XMLHttpRequest(); - const url='/nodes/'+nodeId; + const url='/nodes/nodeCheckIn/'+nodeId; Http.open("GET", url); Http.send(); @@ -139,5 +140,51 @@ function leaveServer(){ console.log(Http.status); console.log(responseObject); createToast(`${responseObject} is leaving`); + setTimeout(() => {}, 45000); } +} + +function saveNodeDetails() { + const nodeId = document.getElementById("nodeId").value; + const nodeName = document.getElementById("inputNodeName").value; + const nodeIp = document.getElementById("inputNodeIp").value; + const nodePort = document.getElementById("inputOrgName").value; + const nodeLocation = document.getElementById("inputNodeLocation").value; + + const reqBody = { + 'id': nodeId, + 'name': nodeName, + 'ip': nodeIp, + 'port': nodePort, + 'location': nodeLocation, + 'online': true + } + + console.log("Request Body: ", reqBody); + + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`Node Updated!`); + } + +} + +function addNewSystem() { + +} + +function updateSystem() { + +} + +function requestNodeUpdate() { + } \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index 773bc45..3a2b7d2 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -5,6 +5,13 @@ const nodesController = require('../controllers/nodesController'); /* GET nodes the server knows */ router.get('/', nodesController.listAllNodes); +// TODO Need to authenticate this request +/* GET the information the server has on a particular node */ +router.get('/:nodeId', nodesController.getNodeInfo); + +// Update an existing node +router.put('/:nodeId', nodesController.updateExistingNode); + // TODO Need to authenticate this request /* POST a new node to the server * @@ -20,15 +27,11 @@ router.get('/', nodesController.listAllNodes); */ router.post('/newNode', nodesController.newNode); -// TODO Need to authenticate this request -/* GET the information the server has on a particular node */ -router.get('/nodeInfo', nodesController.getNodeInfo); - // TODO Need to authenticate this request // Client checkin with the server to update information -router.post('/nodeCheckIn', nodesController.nodeCheckIn); +router.post('/nodeCheckIn/:nodeId', nodesController.nodeCheckIn); // Request a node to check in with the server -router.get('/:nodeId', nodesController.requestNodeCheckIn); +router.get('/nodeCheckIn/:nodeId', nodesController.requestNodeCheckIn); module.exports = router; diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 9575b9f..0bc3a9c 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -9,29 +9,30 @@

    +
    + + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
    +
    + + Join Server + + Leave Server + + Check-in with Node + + Update Node +
    +
    -
    - -
    -
    -
    - - <% if(node.online){%> Online - <% } else {%> Offline - <% } %> -
    - - Join Server - - Leave Server - - Check-in with Node -
    +
    @@ -122,11 +123,11 @@
    - + - + data-bs-target="#newSystemModal">Add New System +
    From 318ee7bf91d08e23075539c82ac33d9ba7d8d633 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 22:30:18 -0400 Subject: [PATCH 14/57] Implement functioning method to update systems on web app --- Client/controllers/clientController.js | 15 +++++-- Server/controllers/nodesController.js | 41 +++++++++++++++++ Server/public/res/js/node.js | 49 ++++++++++++++++++--- Server/routes/nodes.js | 5 ++- Server/views/partials/modifySystemModal.ejs | 22 +++++---- 5 files changed, 113 insertions(+), 19 deletions(-) diff --git a/Client/controllers/clientController.js b/Client/controllers/clientController.js index 8d02999..2101d44 100644 --- a/Client/controllers/clientController.js +++ b/Client/controllers/clientController.js @@ -94,10 +94,12 @@ exports.checkConfig = async function checkConfig() { } /** Check in with the server - * If the bot has a saved ID, check in with the server to update any information or just check back in + * If the bot has a saved ID, check in with the server to get any updated information or just check back in * If the bot does not have a saved ID, it will attempt to request a new ID from the server + * + * @param {boolean} init If set to true, the client will update the server to it's config, instead of taking the server's config */ -exports.checkIn = async () => { +exports.checkIn = async (init = false) => { let reqOptions; await this.checkConfig(); runningClientConfig.online = true; @@ -134,7 +136,8 @@ exports.checkIn = async () => { } else { // ID is in the config, checking in with the server - reqOptions = new requestOptions("/nodes/nodeCheckIn", "POST"); + if (init) reqOptions = new requestOptions(`/nodes/${runningClientConfig.id}`, "PUT"); + else reqOptions = new requestOptions(`/nodes/nodeCheckIn/${runningClientConfig.id}`, "POST"); sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), (responseObject) => { log.DEBUG("Check In Respose: ", responseObject); // Check if the server responded @@ -178,7 +181,8 @@ exports.requestCheckIn = async (req, res) => { * This is the endpoint wrapper to get the presets object */ exports.getPresets = async (req, res) => { - return res.status(200).json(getPresets()); + runningClientConfig.nearbySystems = getPresets(); + return res.status(200).json(runningClientConfig.nearbySystems); } /** Controller for the /client/updatePreset endpoint @@ -187,6 +191,7 @@ exports.getPresets = async (req, res) => { exports.updatePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { updatePreset(req.body.systemName, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile}); }) @@ -198,6 +203,7 @@ exports.updatePreset = async (req, res) => { exports.addNewPreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { addNewPreset(req.body.systemName, req.body.frequencies, req.body.mode, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, req.body.trunkFile); }); @@ -210,6 +216,7 @@ exports.removePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { if (!req.body.systemName) return res.status("500").json({"message": "You must specify a system name to delete, this must match exactly to how the system name is saved."}) removePreset(req.body.systemName, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, req.body.trunkFile); }); diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 3012067..63588a6 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -8,6 +8,7 @@ const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.j const { nodeObject } = require("../utilities/recordHelper.js"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; +const digitalModes = ['p25']; /** * Check in with a singular node, mark it offline if it's offline and @@ -116,6 +117,44 @@ exports.getNodeInfo = async (req, res) => { }) } +/** Updates a specific system/preset on a given node + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + * @param {*} req.params.nodeId The Node ID to update the preset/system on + * @param {*} req.body.systemName The name of the system to update + * @param {*} req.body.mode The radio mode of the preset to + * @param {*} req.body.frequencies The frequencies of the preset + * @param {*} req.body.trunkFile The trunk file to use for digital stations + */ +exports.updateNodeSystem = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + if (!req.body.systemName) return res.status(400).json("No system specified"); + log.DEBUG("Updating system for node: ", req.params.nodeId, req.body); + getNodeInfoFromId(req.params.nodeId, (node) => { + const reqOptions = new requestOptions("/client/updatePreset", "POST", node.ip, node.port); + const reqBody = { + 'systemName': req.body.systemName, + 'mode': req.body.mode, + 'frequencies': req.body.frequencies, + } + if(digitalModes.includes(req.body.mode)) reqBody['trunkFile'] = req.body.trunkFile ?? 'none' + + log.DEBUG("Request body for updating node: ", reqBody, reqOptions); + sendHttpRequest(reqOptions, JSON.stringify(reqBody), async (responseObj) => { + if(responseObj){ + // Good + log.DEBUG("Response from updating node: ", reqBody, responseObj); + return res.sendStatus(200) + } else { + // Bad + log.DEBUG("No Response from updating Node"); + return res.status(400).json("No Response from updating Node, could be offline"); + } + }) + }) +} + /** Updates the information received from the client based on ID * * @param {*} req Default express req from router @@ -171,11 +210,13 @@ exports.updateExistingNode = async = (req, res) => { if (!nodeInfo) { log.WARN("No existing node found with this ID, adding node: ", checkInObject); addNewNode(checkInObject, (newNode) => { + this.requestNodeCheckIn(checkInObject.id); return res.status(201).json({ "updatedKeys": newNode }); }); } else { updateNodeInfo(checkInObject, () => { + this.requestNodeCheckIn(checkInObject.id); return res.status(202).json({ "updatedKeys": checkInObject }); }); } diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index e439c30..e4e5274 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -1,4 +1,11 @@ -function addFrequencyInput(system){ +/** + * Remove a frequency input from the DOM + * + * @param {string} system The system name to add the frequency to + * @param {string} inputId [OPTIONAL] The ID of input, this can be anything unique to this input. If this is not provided the number of frequencies will be used as the ID + */ +function addFrequencyInput(system, inputId = null){ + if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length; // Create new input var icon = document.createElement('i'); icon.classList.add('bi'); @@ -9,6 +16,7 @@ function addFrequencyInput(system){ remove.classList.add('align-middle'); remove.classList.add('float-left'); remove.href = '#' + remove.onclick = () => {removeFrequencyInput(`${system}_systemFreqRow_${inputId}`)} remove.appendChild(icon); var childColRemoveIcon = document.createElement('div'); @@ -17,7 +25,7 @@ function addFrequencyInput(system){ var input = document.createElement('input'); input.classList.add('form-control'); - input.id = 'nodeFreq'; + input.id = `${system}_systemFreq_${inputId}`; input.type = 'text'; var childColInput = document.createElement('div'); @@ -33,6 +41,7 @@ function addFrequencyInput(system){ var colParent = document.createElement('div'); colParent.classList.add("col-md-6"); colParent.classList.add("mb-1"); + colParent.id = `${system}_systemFreqRow_${inputId}` colParent.appendChild(childRow); document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); @@ -169,22 +178,52 @@ function saveNodeDetails() { Http.send(JSON.stringify(reqBody)); Http.onloadend = (e) => { - const responseObject = JSON.parse(Http.responseText) + const responseObject = JSON.parse(Http.responseText); console.log(Http.status); console.log(responseObject); createToast(`Node Updated!`); } - } function addNewSystem() { } -function updateSystem() { +function updateSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + const systemMode = document.getElementById(`${systemName}_systemMode`).value; + const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs){ + systemFreqs.push(inputFreq.value); + } + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId+"/systems"; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Updated!`); + } } function requestNodeUpdate() { +} + +function removeFrequencyInput(elementId) { + const element = document.getElementById(elementId); + element.remove(); } \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index 3a2b7d2..d110343 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -12,6 +12,9 @@ router.get('/:nodeId', nodesController.getNodeInfo); // Update an existing node router.put('/:nodeId', nodesController.updateExistingNode); +// Update an existing node's system +router.put('/:nodeId/systems', nodesController.updateNodeSystem); + // TODO Need to authenticate this request /* POST a new node to the server * @@ -28,7 +31,7 @@ router.put('/:nodeId', nodesController.updateExistingNode); router.post('/newNode', nodesController.newNode); // TODO Need to authenticate this request -// Client checkin with the server to update information +// Client checkin with the server to update client information router.post('/nodeCheckIn/:nodeId', nodesController.nodeCheckIn); // Request a node to check in with the server diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs index d24e83f..88ae381 100644 --- a/Server/views/partials/modifySystemModal.ejs +++ b/Server/views/partials/modifySystemModal.ejs @@ -1,5 +1,5 @@ diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs index 204f863..b9816fc 100644 --- a/Server/views/partials/navbar.ejs +++ b/Server/views/partials/navbar.ejs @@ -28,10 +28,10 @@
  • Something else here
  • - */%> + diff --git a/Server/views/partials/sidebar.ejs b/Server/views/partials/sidebar.ejs index fd51b50..e323e7d 100644 --- a/Server/views/partials/sidebar.ejs +++ b/Server/views/partials/sidebar.ejs @@ -1,4 +1,12 @@
    +
    + + + + +
    +
    +
    +
    -
    \ No newline at end of file +
    + \ No newline at end of file From 598c802b28e8673dcf9f7e0739f30ef56224064b Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:29:47 -0400 Subject: [PATCH 21/57] Remove specific presets and left an example file if needed for clients --- Client/config/radioPresets.json | 1 - Client/config/radioPresets.json.EXAMPLE | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) delete mode 100644 Client/config/radioPresets.json create mode 100644 Client/config/radioPresets.json.EXAMPLE diff --git a/Client/config/radioPresets.json b/Client/config/radioPresets.json deleted file mode 100644 index b8f268a..0000000 --- a/Client/config/radioPresets.json +++ /dev/null @@ -1 +0,0 @@ -{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"},"coppies":{"frequencies":[154690000],"mode":"nbfm","trunkFile":"none"},"asdadadadasd":{"frequencies":[123321000],"mode":"nbfm","trunkFile":"none"}} \ No newline at end of file diff --git a/Client/config/radioPresets.json.EXAMPLE b/Client/config/radioPresets.json.EXAMPLE new file mode 100644 index 0000000..d33fe51 --- /dev/null +++ b/Client/config/radioPresets.json.EXAMPLE @@ -0,0 +1,18 @@ +{ + "Default P25 System Name": { + "frequencies": [ + 155344000, + 155444000, + 155555000 + ], + "mode": "p25", + "trunkFile": "trunk.tsv" + }, + "Default NBFM System": { + "frequencies": [ + 154690000 + ], + "mode": "nbfm", + "trunkFile": "none" + } +} \ No newline at end of file From e0bae665ed57273cdb04738fdaa2c4c7d3db840d Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:45:32 -0400 Subject: [PATCH 22/57] Fixed bug when editing node systems - Name defaulted to new system name --- Server/views/partials/modifySystemModal.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs index db00411..664a755 100644 --- a/Server/views/partials/modifySystemModal.ejs +++ b/Server/views/partials/modifySystemModal.ejs @@ -12,7 +12,7 @@
    - <%= system %><%} else {%>Local Radio System<%}%>"> + <%= system %><%} else {%>Local Radio System<%}%>">
    "> From 5dd27f0bed80204264861f405422d8f0e7182e79 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:46:19 -0400 Subject: [PATCH 23/57] Ignore local radio presets --- Client/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 Client/.gitignore diff --git a/Client/.gitignore b/Client/.gitignore new file mode 100644 index 0000000..c74a7af --- /dev/null +++ b/Client/.gitignore @@ -0,0 +1 @@ +*radioPresets.json \ No newline at end of file From 62c05040286ce99b5516cd7e13dd674efccd9b09 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:47:09 -0400 Subject: [PATCH 24/57] Check if the presets exist when going to get them - Return empty object if no preset file is found --- Client/utilities/updatePresets.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Client/utilities/updatePresets.js b/Client/utilities/updatePresets.js index 1e7bac6..21d6abd 100644 --- a/Client/utilities/updatePresets.js +++ b/Client/utilities/updatePresets.js @@ -17,7 +17,7 @@ function writePresets(presets, callback = undefined) { // Error checking if (err) throw err; log.DEBUG("Write Complete"); - if (callback) callback() + if (callback) callback(); else return }); } @@ -71,8 +71,9 @@ function convertFrequencyToHertz(frequency){ */ exports.getPresets = function getPresets() { const presetDir = path.resolve("./config/radioPresets.json"); - log.DEBUG(`Getting presets from directory: '${presetDir}'`); - return JSON.parse(fs.readFileSync(presetDir)); + log.DEBUG(`Getting presets from directory: '${presetDir}'`); + if (fs.existsSync(presetDir)) return JSON.parse(fs.readFileSync(presetDir)); + else return {}; } /** From f5d58d45dae195a24abc8fa917f4838469c70b7b Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 03:27:39 -0400 Subject: [PATCH 25/57] Semi functional client webUI - can update client info for sure - Working on notifications now --- Client/controllers/clientController.js | 60 ++- Client/public/res/css/main.css | 183 +++++++++ Client/public/res/js/node.js | 400 ++++++++++++++++++++ Client/public/stylesheets/style.css | 8 - Client/routes/bot.js | 2 + Client/routes/client.js | 17 +- Client/routes/index.js | 6 +- Client/utilities/updateConfig.js | 12 +- Client/views/index.ejs | 176 ++++++++- Client/views/partials/joinModal.ejs | 44 +++ Client/views/partials/modifySystemModal.ejs | 61 +++ Client/views/partials/navbar.ejs | 42 ++ 12 files changed, 964 insertions(+), 47 deletions(-) create mode 100644 Client/public/res/css/main.css create mode 100644 Client/public/res/js/node.js delete mode 100644 Client/public/stylesheets/style.css create mode 100644 Client/views/partials/joinModal.ejs create mode 100644 Client/views/partials/modifySystemModal.ejs create mode 100644 Client/views/partials/navbar.ejs diff --git a/Client/controllers/clientController.js b/Client/controllers/clientController.js index 7d1729e..c8812b4 100644 --- a/Client/controllers/clientController.js +++ b/Client/controllers/clientController.js @@ -5,7 +5,7 @@ const log = new DebugBuilder("client", "clientController"); require('dotenv').config(); const modes = require("../config/modes"); // Modules -const { executeAsyncConsoleCommand, BufferToJson } = require("../utilities/utilities"); +const { executeAsyncConsoleCommand, BufferToJson, nodeObject } = require("../utilities/utilities"); // Utilities const { getFullConfig } = require("../utilities/configHandler"); const { updateId, updateConfig, updateClientConfig } = require("../utilities/updateConfig"); @@ -22,11 +22,11 @@ var runningClientConfig = getFullConfig() * @returns {*} */ function checkBodyForPresetFields(req, res, callback) { - if (!req.body?.systemName) return res.status(403).json({"message": "No system in the request"}); - if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({"message": "No frequencies in the request or type is not an array"}); - if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({"message": "No mode in the request"}); + if (!req.body?.systemName) return res.status(403).json({ "message": "No system in the request" }); + if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({ "message": "No frequencies in the request or type is not an array" }); + if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({ "message": "No mode in the request" }); if (!req.body?.trunkFile) { - if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({"message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request"}) + if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({ "message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request" }) // If there is a value keep it but if not, add nothing so the system can update that key (if needed) req.body.trunkFile = req.body.trunkFile ?? "none"; } @@ -34,7 +34,7 @@ function checkBodyForPresetFields(req, res, callback) { return callback(); } -async function checkLocalIP() { +async function checkLocalIP() { if (process.platform === "win32") { // Windows var networkConfig = await executeAsyncConsoleCommand("ipconfig"); @@ -72,21 +72,21 @@ exports.checkConfig = async function checkConfig() { await updateId(0); runningClientConfig.id = 0; } - + if (!runningClientConfig.ip) { const ipAddr = await checkLocalIP(); await updateConfig('CLIENT_IP', ipAddr); runningClientConfig.ip = ipAddr; } - if(!runningClientConfig.name) { + if (!runningClientConfig.name) { const lastOctet = await String(checkLocalIP()).spit('.')[-1]; const name = `Radio-Node-${lastOctet}`; await updateConfig('CLIENT_NAME', name); runningClientConfig.name = name; } - if(!runningClientConfig.port) { + if (!runningClientConfig.port) { const port = 3010; await updateConfig('CLIENT_PORT', port); runningClientConfig.port = port; @@ -104,11 +104,11 @@ exports.checkIn = async (update = false) => { let reqOptions; await this.checkConfig(); // Check if there is an ID found, if not add the node to the server. If there was an ID, check in with the server to make sure it has the correct information - try { - if (!runningClientConfig?.id || runningClientConfig.id == 0) { + try { + if (!runningClientConfig?.id || runningClientConfig.id == 0) { // ID was not found in the config, creating a new node - reqOptions = new requestOptions("/nodes/newNode", "POST"); - sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), async (responseObject) => { + reqOptions = new requestOptions("/nodes/newNode", "POST"); + sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), async (responseObject) => { // Check if the server responded if (!responseObject) { log.WARN("Server did not respond to checkIn. Will wait 60 seconds then try again"); @@ -120,7 +120,7 @@ exports.checkIn = async (update = false) => { } // Update the client's ID if the server accepted its - if (responseObject.statusCode === 202) { + if (responseObject.statusCode === 202) { runningClientConfig.id = responseObject.body.nodeId; log.DEBUG("Response object from new node: ", responseObject, runningClientConfig); await updateId(runningClientConfig.id); @@ -131,7 +131,7 @@ exports.checkIn = async (update = false) => { log.DEBUG("HTTP Error: ", responseObject, await BufferToJson(responseObject.body)); await onHttpError(responseObject.statusCode); } - + }); } else { @@ -149,15 +149,17 @@ exports.checkIn = async (update = false) => { }, 60000); return } - + if (responseObject.statusCode === 202) { log.DEBUG("Updated keys: ", responseObject.body.updatedKeys) // Server accepted an update } if (responseObject.statusCode === 200) { // Server accepted the response but there were no keys to be updated - const tempUpdatedConfig = updateClientConfig(responseObject.body); - if (!tempUpdatedConfig.length > 0) return; + if (!update){ + const tempUpdatedConfig = updateClientConfig(responseObject.body); + if (!tempUpdatedConfig.length > 0) return; + } } if (responseObject.statusCode >= 300) { // Server threw an error @@ -179,6 +181,24 @@ exports.requestCheckIn = async (req, res) => { return res.sendStatus(200); } +/** + * Express JS Wrapper for checking and updating client config + * @param {*} req + * @param {*} res + * @returns + */ +exports.updateClientConfigWrapper = async (req, res) => { + // Convert the online status to a boolean to be worked with + log.DEBUG("REQ Body: ", req.body); + const updatedKeys = await updateClientConfig(req.body); + if (updatedKeys) { + log.DEBUG("Keys have been updated, updating running config and checking in with the server: ", updatedKeys); + runningClientConfig = await getFullConfig(); + await this.checkIn(true); + } + res.status(200).json(updatedKeys); +} + /** Controller for the /client/presets endpoint * This is the endpoint wrapper to get the presets object */ @@ -196,7 +216,7 @@ exports.updatePreset = async (req, res) => { runningClientConfig.nearbySystems = getPresets(); this.checkIn(true); return res.sendStatus(200); - }, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile}); + }, { frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile }); }) } @@ -218,7 +238,7 @@ exports.addNewPreset = async (req, res) => { */ exports.removePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { - if (!req.body.systemName) return res.status("500").json({"message": "You must specify a system name to delete, this must match exactly to how the system name is saved."}) + if (!req.body.systemName) return res.status("500").json({ "message": "You must specify a system name to delete, this must match exactly to how the system name is saved." }) removePreset(req.body.systemName, () => { runningClientConfig.nearbySystems = getPresets(); this.checkIn(true); diff --git a/Client/public/res/css/main.css b/Client/public/res/css/main.css new file mode 100644 index 0000000..5c44612 --- /dev/null +++ b/Client/public/res/css/main.css @@ -0,0 +1,183 @@ +.node-card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #eff0f2; + border-radius: 1rem; + margin-bottom: 24px; + box-shadow: 0 2px 3px #e4e8f0; +} + +.avatar-md { + height: 4rem; + width: 4rem; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #f1f3f7; + border: 1px solid #eff0f2; + border-radius: 0.75rem; +} + +.avatar-title { + align-items: center; + background-color: #3b76e1; + color: #fff; + display: flex; + font-weight: 500; + height: 100%; + justify-content: center; + width: 100%; +} + +.bg-soft-primary { + background-color: rgba(59, 118, 225, .25) !important; +} + +a { + text-decoration: none !important; +} + +.badge-soft-danger { + color: #f56e6e !important; + background-color: rgba(245, 110, 110, .1); +} + +.badge-soft-success { + color: #63ad6f !important; + background-color: rgba(99, 173, 111, .1); +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.badge { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.75rem; +} + +/* Info Card Section */ +.info-card { + background-color: #fff; + border-radius: 10px; + border: none; + position: relative; + margin-bottom: 30px; + box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1); +} + +.info-card .card-statistic .card-icon-large .bi { + font-size: 110px; +} + +.info-card .card-statistic .card-icon { + text-align: center; + line-height: 50px; + margin-left: 15px; + color: #000; + position: absolute; + right: -5px; + top: 20px; + opacity: 0.1; +} + +/* Info Card Background Colors */ + +.l-bg-cherry { + background: linear-gradient(to right, #493240, #f09) !important; + color: #fff; +} + +.l-bg-blue-dark { + background: linear-gradient(to right, #373b44, #4286f4) !important; + color: #fff; +} + +.l-bg-green-dark { + background: linear-gradient(to right, #0a504a, #38ef7d) !important; + color: #fff; +} + +.l-bg-orange-dark { + background: linear-gradient(to right, #a86008, #ffba56) !important; + color: #fff; +} + +.l-bg-cyan { + background: linear-gradient(135deg, #289cf5, #84c0ec) !important; + color: #fff; +} + +.l-bg-green { + background: linear-gradient(135deg, #23bdb8 0%, #43e794 100%) !important; + color: #fff; +} + +.l-bg-orange { + background: linear-gradient(to right, #f9900e, #ffba56) !important; + color: #fff; +} + +/* Global Section */ +.sidebar-container { + min-height: 95vh; +} + +.sidebar { + position: fixed; + top: 5vh; + bottom: 0; + left: 0; + } + +/* User table section */ + +.label { + border-radius: 3px; + font-size: 1.1em; + font-weight: 600; +} + +.user-list tbody td .user-subhead { + font-size: 1em; + font-style: italic; +} + +.table thead tr th { + text-transform: uppercase; + font-size: 0.875em; +} + +.table thead tr th { + border-bottom: 2px solid #e7ebee; +} + +.table tbody tr td:first-child { + font-size: 1.125em; + font-weight: 300; +} + +.table tbody tr td { + font-size: 0.875em; + vertical-align: middle; + border-top: 1px solid #e7ebee; + padding: 12px 8px; +} diff --git a/Client/public/res/js/node.js b/Client/public/res/js/node.js new file mode 100644 index 0000000..dd2cf94 --- /dev/null +++ b/Client/public/res/js/node.js @@ -0,0 +1,400 @@ +$(document).ready(async () => { + console.log("Loading stored notifications..."); + await loadStoredToasts(); + console.log("Showing stored notifications..."); + await showStoredToasts(); +}); + +/** + * Gets all toasts stored in local storage + * + * @returns {Object} Object of toasts in storage + */ +function getStoredToasts() { + if (localStorage.getItem("toasts")) { + const storedToasts = JSON.parse(localStorage.getItem("toasts")); + console.log("LOADED STORED TOASTS: ", storedToasts); + navbarUpdateNotificationBellCount(storedToasts); + return storedToasts; + } + else return false +} + +/** + * Adds a toast to storage, will not allow duplicates + * + * @param {Date} time The date object from when the toast was created + * @param {*} message The message of the toast + */ +function addToastToStorage(time, message) { + var toasts = [{ 'time': time, 'message': message }] + var storedToasts = getStoredToasts(); + console.log("Adding new notification to storage: ", toasts); + toasts = toasts.concat(storedToasts); + console.log("Combined new and stored notifications: ", toasts); + toasts = toasts.filter((value, index, self) => + index === self.findIndex((t) => ( + t.time === value.time && t.message === value.message + )) + ) + console.log("Deduped stored notifications: ", toasts); + localStorage.setItem("toasts", JSON.stringify(toasts)); + navbarUpdateNotificationBellCount(toasts); +} + +/** + * Removes a toast from the local storage + * + * @param {Date} time The date object from when the toast was created + * @param {*} message The message of the toast + */ +function removeToastFromStorage(time, message) { + const toastToRemove = { 'time': time, 'message': message } + console.log("Toast to remove: ", toastToRemove); + var toasts = getStoredToasts(); + console.log("Stored toasts: ", toasts); + if (toasts.indexOf(toastToRemove)) toasts.splice(toasts.indexOf(toastToRemove) - 1, 1) + console.log("Toasts with selected toast removed: ", toasts); + localStorage.setItem("toasts", JSON.stringify(toasts)); + navbarUpdateNotificationBellCount(toasts); +} + +/** + * Shows all stored toasts + */ +function showStoredToasts() { + const storedToasts = getStoredToasts(); + if (!storedToasts) return + console.log("Loaded stored notifications to show: ", storedToasts); + for (const toast of storedToasts) { + const toastId = `${toast.time}-toast`; + console.log("Showing stored toast: ", toast, toastId); + const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(toastId)); + toastElement.show(); + } +} + +/** + * Loads all toasts stored in the local storage into the DOM of the webpage + */ +function loadStoredToasts() { + const storedToasts = getStoredToasts(); + if (!storedToasts) return + console.log("Loaded stored notifications: ", storedToasts); + for (const toast of storedToasts) { + createToast(toast.message, { time: toast.time }) + } +} + +/** + * Will update the count of notifications on the bell icon in the navbar + * + * @param {Array} storedToasts An array of stored toasts to be counted and updated in the navbar + */ +function navbarUpdateNotificationBellCount(storedToasts) { + const notificationBellIcon = document.getElementById("navbar-notification-bell"); + var notificationBellCount = document.getElementById("notification-bell-icon-count"); + if (!notificationBellCount) { + notificationBellCount = document.createElement('span'); + notificationBellCount.id = "notification-bell-icon-count"; + notificationBellCount.classList.add('badge'); + notificationBellCount.classList.add('text-bg-secondary'); + notificationBellCount.appendChild(document.createTextNode(storedToasts.length)); + } + else notificationBellCount.innerHTML = storedToasts.length; + + notificationBellIcon.appendChild(notificationBellCount); +} + +/** + * Remove a frequency input from the DOM + * + * @param {string} system The system name to add the frequency to + * @param {string} inputId [OPTIONAL] The ID of input, this can be anything unique to this input. If this is not provided the number of frequencies will be used as the ID + */ +function addFrequencyInput(system, inputId = null) { + if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length; + // Create new input + var icon = document.createElement('i'); + icon.classList.add('bi'); + icon.classList.add('bi-x-circle'); + icon.classList.add('text-black'); + + var remove = document.createElement('a'); + remove.classList.add('align-middle'); + remove.classList.add('float-left'); + remove.href = '#' + remove.onclick = () => { removeFrequencyInput(`${system}_systemFreqRow_${inputId}`) } + remove.appendChild(icon); + + var childColRemoveIcon = document.createElement('div'); + childColRemoveIcon.classList.add('col-2'); + childColRemoveIcon.appendChild(remove); + + var input = document.createElement('input'); + input.classList.add('form-control'); + input.id = `${system}_systemFreq_${inputId}`; + input.type = 'text'; + + var childColInput = document.createElement('div'); + childColInput.classList.add('col-10'); + childColInput.appendChild(input); + + var childRow = document.createElement('div'); + childRow.classList.add("row"); + childRow.classList.add("px-1"); + childRow.appendChild(childColInput); + childRow.appendChild(childColRemoveIcon); + + var colParent = document.createElement('div'); + colParent.classList.add("col-md-6"); + colParent.classList.add("mb-1"); + colParent.id = `${system}_systemFreqRow_${inputId}` + colParent.appendChild(childRow); + + document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); +} + +/** + * Add a toast element to the DOM + * + * @param {*} notificationMessage The message of the notification + * @param {Date} param1.time The date object for when the toast was created, blank if creating new + * @param {boolean} param1.showNow Show the toast now or just store it + * @returns + */ +function createToast(notificationMessage, { time = undefined, showNow = false } = {}) { + if (!time) time = new Date(Date.now()); + else time = new Date(Date.parse(time)); + const toastTitle = document.createElement('strong'); + toastTitle.classList.add('me-auto'); + toastTitle.appendChild(document.createTextNode("Server Notification")); + + const toastTime = document.createElement('small'); + toastTime.appendChild(document.createTextNode(time.toLocaleString())); + + const toastClose = document.createElement('button'); + toastClose.type = 'button'; + toastClose.classList.add('btn-close'); + toastClose.ariaLabel = 'Close'; + toastClose.setAttribute('data-bs-dismiss', 'toast'); + toastClose.onclick = () => { removeToastFromStorage(time.toISOString(), notificationMessage); }; + + const toastHeader = document.createElement('div'); + toastHeader.classList.add('toast-header'); + toastHeader.appendChild(toastTitle); + toastHeader.appendChild(toastTime); + toastHeader.appendChild(toastClose); + + const toastMessage = document.createElement('p'); + toastMessage.classList.add("px-2"); + toastMessage.appendChild(document.createTextNode(notificationMessage)); + + const toastBody = document.createElement('div'); + toastBody.classList.add('toast-body'); + toastBody.appendChild(toastMessage); + + const wrapperDiv = document.createElement('div'); + wrapperDiv.classList.add('toast'); + //wrapperDiv.classList.add('position-fixed'); + wrapperDiv.id = `${time.toISOString()}-toast`; + wrapperDiv.role = 'alert'; + wrapperDiv.ariaLive = 'assertive'; + wrapperDiv.ariaAtomic = true; + wrapperDiv.setAttribute('data-bs-delay', "7500"); + wrapperDiv.setAttribute('data-bs-animation', true); + wrapperDiv.appendChild(toastHeader); + wrapperDiv.appendChild(toastMessage); + + document.getElementById("toastZone").appendChild(wrapperDiv); + addToastToStorage(time.toISOString(), notificationMessage); + if (showNow) { + const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(`${time.toISOString()}-toast`)); + toastElement.show(); + } + + return; +} + +function sendNodeHeartbeat(nodeId) { + const Http = new XMLHttpRequest(); + const url = '/client/requestCheckIn' + nodeId; + Http.open("GET", url); + Http.send(); + + Http.onloadend = (e) => { + console.log(Http.responseText) + createToast(Http.responseText, { showNow: true }); + } +} + +function joinServer() { + const preset = document.getElementById("selectRadioPreset").value; + const clientId = document.getElementById("inputDiscordClientId").value; + const channelId = document.getElementById("inputDiscordChannelId").value; + + const reqBody = { + 'preset': preset, + 'clientId': clientId, + 'channelId': channelId + }; + + console.log(reqBody); + + const Http = new XMLHttpRequest(); + const url = '/bot/join'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`${responseObject.name} will join shortly`, { showNow: true }); + $("#joinModal").modal('toggle'); + } +} + +function leaveServer() { + const reqBody = {}; + + const Http = new XMLHttpRequest(); + const url = '/bot/leave'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${responseObject} is leaving`, { showNow: true }); + } +} + +function saveNodeDetails() { + const nodeName = document.getElementById("inputNodeName").value; + const nodeIp = document.getElementById("inputNodeIp").value; + const nodePort = document.getElementById("inputOrgName").value; + const nodeLocation = document.getElementById("inputNodeLocation").value; + + const reqBody = { + 'name': nodeName, + 'ip': nodeIp, + 'port': nodePort, + 'location': nodeLocation + } + + console.log("Request Body: ", reqBody); + + const Http = new XMLHttpRequest(); + const url = '/client'; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText); + console.log(Http.status); + console.log(responseObject); + createToast(`Node Updated!`); + location.reload(); + } +} + +function addNewSystem() { + const nodeId = document.getElementById("nodeId").value; + const systemName = document.getElementById(`New System_systemName`).value; + const systemMode = document.getElementById(`New System_systemMode`).value; + const inputSystemFreqs = $(`[id^="New System_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs) { + systemFreqs.push(inputFreq.value); + } + + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + if (reqBody.mode == "p25") reqBody.trunkFile = 'none'; + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = "/client/addPreset"; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Added!`); + location.reload(); + } +} + +function updateSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + const systemMode = document.getElementById(`${systemName}_systemMode`).value; + const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs) { + systemFreqs.push(inputFreq.value); + } + + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + if (reqBody.mode == "p25") reqBody.trunkFile = 'none'; + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = "/client/updatePreset"; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Updated!`); + location.reload(); + } +} + +function removeSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + + const reqBody = { + 'systemName': systemName, + } + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = '/client/removePreset'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Removed!`); + location.reload(); + } +} + +function requestNodeUpdate() { + +} + +function removeFrequencyInput(elementId) { + const element = document.getElementById(elementId); + element.remove(); +} \ No newline at end of file diff --git a/Client/public/stylesheets/style.css b/Client/public/stylesheets/style.css deleted file mode 100644 index 9453385..0000000 --- a/Client/public/stylesheets/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; -} diff --git a/Client/routes/bot.js b/Client/routes/bot.js index 805f8a1..3af6afa 100644 --- a/Client/routes/bot.js +++ b/Client/routes/bot.js @@ -15,7 +15,9 @@ router.get('/status', botController.getStatus); * * @param req The request sent from the master * @param req.body.channelId The channel ID to join +* @param req.body.clientId The discord Client ID to use when connecting to the server * @param req.body.presetName The name of the preset to start listening to +* @param req.body.NGThreshold [OPTIONAL] The noisegate threshold, this will default to 50 */ router.post('/join', botController.joinServer); diff --git a/Client/routes/client.js b/Client/routes/client.js index c3a7ed0..36d6bd5 100644 --- a/Client/routes/client.js +++ b/Client/routes/client.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); // Controllers -const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, updateClient } = require("../controllers/clientController"); +const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, updateClient, updateClientConfigWrapper } = require("../controllers/clientController"); /** GET Request a check in from the client * Queue the client to check in with the server @@ -11,16 +11,21 @@ const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, up */ router.get('/requestCheckIn', requestCheckIn); -/** GET Object of all known presets - * Query the client to get all the known presets - */ -router.put('/', ); - /** GET Object of all known presets * Query the client to get all the known presets */ router.get('/presets', getPresets); +/** + * PUT An update to the running client config (not radio config) + * @param {number} req.body.id The ID given to the node to update + * @param {string} req.body.name The name of the node + * @param {string} req.body.ip The IP the server can contact the node on + * @param {number} req.body.port The port the server can contact the node on + * @param {string} req.body.location The physical location of the node + */ +router.put('/', updateClientConfigWrapper); + /** POST Update to preset * * @param req The request sent from the master diff --git a/Client/routes/index.js b/Client/routes/index.js index ecca96a..bf776f0 100644 --- a/Client/routes/index.js +++ b/Client/routes/index.js @@ -1,9 +1,11 @@ var express = require('express'); var router = express.Router(); +const { getFullConfig } = require('../utilities/configHandler'); /* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); +router.get('/', async function(req, res, next) { + const clientConfig = await getFullConfig(); + res.render('index', { 'node': clientConfig }); }); module.exports = router; diff --git a/Client/utilities/updateConfig.js b/Client/utilities/updateConfig.js index 250d90c..9e90ba8 100644 --- a/Client/utilities/updateConfig.js +++ b/Client/utilities/updateConfig.js @@ -27,7 +27,12 @@ exports.updateId = (updatedId) => { /** * Wrapper to update any or all keys in the client config * - * @param {*} configObject Object with what keys you wish to update (node object format, will be converted) + * @param {Object} configObject Object with what keys you wish to update (node object format, will be converted) + * @param {number} configObject.id The ID given to the node to update + * @param {string} configObject.name The name of the node + * @param {string} configObject.ip The IP the server can contact the node on + * @param {number} configObject.port The port the server can contact the node on + * @param {string} configObject.location The physical location of the node * @returns */ exports.updateClientConfig = (configObject) => { @@ -39,6 +44,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.id != configObject.id) { this.updateConfig('CLIENT_ID', configObject.id); updatedKeys.push({'CLIENT_ID': configObject.id}); + process.env.CLIENT_ID = configObject.id; log.DEBUG("Updated ID to: ", configObject.id); } } @@ -46,6 +52,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.name != configObject.name) { this.updateConfig('CLIENT_NAME', configObject.name); updatedKeys.push({'CLIENT_NAME': configObject.name}); + process.env.CLIENT_NAME = configObject.name; log.DEBUG("Updated name to: ", configObject.name); } } @@ -53,6 +60,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.ip != configObject.ip) { this.updateConfig('CLIENT_IP', configObject.ip); updatedKeys.push({'CLIENT_IP': configObject.ip}); + process.env.CLIENT_IP = configObject.ip; log.DEBUG("Updated ip to: ", configObject.ip); } } @@ -60,6 +68,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.port != configObject.port) { this.updateConfig('CLIENT_PORT', configObject.port); updatedKeys.push({'CLIENT_PORT': configObject.port}); + process.env.CLIENT_PORT = configObject.port; log.DEBUG("Updated port to: ", configObject.port); } } @@ -67,6 +76,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.location != configObject.location) { this.updateConfig('CLIENT_LOCATION', configObject.location); updatedKeys.push({'CLIENT_LOCATION': configObject.location}); + process.env.CLIENT_LOCATION = configObject.location; log.DEBUG("Updated location to: ", configObject.location); } } diff --git a/Client/views/index.ejs b/Client/views/index.ejs index 7b7a1d6..11a4460 100644 --- a/Client/views/index.ejs +++ b/Client/views/index.ejs @@ -1,11 +1,167 @@ - - - <%= title %> - - - -

    <%= title %>

    -

    Welcome to <%= title %>

    - - + + + + + Bootstrap demo + + + + + + + <%- include('partials/navbar.ejs') %> +
    + + + + +
    +
    +
    +
    +
    +
    +

    + + Node Details + +

    +
    +
    +
    + + Online + +
    +
    + + Join Server + + Leave Server + + Check-in + with Node + + Update Node +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +

    + Nearby Systems +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + <% for(const system in node.nearbySystems){ %> + + + + + + + <% // Update system modal %> + <%- include("partials/modifySystemModal.ejs", {'system': system, 'frequencies' : + node.nearbySystems[system].frequencies, 'mode' : node.nearbySystems[system].mode}) %> + <% } %> + +
    System NameFrequenciesProtocol 
    + <%= system %> + + <% if(node.nearbySystems[system].frequencies.length> 1) { %> +
      + <% for(const frequency of node.nearbySystems[system].frequencies) { %> +
    • + <%=frequency%> MHz +
    • + <% } %> +
    + <% } else { const frequency=node.nearbySystems[system].frequencies[0] %> + <%=frequency%> MHz + <% } %> +
    + + <%= node.nearbySystems[system].mode %> + + + "> + + + + + +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    + + <% // new System Modal %> + <%- include("partials/modifySystemModal.ejs", {'system': "New System" , 'frequencies' : [], 'mode' : '' }) %> + + + + + + + + \ No newline at end of file diff --git a/Client/views/partials/joinModal.ejs b/Client/views/partials/joinModal.ejs new file mode 100644 index 0000000..d912911 --- /dev/null +++ b/Client/views/partials/joinModal.ejs @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/Client/views/partials/modifySystemModal.ejs b/Client/views/partials/modifySystemModal.ejs new file mode 100644 index 0000000..664a755 --- /dev/null +++ b/Client/views/partials/modifySystemModal.ejs @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/Client/views/partials/navbar.ejs b/Client/views/partials/navbar.ejs new file mode 100644 index 0000000..b9816fc --- /dev/null +++ b/Client/views/partials/navbar.ejs @@ -0,0 +1,42 @@ + \ No newline at end of file From d4b974f81b2e28f3a35a0d252050525df07793f3 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 17:58:12 -0400 Subject: [PATCH 26/57] Update join command to accept a specific node ID --- Server/commands/join.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Server/commands/join.js b/Server/commands/join.js index 6dab9a7..e7f59b3 100644 --- a/Server/commands/join.js +++ b/Server/commands/join.js @@ -1,7 +1,7 @@ // Modules const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); -const { getMembersInRole, getAllClientIds, filterAutocompleteValues } = require("../utilities/utils"); +const { getMembersInRole, getAllClientIds, filterAutocompleteValues, getKeyByArrayValue } = require("../utilities/utils"); const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests"); const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId, getAllConnections } = require("../utilities/mysqlHandler"); @@ -14,15 +14,16 @@ const log = new DebugBuilder("server", "join"); * @param {*} presetName The preset name to listen to on the client * @param {*} channelId The channel ID to join the bot to * @param {*} connections EITHER A collection of clients that are currently connected OR a single discord client ID (NOT dev portal ID) that should be used to join the server with + * @param {number} nodeId [OPTIONAL] The node ID to join with (will join with another node if given node is not available) * @returns */ -async function joinServerWrapper(presetName, channelId, connections) { +async function joinServerWrapper(presetName, channelId, connections, nodeId = 0) { // Get nodes online var onlineNodes = await new Promise((recordResolve, recordReject) => { getOnlineNodes((nodeRows) => { recordResolve(nodeRows); }); - }); + }); // Check which nodes have the selected preset onlineNodes = onlineNodes.filter(node => node.presets.includes(presetName)); @@ -63,7 +64,10 @@ async function joinServerWrapper(presetName, channelId, connections) { selectedClientId = availableClientIds[0]; } - const selectedNode = nodesCurrentlyAvailable[0]; + let selectedNode; + if (nodeId > 0) selectedNode = getKeyByArrayValue(nodesCurrentlyAvailable, nodeId); + + if (!selectedNode) selectedNode = nodesCurrentlyAvailable[0]; const reqOptions = new requestOptions("/bot/join", "POST", selectedNode.ip, selectedNode.port); const postObject = { From 0426f5eb274684336cb82fd89735879ef0ca6984 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 17:58:35 -0400 Subject: [PATCH 27/57] Add jsDoc to leaveServerWrapper --- Server/commands/leave.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Server/commands/leave.js b/Server/commands/leave.js index c5ad0b2..e65de07 100644 --- a/Server/commands/leave.js +++ b/Server/commands/leave.js @@ -8,6 +8,11 @@ const { checkNodeConnectionByClientId, removeNodeConnectionByNodeId, getAllConne // Global Vars const log = new DebugBuilder("server", "leave"); +/** + * + * @param {*} clientIdObject The client ID object for the node to leave the server. Either 'clientId'||'name' can be set. + * @returns + */ async function leaveServerWrapper(clientIdObject) { if (!clientIdObject.clientId || !clientIdObject.name) return log.ERROR("Tried to leave server without client ID and/or Name"); From 0a8dc75a93b73a22356200cec750160642fa37bc Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:14:40 -0400 Subject: [PATCH 28/57] Update adminController to use join/leave command wrappers --- Server/controllers/adminController.js | 90 ++++++++------------------- 1 file changed, 27 insertions(+), 63 deletions(-) diff --git a/Server/controllers/adminController.js b/Server/controllers/adminController.js index 46b6b8c..fdef282 100644 --- a/Server/controllers/adminController.js +++ b/Server/controllers/adminController.js @@ -7,6 +7,9 @@ const log = new DebugBuilder("server", "adminController"); const mysqlHandler = require("../utilities/mysqlHandler"); const utils = require("../utilities/utils"); const requests = require("../utilities/httpRequests"); +const { leaveServerWrapper } = require("../commands/leave"); +const { joinServerWrapper } = require("../commands/join"); + /** Get the presets of all online nodes, can be used for functions * @@ -15,27 +18,10 @@ const requests = require("../utilities/httpRequests"); */ async function getPresetsOfOnlineNodes(callback) { mysqlHandler.getOnlineNodes((onlineNodes) => { - let systems = {}; - onlineNodes.forEach(onlineNode => { - systems[onlineNode.id] = utils.BufferToJson(onlineNode.nearbySystems); - }); - - return callback(systems); + return callback(onlineNodes); }); } -async function requestNodeListenToPreset(preset, nodeId, callback) { - mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ - reqOptions = new requests.requestOptions("/bot/join", "POST", nodeObject.ip, nodeObject.port); - requests.sendHttpRequest(reqOptions, JSON.stringify({ - "channelID": process.env.DEFAULT_VOICE_CHANNEL_ID, - "presetName": preset - }), (responseObject) => { - return callback(responseObject) - }); - }) -} - async function getNodeBotStatus(nodeId, callback) { mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); @@ -51,24 +37,6 @@ async function getNodeBotStatus(nodeId, callback) { }); } -async function requestNodeLeaveServer(nodeId, callback) { - getNodeBotStatus(nodeId, (responseObject) => { - if (responseObject === false) { - // Bot is joined - mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ - reqOptions = new requests.requestOptions("/bot/leave", "POST", nodeObject.ip, nodeObject.port); - requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { - return callback(responseObject); - }); - }); - } - else { - // Bot is free - return callback(false); - } - }) -} - /** Return to requests for the presets of all online nodes, cannot be used in functions * @@ -87,43 +55,39 @@ exports.getAvailablePresets = async (req, res) => { * * @param {*} req Express request parameter * @var {*} req.body.preset The preset to join (REQ) - * @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset) + * @var {*} req.body.nodeId The specific node to join (OPT/REQ if more than one node has the preset) + * @var {*} req.body.clientId The ID of the client that we want to join with + * @var {*} req.body.channelId The channel Id of the discord channel to join * @param {*} res Express response parameter */ exports.joinPreset = async (req, res) => { - if (!req.body.preset) return res.status(400).json("No preset specified"); - await getPresetsOfOnlineNodes((systems) => { - const systemsWithSelectedPreset = Object.values(systems).filter(nodePresets => nodePresets.includes(req.body.preset)).length - if (!systemsWithSelectedPreset) return res.status(400).json("No system online with that preset"); - if (systemsWithSelectedPreset > 1) { - if (!req.body.nodeId) return res.status(175).json("Multiple locations with the selected channel, please specify a nodeID (nodeId)") - requestNodeListenToPreset(req.body.preset, req.body.nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Timeout reached"); - return res.sendStatus(responseObject.statusCode); - }); - } - else { - let nodeId; - if (!req.body.nodeId) nodeId = utils.getKeyByArrayValue(systems, req.body.preset); - else nodeId = req.body.nodeId; - requestNodeListenToPreset(req.body.preset, nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Timeout reached"); - return res.sendStatus(responseObject.statusCode); - }); - } - }); + if (!req.body.preset) return res.status(400).json("No preset specified"); + if (!req.body.nodeId) return res.status(400).json("No node ID specified"); + if (!req.body.clientId) return res.status(400).json("No client ID specified"); + if (!req.body.channelId) return res.status(400).json("No channel ID specified"); + + const preset = req.body.preset; + const nodeId = req.body.nodeId; + const clientId = req.body.clientId; + const channelId = req.body.channelId; + + const joinedClient = await joinServerWrapper(preset, channelId, clientId, nodeId); + if (!joinedClient) return res.send(400).json("No joined client"); + return res.send(200).json(joinedClient); } /** Request a node to join the server listening to a specific preset * * @param {*} req Express request parameter * @param {*} res Express response parameter + * @var {*} req.body.clientId The ID of the client to disconnect */ exports.leaveServer = async (req, res) => { - if (!req.body.nodeId) return res.status(400).json("No nodeID specified"); + if (!req.body.clientId) return res.status(400).json("No clientID specified"); - requestNodeLeaveServer(req.body.nodeId, (responseObject) => { - if (responseObject === false) return res.status(400).json("Bot not joined to server"); - return res.sendStatus(responseObject.statusCode); - }); + const clientId = req.body.clientId; + + await leaveServerWrapper(clientId) + + return res.send(200); } \ No newline at end of file From cfeea57744eaeb0bec46cd83cba0b2f95ff6b1d5 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:14:53 -0400 Subject: [PATCH 29/57] Update jsDoc in nodeController --- Server/controllers/nodesController.js | 42 +++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 9c9d274..45d26e8 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -7,13 +7,14 @@ const utils = require("../utilities/utils"); const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.js"); const { nodeObject } = require("../utilities/recordHelper.js"); const { joinServerWrapper } = require("../commands/join"); +const { leaveServerWrapper } = require("../commands/leave"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; /** * - * @param {*} req - * @param {*} res + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router */ exports.listAllNodes = async (req, res) => { getAllNodes((allNodes) => { @@ -23,7 +24,11 @@ exports.listAllNodes = async (req, res) => { }); } -// Add a new node to the storage +/** + * Add a new node to the storage + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.newNode = async (req, res) => { if (!req.body.name) return res.status(400).json("No name specified for new node"); @@ -53,7 +58,11 @@ exports.newNode = async (req, res) => { } } -// Get the known info for the node specified +/** Get the known info for the node specified + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.getNodeInfo = async (req, res) => { if (!req.query.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.query.id, (nodeInfo) => { @@ -61,7 +70,11 @@ exports.getNodeInfo = async (req, res) => { }) } -// Updates the information received from the client based on ID +/** Updates the information received from the client based on ID + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ exports.nodeCheckIn = async (req, res) => { if (!req.body.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.body.id, (nodeInfo) => { @@ -126,6 +139,8 @@ exports.nodeCheckIn = async (req, res) => { /** * Request the node to join the specified server/channel and listen to the specified resource * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router * @param req.body.clientId The client ID to join discord with (NOT dev portal ID, right click user -> Copy ID) * @param req.body.channelId The Channel ID to join in Discord * @param req.body.presetName The preset name to listen to in Discord @@ -135,6 +150,17 @@ exports.requestNodeJoinServer = async (req, res) => { await joinServerWrapper(req.body.presetName, req.body.channelId, req.body.clientId); } +/** + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + * @param {*} req.body.clientId The client ID to request to leave the server + */ +exports.requestNodeLeaveServer = async (req, res) => { + if (!req.body.ClientId) return res.status(400).json("Missing client ID in request"); + await leaveServerWrapper({clientId: req.body.ClientId}); +} + /** * The node monitor service, this will periodically check in on the online nodes to make sure they are still online */ @@ -143,6 +169,9 @@ exports.nodeMonitorService = class nodeMonitorService { this.log = new DebugBuilder("server", "nodeMonitorService"); } + /** + * Start the node monitor service in the background + */ async start(){ // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start await new Promise(resolve => setTimeout(resolve, refreshInterval/10)); @@ -161,6 +190,9 @@ exports.nodeMonitorService = class nodeMonitorService { } } + /** + * Check in with all online nodes and mark any nodes that are actually offline + */ async checkInWithOnlineNodes(){ getOnlineNodes((nodes) => { this.log.DEBUG("Online Nodes: ", nodes); From 4a54be7e514bafdddeb4b830c4091fb620c53dab Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:15:17 -0400 Subject: [PATCH 30/57] Update jsDoc in utils --- Server/utilities/utils.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Server/utilities/utils.js b/Server/utilities/utils.js index 2acef73..06aa75b 100644 --- a/Server/utilities/utils.js +++ b/Server/utilities/utils.js @@ -119,7 +119,12 @@ exports.getClientObjectByClientID = (clientId) => { return undefined } - +/** + * Wrapper to filter auto complete + * + * @param {*} interaction + * @param {*} options + */ exports.filterAutocompleteValues = async (interaction, options) => { // Get the command used const command = interaction.command; From 02854fb78391ccb1751d0cd0e6b669f1d46f3da8 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 18:16:42 -0400 Subject: [PATCH 31/57] Initial bones for #37 --- Server/public/res/css/main.css | 142 ++++++++++++++++++++++++++ Server/public/res/js/controller.js | 3 + Server/routes/index.js | 27 ++++- Server/views/controller.ejs | 11 ++ Server/views/index.ejs | 144 +++++++++++++++++++++++++-- Server/views/node.ejs | 41 ++++++++ Server/views/partials/bodyEnd.ejs | 11 ++ Server/views/partials/head.ejs | 9 ++ Server/views/partials/htmlFooter.ejs | 1 + Server/views/partials/htmlHead.ejs | 6 ++ Server/views/partials/navbar.ejs | 40 ++++++++ Server/views/partials/nodeCard.ejs | 57 +++++++++++ Server/views/partials/sidebar.ejs | 83 +++++++++++++++ 13 files changed, 561 insertions(+), 14 deletions(-) create mode 100644 Server/public/res/css/main.css create mode 100644 Server/public/res/js/controller.js create mode 100644 Server/views/controller.ejs create mode 100644 Server/views/node.ejs create mode 100644 Server/views/partials/bodyEnd.ejs create mode 100644 Server/views/partials/head.ejs create mode 100644 Server/views/partials/htmlFooter.ejs create mode 100644 Server/views/partials/htmlHead.ejs create mode 100644 Server/views/partials/navbar.ejs create mode 100644 Server/views/partials/nodeCard.ejs create mode 100644 Server/views/partials/sidebar.ejs diff --git a/Server/public/res/css/main.css b/Server/public/res/css/main.css new file mode 100644 index 0000000..d057e2a --- /dev/null +++ b/Server/public/res/css/main.css @@ -0,0 +1,142 @@ +.node-card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #eff0f2; + border-radius: 1rem; + margin-bottom: 24px; + box-shadow: 0 2px 3px #e4e8f0; +} + +.avatar-md { + height: 4rem; + width: 4rem; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #f1f3f7; + border: 1px solid #eff0f2; + border-radius: 0.75rem; +} + +.avatar-title { + align-items: center; + background-color: #3b76e1; + color: #fff; + display: flex; + font-weight: 500; + height: 100%; + justify-content: center; + width: 100%; +} + +.bg-soft-primary { + background-color: rgba(59, 118, 225, .25) !important; +} + +a { + text-decoration: none !important; +} + +.badge-soft-danger { + color: #f56e6e !important; + background-color: rgba(245, 110, 110, .1); +} + +.badge-soft-success { + color: #63ad6f !important; + background-color: rgba(99, 173, 111, .1); +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.badge { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.75rem; +} + +/* Info Card Section */ +.info-card { + background-color: #fff; + border-radius: 10px; + border: none; + position: relative; + margin-bottom: 30px; + box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1); +} + +.info-card .card-statistic .card-icon-large .bi { + font-size: 110px; +} + +.info-card .card-statistic .card-icon { + text-align: center; + line-height: 50px; + margin-left: 15px; + color: #000; + position: absolute; + right: -5px; + top: 20px; + opacity: 0.1; +} + +/* Info Card Background Colors */ + +.l-bg-cherry { + background: linear-gradient(to right, #493240, #f09) !important; + color: #fff; +} + +.l-bg-blue-dark { + background: linear-gradient(to right, #373b44, #4286f4) !important; + color: #fff; +} + +.l-bg-green-dark { + background: linear-gradient(to right, #0a504a, #38ef7d) !important; + color: #fff; +} + +.l-bg-orange-dark { + background: linear-gradient(to right, #a86008, #ffba56) !important; + color: #fff; +} + +.l-bg-cyan { + background: linear-gradient(135deg, #289cf5, #84c0ec) !important; + color: #fff; +} + +.l-bg-green { + background: linear-gradient(135deg, #23bdb8 0%, #43e794 100%) !important; + color: #fff; +} + +.l-bg-orange { + background: linear-gradient(to right, #f9900e, #ffba56) !important; + color: #fff; +} + +/* Global Section */ +.sidebar-container { + min-height: 94.2vh; +} \ No newline at end of file diff --git a/Server/public/res/js/controller.js b/Server/public/res/js/controller.js new file mode 100644 index 0000000..04c0fd9 --- /dev/null +++ b/Server/public/res/js/controller.js @@ -0,0 +1,3 @@ +function sendNodeHeartbeat(nodeId) { + console.log(nodeId); +} \ No newline at end of file diff --git a/Server/routes/index.js b/Server/routes/index.js index a188406..1f43b2a 100644 --- a/Server/routes/index.js +++ b/Server/routes/index.js @@ -1,11 +1,12 @@ -const libCore = require("../libCore"); var express = require('express'); var router = express.Router(); +const { getAllNodes, getNodeInfoFromId } = require("../utilities/mysqlHandler"); + /* GET home page. */ router.get('/', (req, res) => { - var sources = libCore.getSources(); - //res.render('index', { "sources": sources }); + //var sources = libCore.getSources(); + return res.render('index'); var htmlOutput = ""; @@ -28,4 +29,24 @@ router.get('/', (req, res) => { res.send(htmlOutput); }); +/* GET node controller page. */ +router.get('/controller', async (req, res) => { + var nodes = await new Promise((recordResolve, recordReject) => { + getAllNodes((nodeRows) => { + recordResolve(nodeRows); + }); + }); + + //var sources = libCore.getSources(); + return res.render('controller', {'nodes' : nodes}); +}); + +/* GET individual node page. */ +router.get('/node/:id', async (req, res) => { + var node = await getNodeInfoFromId(req.params.id); + + //var sources = libCore.getSources(); + return res.render('node', {'node' : node}); +}); + module.exports = router; diff --git a/Server/views/controller.ejs b/Server/views/controller.ejs new file mode 100644 index 0000000..28099a9 --- /dev/null +++ b/Server/views/controller.ejs @@ -0,0 +1,11 @@ +<%- include('partials/htmlHead.ejs') %> +
    +
    + <% for(const node of nodes) {%> + <%- include('partials/nodeCard.ejs', {'node': node}) %> + <% } %> +
    +
    +<%- include('partials/bodyEnd.ejs') %> + +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/index.ejs b/Server/views/index.ejs index 7b7a1d6..b6524a3 100644 --- a/Server/views/index.ejs +++ b/Server/views/index.ejs @@ -1,11 +1,133 @@ - - - - <%= title %> - - - -

    <%= title %>

    -

    Welcome to <%= title %>

    - - +<%- include('partials/htmlHead.ejs') %> +
    +
    +
    +
    +
    +
    +
    +
    New Orders
    +
    +
    +
    +

    + 3,243 +

    +
    +
    + 12.5% +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    New Orders
    +
    +
    +
    +

    + 3,243 +

    +
    +
    + 12.5% +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    Phyllis Gatlin
    + Full Stack Developer +
    +
    +
    +

    070 2860 5375 +

    +

    + PhyllisGatlin@spy.com

    +

    52 + Ilchester MYBSTER 9WX

    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    Diana Owens
    + UI/UX Designer +
    +
    +
    +

    087 6321 3235 +

    +

    + DianaOwens@spy.com

    +

    52 + Ilchester MYBSTER 9WX

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +<%- include('partials/bodyEnd.ejs') %> + +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/node.ejs b/Server/views/node.ejs new file mode 100644 index 0000000..941e326 --- /dev/null +++ b/Server/views/node.ejs @@ -0,0 +1,41 @@ +<%- include('partials/htmlHead.ejs') %> +
    +
    + Account Details + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
    +
    +
    +
    + + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    + + +
    +
    +
    +<%- include('partials/bodyEnd.ejs') %> +<%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/bodyEnd.ejs b/Server/views/partials/bodyEnd.ejs new file mode 100644 index 0000000..2a347cf --- /dev/null +++ b/Server/views/partials/bodyEnd.ejs @@ -0,0 +1,11 @@ +
    +
    +
    + + + + + \ No newline at end of file diff --git a/Server/views/partials/head.ejs b/Server/views/partials/head.ejs new file mode 100644 index 0000000..3429389 --- /dev/null +++ b/Server/views/partials/head.ejs @@ -0,0 +1,9 @@ + + + + Bootstrap demo + + + + \ No newline at end of file diff --git a/Server/views/partials/htmlFooter.ejs b/Server/views/partials/htmlFooter.ejs new file mode 100644 index 0000000..62d09b8 --- /dev/null +++ b/Server/views/partials/htmlFooter.ejs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Server/views/partials/htmlHead.ejs b/Server/views/partials/htmlHead.ejs new file mode 100644 index 0000000..5a3ef3b --- /dev/null +++ b/Server/views/partials/htmlHead.ejs @@ -0,0 +1,6 @@ + + + <%- include('head.ejs') %> + + <%- include('navbar.ejs') %> + <%- include('sidebar.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs new file mode 100644 index 0000000..ccac10e --- /dev/null +++ b/Server/views/partials/navbar.ejs @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/Server/views/partials/nodeCard.ejs b/Server/views/partials/nodeCard.ejs new file mode 100644 index 0000000..b676560 --- /dev/null +++ b/Server/views/partials/nodeCard.ejs @@ -0,0 +1,57 @@ +
    +
    +
    + +
    +
    + +
    +
    +
    + <%= node.name %> +
    + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
    +
    +
    +

    + + <%= node.location %> +

    +

    + + + <%= node.ip %>:<%= node.port %> + +

    +

    +

    +

    +
    +
    +
    +
    \ No newline at end of file diff --git a/Server/views/partials/sidebar.ejs b/Server/views/partials/sidebar.ejs new file mode 100644 index 0000000..8eb5815 --- /dev/null +++ b/Server/views/partials/sidebar.ejs @@ -0,0 +1,83 @@ +
    +
    +
    + +
    +
    \ No newline at end of file From c5f7cc1da67373edc1c286b20020791edd32e797 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 15 Jul 2023 23:30:41 -0400 Subject: [PATCH 32/57] Additional changes for #37 - Updating side bar - Updating nav bar - Adding node details page - Adding controller page - Updating routes --- Server/controllers/nodesController.js | 144 ++++++++-------- Server/public/res/css/main.css | 36 +++- Server/public/res/js/node.js | 50 ++++++ Server/routes/nodes.js | 5 +- Server/utilities/recordHelper.js | 3 +- Server/views/node.ejs | 179 ++++++++++++++++---- Server/views/partials/bodyEnd.ejs | 5 +- Server/views/partials/modifySystemModal.ejs | 54 ++++++ Server/views/partials/navbar.ejs | 4 +- Server/views/partials/sidebar.ejs | 59 +------ 10 files changed, 376 insertions(+), 163 deletions(-) create mode 100644 Server/public/res/js/node.js create mode 100644 Server/views/partials/modifySystemModal.ejs diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 45d26e8..c1999ed 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -2,15 +2,61 @@ const { DebugBuilder } = require("../utilities/debugBuilder.js"); const log = new DebugBuilder("server", "nodesController"); // Utilities -const {getAllNodes, addNewNode, updateNodeInfo, getNodeInfoFromId, getOnlineNodes } = require("../utilities/mysqlHandler"); +const { getAllNodes, addNewNode, updateNodeInfo, getNodeInfoFromId, getOnlineNodes } = require("../utilities/mysqlHandler"); const utils = require("../utilities/utils"); const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.js"); const { nodeObject } = require("../utilities/recordHelper.js"); -const { joinServerWrapper } = require("../commands/join"); -const { leaveServerWrapper } = require("../commands/leave"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; +/** + * Check in with a singular node, mark it offline if it's offline and + * + * @param {*} node The node Object to check in with + */ +async function checkInWithNode(node) { + const reqOptions = new requestOptions("/client/requestCheckIn", "GET", node.ip, node.port) + sendHttpRequest(reqOptions, "", (responseObj) => { + if (responseObj) { + log.DEBUG("Response from: ", node.name, responseObj); + const onlineNode = new nodeObject({ _online: false, _id: node.id }); + log.DEBUG("Node update object: ", onlineNode); + updateNodeInfo(onlineNode, (sqlResponse) => { + if (!sqlResponse) this.log.ERROR("No response from SQL object"); + + log.DEBUG("Updated node: ", sqlResponse); + return true + }) + } + else { + log.DEBUG("No response from node, assuming it's offline"); + const offlineNode = new nodeObject({ _online: false, _id: node.id }); + log.DEBUG("Offline node update object: ", offlineNode); + updateNodeInfo(offlineNode, (sqlResponse) => { + if (!sqlResponse) this.log.ERROR("No response from SQL object"); + + log.DEBUG("Updated offline node: ", sqlResponse); + return false + }) + } + }) +} +exports.checkInWithNode = checkInWithNode; + +/** + * Check in with all online nodes and mark any nodes that are actually offline +*/ +async function checkInWithOnlineNodes() { + getOnlineNodes((nodes) => { + log.DEBUG("Online Nodes: ", nodes); + for (const node of nodes) { + checkInWithNode(node); + } + return; + }); +} +exports.checkInWithOnlineNodes = checkInWithOnlineNodes; + /** * * @param {*} req Default express req from router @@ -28,7 +74,7 @@ exports.listAllNodes = async (req, res) => { * Add a new node to the storage * @param {*} req Default express req from router * @param {*} res Defualt express res from router - */ + */ exports.newNode = async (req, res) => { if (!req.body.name) return res.status(400).json("No name specified for new node"); @@ -45,7 +91,7 @@ exports.newNode = async (req, res) => { addNewNode(newNode, (newNodeObject) => { // Send back a success if the user has been added and the ID for the client to keep track of - res.status(202).json({"nodeId": newNodeObject.id}); + res.status(202).json({ "nodeId": newNodeObject.id }); }) } catch (err) { @@ -62,7 +108,7 @@ exports.newNode = async (req, res) => { * * @param {*} req Default express req from router * @param {*} res Defualt express res from router - */ + */ exports.getNodeInfo = async (req, res) => { if (!req.query.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.query.id, (nodeInfo) => { @@ -75,7 +121,7 @@ exports.getNodeInfo = async (req, res) => { * @param {*} req Default express req from router * @param {*} res Defualt express res from router */ -exports.nodeCheckIn = async (req, res) => { +exports.nodeCheckIn = async (req, res) => { if (!req.body.id) return res.status(400).json("No id specified"); getNodeInfoFromId(req.body.id, (nodeInfo) => { let checkInObject = {}; @@ -87,7 +133,7 @@ exports.nodeCheckIn = async (req, res) => { if (req.body.name && req.body.name != nodeInfo.name) { checkInObject._name = req.body.name; isObjectUpdated = true; - } + } if (req.body.ip && req.body.ip != nodeInfo.ip) { checkInObject._ip = req.body.ip; @@ -115,9 +161,9 @@ exports.nodeCheckIn = async (req, res) => { } // If no changes are made tell the client - if (!isObjectUpdated) return res.status(200).json("No keys updated"); + if (!isObjectUpdated) return res.status(200).json("No keys updated"); - log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); + log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); checkInObject._id = req.body.id; checkInObject = new nodeObject(checkInObject); @@ -125,40 +171,28 @@ exports.nodeCheckIn = async (req, res) => { if (!nodeInfo) { log.WARN("No existing node found with this ID, adding node: ", checkInObject); addNewNode(checkInObject, (newNode) => { - return res.status(201).json({"updatedKeys": newNode}); + return res.status(201).json({ "updatedKeys": newNode }); }); } - else { + else { updateNodeInfo(checkInObject, () => { - return res.status(202).json({"updatedKeys": checkInObject}); + return res.status(202).json({ "updatedKeys": checkInObject }); }); - } + } }); } /** - * Request the node to join the specified server/channel and listen to the specified resource + * Requests a specific node to check in with the server, if it's online * * @param {*} req Default express req from router * @param {*} res Defualt express res from router - * @param req.body.clientId The client ID to join discord with (NOT dev portal ID, right click user -> Copy ID) - * @param req.body.channelId The Channel ID to join in Discord - * @param req.body.presetName The preset name to listen to in Discord */ -exports.requestNodeJoinServer = async (req, res) => { - if (!req.body.clientId || !req.body.channelId || !req.body.presetName) return res.status(400).json("Missing information in request, requires clientId, channelId, presetName"); - await joinServerWrapper(req.body.presetName, req.body.channelId, req.body.clientId); -} - -/** - * - * @param {*} req Default express req from router - * @param {*} res Defualt express res from router - * @param {*} req.body.clientId The client ID to request to leave the server - */ -exports.requestNodeLeaveServer = async (req, res) => { - if (!req.body.ClientId) return res.status(400).json("Missing client ID in request"); - await leaveServerWrapper({clientId: req.body.ClientId}); +exports.requestNodeCheckIn = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No Node ID supplied in request"); + const node = await getNodeInfoFromId(req.params.nodeId); + if (!node) return res.status(400).json("No Node with the ID given"); + checkInWithNode(node); } /** @@ -172,49 +206,21 @@ exports.nodeMonitorService = class nodeMonitorService { /** * Start the node monitor service in the background */ - async start(){ - // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start - await new Promise(resolve => setTimeout(resolve, refreshInterval/10)); + async start() { + // Wait for the a portion of the refresh period before checking in with the nodes, so the rest of the bot can start + await new Promise(resolve => setTimeout(resolve, refreshInterval / 10)); log.INFO("Starting Node Monitor Service"); // Check in before starting the infinite loop - await this.checkInWithOnlineNodes(); - - - while(true){ + await checkInWithOnlineNodes(); + + + while (true) { // Wait for the refresh interval, then wait for the posts to return, then wait a quarter of the refresh interval to make sure everything is cleared up await new Promise(resolve => setTimeout(resolve, refreshInterval)); - await this.checkInWithOnlineNodes(); + await checkInWithOnlineNodes(); await new Promise(resolve => setTimeout(resolve, refreshInterval / 4)); continue; - } - } - - /** - * Check in with all online nodes and mark any nodes that are actually offline - */ - async checkInWithOnlineNodes(){ - getOnlineNodes((nodes) => { - this.log.DEBUG("Online Nodes: ", nodes); - for (const node of nodes) { - const reqOptions = new requestOptions("/client/requestCheckIn", "GET", node.ip, node.port) - sendHttpRequest(reqOptions, "", (responseObj) => { - if (responseObj) { - this.log.DEBUG("Response from: ", node.name, responseObj); - } - else { - this.log.DEBUG("No response from node, assuming it's offline"); - const offlineNode = new nodeObject({ _online: 0, _id: node.id }); - this.log.DEBUG("Offline node update object: ", offlineNode); - updateNodeInfo(offlineNode, (sqlResponse) => { - if (!sqlResponse) this.log.ERROR("No response from SQL object"); - - this.log.DEBUG("Updated offline node: ", sqlResponse); - }) - } - }) - } - return; - }); + } } } diff --git a/Server/public/res/css/main.css b/Server/public/res/css/main.css index d057e2a..6384148 100644 --- a/Server/public/res/css/main.css +++ b/Server/public/res/css/main.css @@ -139,4 +139,38 @@ a { /* Global Section */ .sidebar-container { min-height: 94.2vh; -} \ No newline at end of file +} + +/* User table section */ + +.label { + border-radius: 3px; + font-size: 1.1em; + font-weight: 600; +} + +.user-list tbody td .user-subhead { + font-size: 1em; + font-style: italic; +} + +.table thead tr th { + text-transform: uppercase; + font-size: 0.875em; +} + +.table thead tr th { + border-bottom: 2px solid #e7ebee; +} + +.table tbody tr td:first-child { + font-size: 1.125em; + font-weight: 300; +} + +.table tbody tr td { + font-size: 0.875em; + vertical-align: middle; + border-top: 1px solid #e7ebee; + padding: 12px 8px; +} diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js new file mode 100644 index 0000000..a1530f4 --- /dev/null +++ b/Server/public/res/js/node.js @@ -0,0 +1,50 @@ +function addFrequencyInput(system){ + // Create new input + var icon = document.createElement('i'); + icon.classList.add('bi'); + icon.classList.add('bi-x-circle'); + icon.classList.add('text-black'); + + var remove = document.createElement('a'); + remove.classList.add('align-middle'); + remove.classList.add('float-left'); + remove.href = '#' + remove.appendChild(icon); + + var childColRemoveIcon = document.createElement('div'); + childColRemoveIcon.classList.add('col-2'); + childColRemoveIcon.appendChild(remove); + + var input = document.createElement('input'); + input.classList.add('form-control'); + input.id = 'nodeFreq'; + input.type = 'text'; + + var childColInput = document.createElement('div'); + childColInput.classList.add('col-10'); + childColInput.appendChild(input); + + var childRow = document.createElement('div'); + childRow.classList.add("row"); + childRow.classList.add("px-1"); + childRow.appendChild(childColInput); + childRow.appendChild(childColRemoveIcon); + + var colParent = document.createElement('div'); + colParent.classList.add("col-md-6"); + colParent.classList.add("mb-1"); + colParent.appendChild(childRow); + + document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); +} + +function checkInByNodeId(nodeId){ + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId; + Http.open("GET", url); + Http.send(); + + Http.onreadystatechange = (e) => { + console.log(Http.responseText) + } +} \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index b90e8db..773bc45 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -28,8 +28,7 @@ router.get('/nodeInfo', nodesController.getNodeInfo); // Client checkin with the server to update information router.post('/nodeCheckIn', nodesController.nodeCheckIn); -// TODO Need to authenticate this request -// Request a particular client to join a particular channel listening to a particular preset -router.post('/joinServer', nodesController.requestNodeJoinServer); +// Request a node to check in with the server +router.get('/:nodeId', nodesController.requestNodeCheckIn); module.exports = router; diff --git a/Server/utilities/recordHelper.js b/Server/utilities/recordHelper.js index 5464374..eb26ae3 100644 --- a/Server/utilities/recordHelper.js +++ b/Server/utilities/recordHelper.js @@ -118,8 +118,7 @@ class nodeObject { this.ip = _ip; this.port = _port; this.location = _location; - this.nearbySystems = _nearbySystems; - if (this.nearbySystems) this.presets = Object.keys(_nearbySystems); + this.nearbySystems = _nearbySystems; this.online = _online; } } diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 941e326..05cd447 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -1,41 +1,154 @@ <%- include('partials/htmlHead.ejs') %> -
    -
    - Account Details - <% if(node.online){%> Online - <% } else {%> Offline - <% } %> +
    +
    +
    +

    + + Node Details + +

    +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    + + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
    + + Join Server + + Leave Server + + Check-in with Node +
    +
    + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +

    + Nearby Systems +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + <% for(const system in node.nearbySystems){ %> + + + + + + + <% // Update system modal %> + <%- include("partials/modifySystemModal.ejs", {'system': system, 'frequencies': node.nearbySystems[system].frequencies}) %> + <% } %> + +
    System NameFrequenciesProtocol 
    + <%= system %> + + <% if(node.nearbySystems[system].frequencies.length> 1) { %> +
      + <% for(const frequency of + node.nearbySystems[system].frequencies) { %> +
    • + <%=frequency%> MHz +
    • + <% } %> +
    + <% } else { const + frequency=node.nearbySystems[system].frequencies[0] + %> + <%=frequency%> MHz + <% } %> +
    + + <%= node.nearbySystems[system].mode %> + + + "> + + + + + +
    +
    +
    +
    +
    +
    + + + + +
    +
    +
    -
    -
    -
    - - -
    -
    -
    - - -
    -
    -
    -
    - - + + <% // new System Modal %> + <%- include('partials/bodyEnd.ejs') %> + <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/bodyEnd.ejs b/Server/views/partials/bodyEnd.ejs index 2a347cf..992066b 100644 --- a/Server/views/partials/bodyEnd.ejs +++ b/Server/views/partials/bodyEnd.ejs @@ -7,5 +7,6 @@ integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"> - \ No newline at end of file + \ No newline at end of file diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs new file mode 100644 index 0000000..d24e83f --- /dev/null +++ b/Server/views/partials/modifySystemModal.ejs @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs index ccac10e..169185f 100644 --- a/Server/views/partials/navbar.ejs +++ b/Server/views/partials/navbar.ejs @@ -1,12 +1,13 @@
    <%- include('partials/bodyEnd.ejs') %> - + <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 2a1bb49..9575b9f 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -30,7 +30,7 @@ Leave Server - Check-in with Node + Check-in with Node
    From 61d7b69c10dfec0fce1cc3b8e454b7c44ccc63e2 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 01:51:25 -0400 Subject: [PATCH 35/57] Only show heartbeat toast once HTTP request is complete --- Server/public/res/js/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 68dedd6..b82d08a 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -86,7 +86,7 @@ function sendNodeHeartbeat(nodeId){ Http.open("GET", url); Http.send(); - Http.onreadystatechange = (e) => { + Http.onloadend = (e) => { console.log(Http.responseText) createToast(Http.responseText); } From 6ffa12911a17e9e668997366ed6175182db1adcb Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 01:54:13 -0400 Subject: [PATCH 36/57] Update toast creator to display proper date string --- Server/public/res/js/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index b82d08a..24b784e 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -44,7 +44,7 @@ function createToast(notificationMessage){ toastTitle.appendChild(document.createTextNode("Server Notification")); const toastTime = document.createElement('small'); - toastTime.appendChild(document.createTextNode(Date.now().toLocaleString())); + toastTime.appendChild(document.createTextNode(new Date(Date.now()).toLocaleString())); const toastClose = document.createElement('button'); toastClose.type = 'button'; From d7ea6bbbd47d643c2520bd1bf90cec38b30d1295 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 16:47:42 -0400 Subject: [PATCH 37/57] Updated NodeCard Design --- Server/views/partials/nodeCard.ejs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/Server/views/partials/nodeCard.ejs b/Server/views/partials/nodeCard.ejs index b676560..61082be 100644 --- a/Server/views/partials/nodeCard.ejs +++ b/Server/views/partials/nodeCard.ejs @@ -12,10 +12,6 @@
    -
    - -
    <%= node.name %> @@ -35,21 +31,16 @@ <%= node.ip %>:<%= node.port %> -

    +

    +

    -

    +
  • <%= system %>
  • + <% } %> +

    From 648782658c02e3aaded6a0c3360d9de579de2e2a Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 18:56:47 -0400 Subject: [PATCH 38/57] Update API and add webapp saving --- Server/controllers/nodesController.js | 32 +++++++++++++---- Server/public/res/js/node.js | 49 ++++++++++++++++++++++++++- Server/routes/nodes.js | 15 ++++---- Server/views/node.ejs | 41 +++++++++++----------- 4 files changed, 103 insertions(+), 34 deletions(-) diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index fa7b622..3012067 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -110,8 +110,8 @@ exports.newNode = async (req, res) => { * @param {*} res Defualt express res from router */ exports.getNodeInfo = async (req, res) => { - if (!req.query.id) return res.status(400).json("No id specified"); - getNodeInfoFromId(req.query.id, (nodeInfo) => { + if (!req.params.id) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.id, (nodeInfo) => { res.status(200).json(nodeInfo); }) } @@ -121,9 +121,9 @@ exports.getNodeInfo = async (req, res) => { * @param {*} req Default express req from router * @param {*} res Defualt express res from router */ -exports.nodeCheckIn = async (req, res) => { - if (!req.body.id) return res.status(400).json("No id specified"); - getNodeInfoFromId(req.body.id, (nodeInfo) => { +exports.updateExistingNode = async = (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.nodeId, (nodeInfo) => { let checkInObject = {}; // Convert the online status to a boolean to be worked with log.DEBUG("REQ Body: ", req.body); @@ -163,9 +163,9 @@ exports.nodeCheckIn = async (req, res) => { // If no changes are made tell the client if (!isObjectUpdated) return res.status(200).json("No keys updated"); - log.INFO("Updating the following keys for ID: ", req.body.id, checkInObject); + log.INFO("Updating the following keys for ID: ", req.params.nodeId, checkInObject); - checkInObject._id = req.body.id; + checkInObject._id = req.params.nodeId; checkInObject = new nodeObject(checkInObject); if (!nodeInfo) { @@ -182,6 +182,24 @@ exports.nodeCheckIn = async (req, res) => { }); } +/** Allows the bots to check in and get any updates from the server + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + */ +exports.nodeCheckIn = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + getNodeInfoFromId(req.params.nodeId, (nodeInfo) => { + if (!nodeInfo.online) { + nodeInfo.online = true; + updateNodeInfo(nodeInfo, () => { + return res.status(200).json(nodeInfo); + }) + } + else return res.status(200).json(nodeInfo); + }); +} + /** * Requests a specific node to check in with the server, if it's online * diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 24b784e..e439c30 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -78,11 +78,12 @@ function createToast(notificationMessage){ document.getElementById("toastZone").appendChild(wrapperDiv); $('.toast').toast('show'); + return $('.toast'); } function sendNodeHeartbeat(nodeId){ const Http = new XMLHttpRequest(); - const url='/nodes/'+nodeId; + const url='/nodes/nodeCheckIn/'+nodeId; Http.open("GET", url); Http.send(); @@ -139,5 +140,51 @@ function leaveServer(){ console.log(Http.status); console.log(responseObject); createToast(`${responseObject} is leaving`); + setTimeout(() => {}, 45000); } +} + +function saveNodeDetails() { + const nodeId = document.getElementById("nodeId").value; + const nodeName = document.getElementById("inputNodeName").value; + const nodeIp = document.getElementById("inputNodeIp").value; + const nodePort = document.getElementById("inputOrgName").value; + const nodeLocation = document.getElementById("inputNodeLocation").value; + + const reqBody = { + 'id': nodeId, + 'name': nodeName, + 'ip': nodeIp, + 'port': nodePort, + 'location': nodeLocation, + 'online': true + } + + console.log("Request Body: ", reqBody); + + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`Node Updated!`); + } + +} + +function addNewSystem() { + +} + +function updateSystem() { + +} + +function requestNodeUpdate() { + } \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index 773bc45..3a2b7d2 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -5,6 +5,13 @@ const nodesController = require('../controllers/nodesController'); /* GET nodes the server knows */ router.get('/', nodesController.listAllNodes); +// TODO Need to authenticate this request +/* GET the information the server has on a particular node */ +router.get('/:nodeId', nodesController.getNodeInfo); + +// Update an existing node +router.put('/:nodeId', nodesController.updateExistingNode); + // TODO Need to authenticate this request /* POST a new node to the server * @@ -20,15 +27,11 @@ router.get('/', nodesController.listAllNodes); */ router.post('/newNode', nodesController.newNode); -// TODO Need to authenticate this request -/* GET the information the server has on a particular node */ -router.get('/nodeInfo', nodesController.getNodeInfo); - // TODO Need to authenticate this request // Client checkin with the server to update information -router.post('/nodeCheckIn', nodesController.nodeCheckIn); +router.post('/nodeCheckIn/:nodeId', nodesController.nodeCheckIn); // Request a node to check in with the server -router.get('/:nodeId', nodesController.requestNodeCheckIn); +router.get('/nodeCheckIn/:nodeId', nodesController.requestNodeCheckIn); module.exports = router; diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 9575b9f..0bc3a9c 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -9,29 +9,30 @@

    +
    + + <% if(node.online){%> Online + <% } else {%> Offline + <% } %> +
    +
    + + Join Server + + Leave Server + + Check-in with Node + + Update Node +
    +
    -
    - -
    -
    -
    - - <% if(node.online){%> Online - <% } else {%> Offline - <% } %> -
    - - Join Server - - Leave Server - - Check-in with Node -
    +
    @@ -122,11 +123,11 @@
    - + - + data-bs-target="#newSystemModal">Add New System +
    From 0f114066a66e29fb24dc3149b18fcacdb00c1537 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sun, 16 Jul 2023 22:30:18 -0400 Subject: [PATCH 39/57] Implement functioning method to update systems on web app --- Client/controllers/clientController.js | 15 +++++-- Server/controllers/nodesController.js | 41 +++++++++++++++++ Server/public/res/js/node.js | 49 ++++++++++++++++++--- Server/routes/nodes.js | 5 ++- Server/views/partials/modifySystemModal.ejs | 22 +++++---- 5 files changed, 113 insertions(+), 19 deletions(-) diff --git a/Client/controllers/clientController.js b/Client/controllers/clientController.js index 8d02999..2101d44 100644 --- a/Client/controllers/clientController.js +++ b/Client/controllers/clientController.js @@ -94,10 +94,12 @@ exports.checkConfig = async function checkConfig() { } /** Check in with the server - * If the bot has a saved ID, check in with the server to update any information or just check back in + * If the bot has a saved ID, check in with the server to get any updated information or just check back in * If the bot does not have a saved ID, it will attempt to request a new ID from the server + * + * @param {boolean} init If set to true, the client will update the server to it's config, instead of taking the server's config */ -exports.checkIn = async () => { +exports.checkIn = async (init = false) => { let reqOptions; await this.checkConfig(); runningClientConfig.online = true; @@ -134,7 +136,8 @@ exports.checkIn = async () => { } else { // ID is in the config, checking in with the server - reqOptions = new requestOptions("/nodes/nodeCheckIn", "POST"); + if (init) reqOptions = new requestOptions(`/nodes/${runningClientConfig.id}`, "PUT"); + else reqOptions = new requestOptions(`/nodes/nodeCheckIn/${runningClientConfig.id}`, "POST"); sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), (responseObject) => { log.DEBUG("Check In Respose: ", responseObject); // Check if the server responded @@ -178,7 +181,8 @@ exports.requestCheckIn = async (req, res) => { * This is the endpoint wrapper to get the presets object */ exports.getPresets = async (req, res) => { - return res.status(200).json(getPresets()); + runningClientConfig.nearbySystems = getPresets(); + return res.status(200).json(runningClientConfig.nearbySystems); } /** Controller for the /client/updatePreset endpoint @@ -187,6 +191,7 @@ exports.getPresets = async (req, res) => { exports.updatePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { updatePreset(req.body.systemName, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile}); }) @@ -198,6 +203,7 @@ exports.updatePreset = async (req, res) => { exports.addNewPreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { addNewPreset(req.body.systemName, req.body.frequencies, req.body.mode, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, req.body.trunkFile); }); @@ -210,6 +216,7 @@ exports.removePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { if (!req.body.systemName) return res.status("500").json({"message": "You must specify a system name to delete, this must match exactly to how the system name is saved."}) removePreset(req.body.systemName, () => { + runningClientConfig.nearbySystems = getPresets(); return res.sendStatus(200); }, req.body.trunkFile); }); diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index 3012067..63588a6 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -8,6 +8,7 @@ const { sendHttpRequest, requestOptions } = require("../utilities/httpRequests.j const { nodeObject } = require("../utilities/recordHelper.js"); const refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000; +const digitalModes = ['p25']; /** * Check in with a singular node, mark it offline if it's offline and @@ -116,6 +117,44 @@ exports.getNodeInfo = async (req, res) => { }) } +/** Updates a specific system/preset on a given node + * + * @param {*} req Default express req from router + * @param {*} res Defualt express res from router + * @param {*} req.params.nodeId The Node ID to update the preset/system on + * @param {*} req.body.systemName The name of the system to update + * @param {*} req.body.mode The radio mode of the preset to + * @param {*} req.body.frequencies The frequencies of the preset + * @param {*} req.body.trunkFile The trunk file to use for digital stations + */ +exports.updateNodeSystem = async (req, res) => { + if (!req.params.nodeId) return res.status(400).json("No id specified"); + if (!req.body.systemName) return res.status(400).json("No system specified"); + log.DEBUG("Updating system for node: ", req.params.nodeId, req.body); + getNodeInfoFromId(req.params.nodeId, (node) => { + const reqOptions = new requestOptions("/client/updatePreset", "POST", node.ip, node.port); + const reqBody = { + 'systemName': req.body.systemName, + 'mode': req.body.mode, + 'frequencies': req.body.frequencies, + } + if(digitalModes.includes(req.body.mode)) reqBody['trunkFile'] = req.body.trunkFile ?? 'none' + + log.DEBUG("Request body for updating node: ", reqBody, reqOptions); + sendHttpRequest(reqOptions, JSON.stringify(reqBody), async (responseObj) => { + if(responseObj){ + // Good + log.DEBUG("Response from updating node: ", reqBody, responseObj); + return res.sendStatus(200) + } else { + // Bad + log.DEBUG("No Response from updating Node"); + return res.status(400).json("No Response from updating Node, could be offline"); + } + }) + }) +} + /** Updates the information received from the client based on ID * * @param {*} req Default express req from router @@ -171,11 +210,13 @@ exports.updateExistingNode = async = (req, res) => { if (!nodeInfo) { log.WARN("No existing node found with this ID, adding node: ", checkInObject); addNewNode(checkInObject, (newNode) => { + this.requestNodeCheckIn(checkInObject.id); return res.status(201).json({ "updatedKeys": newNode }); }); } else { updateNodeInfo(checkInObject, () => { + this.requestNodeCheckIn(checkInObject.id); return res.status(202).json({ "updatedKeys": checkInObject }); }); } diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index e439c30..e4e5274 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -1,4 +1,11 @@ -function addFrequencyInput(system){ +/** + * Remove a frequency input from the DOM + * + * @param {string} system The system name to add the frequency to + * @param {string} inputId [OPTIONAL] The ID of input, this can be anything unique to this input. If this is not provided the number of frequencies will be used as the ID + */ +function addFrequencyInput(system, inputId = null){ + if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length; // Create new input var icon = document.createElement('i'); icon.classList.add('bi'); @@ -9,6 +16,7 @@ function addFrequencyInput(system){ remove.classList.add('align-middle'); remove.classList.add('float-left'); remove.href = '#' + remove.onclick = () => {removeFrequencyInput(`${system}_systemFreqRow_${inputId}`)} remove.appendChild(icon); var childColRemoveIcon = document.createElement('div'); @@ -17,7 +25,7 @@ function addFrequencyInput(system){ var input = document.createElement('input'); input.classList.add('form-control'); - input.id = 'nodeFreq'; + input.id = `${system}_systemFreq_${inputId}`; input.type = 'text'; var childColInput = document.createElement('div'); @@ -33,6 +41,7 @@ function addFrequencyInput(system){ var colParent = document.createElement('div'); colParent.classList.add("col-md-6"); colParent.classList.add("mb-1"); + colParent.id = `${system}_systemFreqRow_${inputId}` colParent.appendChild(childRow); document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); @@ -169,22 +178,52 @@ function saveNodeDetails() { Http.send(JSON.stringify(reqBody)); Http.onloadend = (e) => { - const responseObject = JSON.parse(Http.responseText) + const responseObject = JSON.parse(Http.responseText); console.log(Http.status); console.log(responseObject); createToast(`Node Updated!`); } - } function addNewSystem() { } -function updateSystem() { +function updateSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + const systemMode = document.getElementById(`${systemName}_systemMode`).value; + const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs){ + systemFreqs.push(inputFreq.value); + } + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url='/nodes/'+nodeId+"/systems"; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Updated!`); + } } function requestNodeUpdate() { +} + +function removeFrequencyInput(elementId) { + const element = document.getElementById(elementId); + element.remove(); } \ No newline at end of file diff --git a/Server/routes/nodes.js b/Server/routes/nodes.js index 3a2b7d2..d110343 100644 --- a/Server/routes/nodes.js +++ b/Server/routes/nodes.js @@ -12,6 +12,9 @@ router.get('/:nodeId', nodesController.getNodeInfo); // Update an existing node router.put('/:nodeId', nodesController.updateExistingNode); +// Update an existing node's system +router.put('/:nodeId/systems', nodesController.updateNodeSystem); + // TODO Need to authenticate this request /* POST a new node to the server * @@ -28,7 +31,7 @@ router.put('/:nodeId', nodesController.updateExistingNode); router.post('/newNode', nodesController.newNode); // TODO Need to authenticate this request -// Client checkin with the server to update information +// Client checkin with the server to update client information router.post('/nodeCheckIn/:nodeId', nodesController.nodeCheckIn); // Request a node to check in with the server diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs index d24e83f..88ae381 100644 --- a/Server/views/partials/modifySystemModal.ejs +++ b/Server/views/partials/modifySystemModal.ejs @@ -1,5 +1,5 @@ diff --git a/Server/views/partials/navbar.ejs b/Server/views/partials/navbar.ejs index 204f863..b9816fc 100644 --- a/Server/views/partials/navbar.ejs +++ b/Server/views/partials/navbar.ejs @@ -28,10 +28,10 @@
  • Something else here
  • - */%> + diff --git a/Server/views/partials/sidebar.ejs b/Server/views/partials/sidebar.ejs index fd51b50..e323e7d 100644 --- a/Server/views/partials/sidebar.ejs +++ b/Server/views/partials/sidebar.ejs @@ -1,4 +1,12 @@
    +
    + + + + +
    +
    +
    +
    -
    \ No newline at end of file +
    + \ No newline at end of file From 4ceb71bd8479bd806f3c5338bd58ad12a27c0a54 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:29:47 -0400 Subject: [PATCH 46/57] Remove specific presets and left an example file if needed for clients --- Client/config/radioPresets.json | 1 - Client/config/radioPresets.json.EXAMPLE | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) delete mode 100644 Client/config/radioPresets.json create mode 100644 Client/config/radioPresets.json.EXAMPLE diff --git a/Client/config/radioPresets.json b/Client/config/radioPresets.json deleted file mode 100644 index b8f268a..0000000 --- a/Client/config/radioPresets.json +++ /dev/null @@ -1 +0,0 @@ -{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"},"coppies":{"frequencies":[154690000],"mode":"nbfm","trunkFile":"none"},"asdadadadasd":{"frequencies":[123321000],"mode":"nbfm","trunkFile":"none"}} \ No newline at end of file diff --git a/Client/config/radioPresets.json.EXAMPLE b/Client/config/radioPresets.json.EXAMPLE new file mode 100644 index 0000000..d33fe51 --- /dev/null +++ b/Client/config/radioPresets.json.EXAMPLE @@ -0,0 +1,18 @@ +{ + "Default P25 System Name": { + "frequencies": [ + 155344000, + 155444000, + 155555000 + ], + "mode": "p25", + "trunkFile": "trunk.tsv" + }, + "Default NBFM System": { + "frequencies": [ + 154690000 + ], + "mode": "nbfm", + "trunkFile": "none" + } +} \ No newline at end of file From c680c8fb2c7d5abaae241572f0a1d928da111c48 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:45:32 -0400 Subject: [PATCH 47/57] Fixed bug when editing node systems - Name defaulted to new system name --- Server/views/partials/modifySystemModal.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/views/partials/modifySystemModal.ejs b/Server/views/partials/modifySystemModal.ejs index db00411..664a755 100644 --- a/Server/views/partials/modifySystemModal.ejs +++ b/Server/views/partials/modifySystemModal.ejs @@ -12,7 +12,7 @@
    - <%= system %><%} else {%>Local Radio System<%}%>"> + <%= system %><%} else {%>Local Radio System<%}%>">
    "> From bc09840dda5eb0e68e4538dc3ff7f865d332a76e Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:46:19 -0400 Subject: [PATCH 48/57] Ignore local radio presets --- Client/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 Client/.gitignore diff --git a/Client/.gitignore b/Client/.gitignore new file mode 100644 index 0000000..c74a7af --- /dev/null +++ b/Client/.gitignore @@ -0,0 +1 @@ +*radioPresets.json \ No newline at end of file From 167f87128e054106b9805e83c8870b362d508beb Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 01:47:09 -0400 Subject: [PATCH 49/57] Check if the presets exist when going to get them - Return empty object if no preset file is found --- Client/utilities/updatePresets.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Client/utilities/updatePresets.js b/Client/utilities/updatePresets.js index 1e7bac6..21d6abd 100644 --- a/Client/utilities/updatePresets.js +++ b/Client/utilities/updatePresets.js @@ -17,7 +17,7 @@ function writePresets(presets, callback = undefined) { // Error checking if (err) throw err; log.DEBUG("Write Complete"); - if (callback) callback() + if (callback) callback(); else return }); } @@ -71,8 +71,9 @@ function convertFrequencyToHertz(frequency){ */ exports.getPresets = function getPresets() { const presetDir = path.resolve("./config/radioPresets.json"); - log.DEBUG(`Getting presets from directory: '${presetDir}'`); - return JSON.parse(fs.readFileSync(presetDir)); + log.DEBUG(`Getting presets from directory: '${presetDir}'`); + if (fs.existsSync(presetDir)) return JSON.parse(fs.readFileSync(presetDir)); + else return {}; } /** From abdb725964b7ac223ae277ea9b390ebd78780f35 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 03:27:39 -0400 Subject: [PATCH 50/57] Semi functional client webUI - can update client info for sure - Working on notifications now --- Client/controllers/clientController.js | 60 ++- Client/public/res/css/main.css | 183 +++++++++ Client/public/res/js/node.js | 400 ++++++++++++++++++++ Client/public/stylesheets/style.css | 8 - Client/routes/bot.js | 2 + Client/routes/client.js | 17 +- Client/routes/index.js | 6 +- Client/utilities/updateConfig.js | 12 +- Client/views/index.ejs | 176 ++++++++- Client/views/partials/joinModal.ejs | 44 +++ Client/views/partials/modifySystemModal.ejs | 61 +++ Client/views/partials/navbar.ejs | 42 ++ 12 files changed, 964 insertions(+), 47 deletions(-) create mode 100644 Client/public/res/css/main.css create mode 100644 Client/public/res/js/node.js delete mode 100644 Client/public/stylesheets/style.css create mode 100644 Client/views/partials/joinModal.ejs create mode 100644 Client/views/partials/modifySystemModal.ejs create mode 100644 Client/views/partials/navbar.ejs diff --git a/Client/controllers/clientController.js b/Client/controllers/clientController.js index 7d1729e..c8812b4 100644 --- a/Client/controllers/clientController.js +++ b/Client/controllers/clientController.js @@ -5,7 +5,7 @@ const log = new DebugBuilder("client", "clientController"); require('dotenv').config(); const modes = require("../config/modes"); // Modules -const { executeAsyncConsoleCommand, BufferToJson } = require("../utilities/utilities"); +const { executeAsyncConsoleCommand, BufferToJson, nodeObject } = require("../utilities/utilities"); // Utilities const { getFullConfig } = require("../utilities/configHandler"); const { updateId, updateConfig, updateClientConfig } = require("../utilities/updateConfig"); @@ -22,11 +22,11 @@ var runningClientConfig = getFullConfig() * @returns {*} */ function checkBodyForPresetFields(req, res, callback) { - if (!req.body?.systemName) return res.status(403).json({"message": "No system in the request"}); - if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({"message": "No frequencies in the request or type is not an array"}); - if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({"message": "No mode in the request"}); + if (!req.body?.systemName) return res.status(403).json({ "message": "No system in the request" }); + if (!req.body?.frequencies && Array.isArray(req.body.frequencies)) return res.status(403).json({ "message": "No frequencies in the request or type is not an array" }); + if (!req.body?.mode && typeof req.body.mode === "string") return res.status(403).json({ "message": "No mode in the request" }); if (!req.body?.trunkFile) { - if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({"message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request"}) + if (modes.digitalModes.includes(req.body.mode)) return res.status(403).json({ "message": "No trunk file in the request but digital mode specified. If you are not using a trunk file for this frequency make sure to specify 'none' for trunk file in the request" }) // If there is a value keep it but if not, add nothing so the system can update that key (if needed) req.body.trunkFile = req.body.trunkFile ?? "none"; } @@ -34,7 +34,7 @@ function checkBodyForPresetFields(req, res, callback) { return callback(); } -async function checkLocalIP() { +async function checkLocalIP() { if (process.platform === "win32") { // Windows var networkConfig = await executeAsyncConsoleCommand("ipconfig"); @@ -72,21 +72,21 @@ exports.checkConfig = async function checkConfig() { await updateId(0); runningClientConfig.id = 0; } - + if (!runningClientConfig.ip) { const ipAddr = await checkLocalIP(); await updateConfig('CLIENT_IP', ipAddr); runningClientConfig.ip = ipAddr; } - if(!runningClientConfig.name) { + if (!runningClientConfig.name) { const lastOctet = await String(checkLocalIP()).spit('.')[-1]; const name = `Radio-Node-${lastOctet}`; await updateConfig('CLIENT_NAME', name); runningClientConfig.name = name; } - if(!runningClientConfig.port) { + if (!runningClientConfig.port) { const port = 3010; await updateConfig('CLIENT_PORT', port); runningClientConfig.port = port; @@ -104,11 +104,11 @@ exports.checkIn = async (update = false) => { let reqOptions; await this.checkConfig(); // Check if there is an ID found, if not add the node to the server. If there was an ID, check in with the server to make sure it has the correct information - try { - if (!runningClientConfig?.id || runningClientConfig.id == 0) { + try { + if (!runningClientConfig?.id || runningClientConfig.id == 0) { // ID was not found in the config, creating a new node - reqOptions = new requestOptions("/nodes/newNode", "POST"); - sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), async (responseObject) => { + reqOptions = new requestOptions("/nodes/newNode", "POST"); + sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), async (responseObject) => { // Check if the server responded if (!responseObject) { log.WARN("Server did not respond to checkIn. Will wait 60 seconds then try again"); @@ -120,7 +120,7 @@ exports.checkIn = async (update = false) => { } // Update the client's ID if the server accepted its - if (responseObject.statusCode === 202) { + if (responseObject.statusCode === 202) { runningClientConfig.id = responseObject.body.nodeId; log.DEBUG("Response object from new node: ", responseObject, runningClientConfig); await updateId(runningClientConfig.id); @@ -131,7 +131,7 @@ exports.checkIn = async (update = false) => { log.DEBUG("HTTP Error: ", responseObject, await BufferToJson(responseObject.body)); await onHttpError(responseObject.statusCode); } - + }); } else { @@ -149,15 +149,17 @@ exports.checkIn = async (update = false) => { }, 60000); return } - + if (responseObject.statusCode === 202) { log.DEBUG("Updated keys: ", responseObject.body.updatedKeys) // Server accepted an update } if (responseObject.statusCode === 200) { // Server accepted the response but there were no keys to be updated - const tempUpdatedConfig = updateClientConfig(responseObject.body); - if (!tempUpdatedConfig.length > 0) return; + if (!update){ + const tempUpdatedConfig = updateClientConfig(responseObject.body); + if (!tempUpdatedConfig.length > 0) return; + } } if (responseObject.statusCode >= 300) { // Server threw an error @@ -179,6 +181,24 @@ exports.requestCheckIn = async (req, res) => { return res.sendStatus(200); } +/** + * Express JS Wrapper for checking and updating client config + * @param {*} req + * @param {*} res + * @returns + */ +exports.updateClientConfigWrapper = async (req, res) => { + // Convert the online status to a boolean to be worked with + log.DEBUG("REQ Body: ", req.body); + const updatedKeys = await updateClientConfig(req.body); + if (updatedKeys) { + log.DEBUG("Keys have been updated, updating running config and checking in with the server: ", updatedKeys); + runningClientConfig = await getFullConfig(); + await this.checkIn(true); + } + res.status(200).json(updatedKeys); +} + /** Controller for the /client/presets endpoint * This is the endpoint wrapper to get the presets object */ @@ -196,7 +216,7 @@ exports.updatePreset = async (req, res) => { runningClientConfig.nearbySystems = getPresets(); this.checkIn(true); return res.sendStatus(200); - }, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile}); + }, { frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile }); }) } @@ -218,7 +238,7 @@ exports.addNewPreset = async (req, res) => { */ exports.removePreset = async (req, res) => { checkBodyForPresetFields(req, res, () => { - if (!req.body.systemName) return res.status("500").json({"message": "You must specify a system name to delete, this must match exactly to how the system name is saved."}) + if (!req.body.systemName) return res.status("500").json({ "message": "You must specify a system name to delete, this must match exactly to how the system name is saved." }) removePreset(req.body.systemName, () => { runningClientConfig.nearbySystems = getPresets(); this.checkIn(true); diff --git a/Client/public/res/css/main.css b/Client/public/res/css/main.css new file mode 100644 index 0000000..5c44612 --- /dev/null +++ b/Client/public/res/css/main.css @@ -0,0 +1,183 @@ +.node-card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #eff0f2; + border-radius: 1rem; + margin-bottom: 24px; + box-shadow: 0 2px 3px #e4e8f0; +} + +.avatar-md { + height: 4rem; + width: 4rem; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #f1f3f7; + border: 1px solid #eff0f2; + border-radius: 0.75rem; +} + +.avatar-title { + align-items: center; + background-color: #3b76e1; + color: #fff; + display: flex; + font-weight: 500; + height: 100%; + justify-content: center; + width: 100%; +} + +.bg-soft-primary { + background-color: rgba(59, 118, 225, .25) !important; +} + +a { + text-decoration: none !important; +} + +.badge-soft-danger { + color: #f56e6e !important; + background-color: rgba(245, 110, 110, .1); +} + +.badge-soft-success { + color: #63ad6f !important; + background-color: rgba(99, 173, 111, .1); +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.badge { + display: inline-block; + padding: 0.25em 0.6em; + font-size: 75%; + font-weight: 500; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.75rem; +} + +/* Info Card Section */ +.info-card { + background-color: #fff; + border-radius: 10px; + border: none; + position: relative; + margin-bottom: 30px; + box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1); +} + +.info-card .card-statistic .card-icon-large .bi { + font-size: 110px; +} + +.info-card .card-statistic .card-icon { + text-align: center; + line-height: 50px; + margin-left: 15px; + color: #000; + position: absolute; + right: -5px; + top: 20px; + opacity: 0.1; +} + +/* Info Card Background Colors */ + +.l-bg-cherry { + background: linear-gradient(to right, #493240, #f09) !important; + color: #fff; +} + +.l-bg-blue-dark { + background: linear-gradient(to right, #373b44, #4286f4) !important; + color: #fff; +} + +.l-bg-green-dark { + background: linear-gradient(to right, #0a504a, #38ef7d) !important; + color: #fff; +} + +.l-bg-orange-dark { + background: linear-gradient(to right, #a86008, #ffba56) !important; + color: #fff; +} + +.l-bg-cyan { + background: linear-gradient(135deg, #289cf5, #84c0ec) !important; + color: #fff; +} + +.l-bg-green { + background: linear-gradient(135deg, #23bdb8 0%, #43e794 100%) !important; + color: #fff; +} + +.l-bg-orange { + background: linear-gradient(to right, #f9900e, #ffba56) !important; + color: #fff; +} + +/* Global Section */ +.sidebar-container { + min-height: 95vh; +} + +.sidebar { + position: fixed; + top: 5vh; + bottom: 0; + left: 0; + } + +/* User table section */ + +.label { + border-radius: 3px; + font-size: 1.1em; + font-weight: 600; +} + +.user-list tbody td .user-subhead { + font-size: 1em; + font-style: italic; +} + +.table thead tr th { + text-transform: uppercase; + font-size: 0.875em; +} + +.table thead tr th { + border-bottom: 2px solid #e7ebee; +} + +.table tbody tr td:first-child { + font-size: 1.125em; + font-weight: 300; +} + +.table tbody tr td { + font-size: 0.875em; + vertical-align: middle; + border-top: 1px solid #e7ebee; + padding: 12px 8px; +} diff --git a/Client/public/res/js/node.js b/Client/public/res/js/node.js new file mode 100644 index 0000000..dd2cf94 --- /dev/null +++ b/Client/public/res/js/node.js @@ -0,0 +1,400 @@ +$(document).ready(async () => { + console.log("Loading stored notifications..."); + await loadStoredToasts(); + console.log("Showing stored notifications..."); + await showStoredToasts(); +}); + +/** + * Gets all toasts stored in local storage + * + * @returns {Object} Object of toasts in storage + */ +function getStoredToasts() { + if (localStorage.getItem("toasts")) { + const storedToasts = JSON.parse(localStorage.getItem("toasts")); + console.log("LOADED STORED TOASTS: ", storedToasts); + navbarUpdateNotificationBellCount(storedToasts); + return storedToasts; + } + else return false +} + +/** + * Adds a toast to storage, will not allow duplicates + * + * @param {Date} time The date object from when the toast was created + * @param {*} message The message of the toast + */ +function addToastToStorage(time, message) { + var toasts = [{ 'time': time, 'message': message }] + var storedToasts = getStoredToasts(); + console.log("Adding new notification to storage: ", toasts); + toasts = toasts.concat(storedToasts); + console.log("Combined new and stored notifications: ", toasts); + toasts = toasts.filter((value, index, self) => + index === self.findIndex((t) => ( + t.time === value.time && t.message === value.message + )) + ) + console.log("Deduped stored notifications: ", toasts); + localStorage.setItem("toasts", JSON.stringify(toasts)); + navbarUpdateNotificationBellCount(toasts); +} + +/** + * Removes a toast from the local storage + * + * @param {Date} time The date object from when the toast was created + * @param {*} message The message of the toast + */ +function removeToastFromStorage(time, message) { + const toastToRemove = { 'time': time, 'message': message } + console.log("Toast to remove: ", toastToRemove); + var toasts = getStoredToasts(); + console.log("Stored toasts: ", toasts); + if (toasts.indexOf(toastToRemove)) toasts.splice(toasts.indexOf(toastToRemove) - 1, 1) + console.log("Toasts with selected toast removed: ", toasts); + localStorage.setItem("toasts", JSON.stringify(toasts)); + navbarUpdateNotificationBellCount(toasts); +} + +/** + * Shows all stored toasts + */ +function showStoredToasts() { + const storedToasts = getStoredToasts(); + if (!storedToasts) return + console.log("Loaded stored notifications to show: ", storedToasts); + for (const toast of storedToasts) { + const toastId = `${toast.time}-toast`; + console.log("Showing stored toast: ", toast, toastId); + const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(toastId)); + toastElement.show(); + } +} + +/** + * Loads all toasts stored in the local storage into the DOM of the webpage + */ +function loadStoredToasts() { + const storedToasts = getStoredToasts(); + if (!storedToasts) return + console.log("Loaded stored notifications: ", storedToasts); + for (const toast of storedToasts) { + createToast(toast.message, { time: toast.time }) + } +} + +/** + * Will update the count of notifications on the bell icon in the navbar + * + * @param {Array} storedToasts An array of stored toasts to be counted and updated in the navbar + */ +function navbarUpdateNotificationBellCount(storedToasts) { + const notificationBellIcon = document.getElementById("navbar-notification-bell"); + var notificationBellCount = document.getElementById("notification-bell-icon-count"); + if (!notificationBellCount) { + notificationBellCount = document.createElement('span'); + notificationBellCount.id = "notification-bell-icon-count"; + notificationBellCount.classList.add('badge'); + notificationBellCount.classList.add('text-bg-secondary'); + notificationBellCount.appendChild(document.createTextNode(storedToasts.length)); + } + else notificationBellCount.innerHTML = storedToasts.length; + + notificationBellIcon.appendChild(notificationBellCount); +} + +/** + * Remove a frequency input from the DOM + * + * @param {string} system The system name to add the frequency to + * @param {string} inputId [OPTIONAL] The ID of input, this can be anything unique to this input. If this is not provided the number of frequencies will be used as the ID + */ +function addFrequencyInput(system, inputId = null) { + if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length; + // Create new input + var icon = document.createElement('i'); + icon.classList.add('bi'); + icon.classList.add('bi-x-circle'); + icon.classList.add('text-black'); + + var remove = document.createElement('a'); + remove.classList.add('align-middle'); + remove.classList.add('float-left'); + remove.href = '#' + remove.onclick = () => { removeFrequencyInput(`${system}_systemFreqRow_${inputId}`) } + remove.appendChild(icon); + + var childColRemoveIcon = document.createElement('div'); + childColRemoveIcon.classList.add('col-2'); + childColRemoveIcon.appendChild(remove); + + var input = document.createElement('input'); + input.classList.add('form-control'); + input.id = `${system}_systemFreq_${inputId}`; + input.type = 'text'; + + var childColInput = document.createElement('div'); + childColInput.classList.add('col-10'); + childColInput.appendChild(input); + + var childRow = document.createElement('div'); + childRow.classList.add("row"); + childRow.classList.add("px-1"); + childRow.appendChild(childColInput); + childRow.appendChild(childColRemoveIcon); + + var colParent = document.createElement('div'); + colParent.classList.add("col-md-6"); + colParent.classList.add("mb-1"); + colParent.id = `${system}_systemFreqRow_${inputId}` + colParent.appendChild(childRow); + + document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); +} + +/** + * Add a toast element to the DOM + * + * @param {*} notificationMessage The message of the notification + * @param {Date} param1.time The date object for when the toast was created, blank if creating new + * @param {boolean} param1.showNow Show the toast now or just store it + * @returns + */ +function createToast(notificationMessage, { time = undefined, showNow = false } = {}) { + if (!time) time = new Date(Date.now()); + else time = new Date(Date.parse(time)); + const toastTitle = document.createElement('strong'); + toastTitle.classList.add('me-auto'); + toastTitle.appendChild(document.createTextNode("Server Notification")); + + const toastTime = document.createElement('small'); + toastTime.appendChild(document.createTextNode(time.toLocaleString())); + + const toastClose = document.createElement('button'); + toastClose.type = 'button'; + toastClose.classList.add('btn-close'); + toastClose.ariaLabel = 'Close'; + toastClose.setAttribute('data-bs-dismiss', 'toast'); + toastClose.onclick = () => { removeToastFromStorage(time.toISOString(), notificationMessage); }; + + const toastHeader = document.createElement('div'); + toastHeader.classList.add('toast-header'); + toastHeader.appendChild(toastTitle); + toastHeader.appendChild(toastTime); + toastHeader.appendChild(toastClose); + + const toastMessage = document.createElement('p'); + toastMessage.classList.add("px-2"); + toastMessage.appendChild(document.createTextNode(notificationMessage)); + + const toastBody = document.createElement('div'); + toastBody.classList.add('toast-body'); + toastBody.appendChild(toastMessage); + + const wrapperDiv = document.createElement('div'); + wrapperDiv.classList.add('toast'); + //wrapperDiv.classList.add('position-fixed'); + wrapperDiv.id = `${time.toISOString()}-toast`; + wrapperDiv.role = 'alert'; + wrapperDiv.ariaLive = 'assertive'; + wrapperDiv.ariaAtomic = true; + wrapperDiv.setAttribute('data-bs-delay', "7500"); + wrapperDiv.setAttribute('data-bs-animation', true); + wrapperDiv.appendChild(toastHeader); + wrapperDiv.appendChild(toastMessage); + + document.getElementById("toastZone").appendChild(wrapperDiv); + addToastToStorage(time.toISOString(), notificationMessage); + if (showNow) { + const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(`${time.toISOString()}-toast`)); + toastElement.show(); + } + + return; +} + +function sendNodeHeartbeat(nodeId) { + const Http = new XMLHttpRequest(); + const url = '/client/requestCheckIn' + nodeId; + Http.open("GET", url); + Http.send(); + + Http.onloadend = (e) => { + console.log(Http.responseText) + createToast(Http.responseText, { showNow: true }); + } +} + +function joinServer() { + const preset = document.getElementById("selectRadioPreset").value; + const clientId = document.getElementById("inputDiscordClientId").value; + const channelId = document.getElementById("inputDiscordChannelId").value; + + const reqBody = { + 'preset': preset, + 'clientId': clientId, + 'channelId': channelId + }; + + console.log(reqBody); + + const Http = new XMLHttpRequest(); + const url = '/bot/join'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText) + console.log(Http.status); + console.log(responseObject); + createToast(`${responseObject.name} will join shortly`, { showNow: true }); + $("#joinModal").modal('toggle'); + } +} + +function leaveServer() { + const reqBody = {}; + + const Http = new XMLHttpRequest(); + const url = '/bot/leave'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${responseObject} is leaving`, { showNow: true }); + } +} + +function saveNodeDetails() { + const nodeName = document.getElementById("inputNodeName").value; + const nodeIp = document.getElementById("inputNodeIp").value; + const nodePort = document.getElementById("inputOrgName").value; + const nodeLocation = document.getElementById("inputNodeLocation").value; + + const reqBody = { + 'name': nodeName, + 'ip': nodeIp, + 'port': nodePort, + 'location': nodeLocation + } + + console.log("Request Body: ", reqBody); + + const Http = new XMLHttpRequest(); + const url = '/client'; + Http.open("PUT", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = JSON.parse(Http.responseText); + console.log(Http.status); + console.log(responseObject); + createToast(`Node Updated!`); + location.reload(); + } +} + +function addNewSystem() { + const nodeId = document.getElementById("nodeId").value; + const systemName = document.getElementById(`New System_systemName`).value; + const systemMode = document.getElementById(`New System_systemMode`).value; + const inputSystemFreqs = $(`[id^="New System_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs) { + systemFreqs.push(inputFreq.value); + } + + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + if (reqBody.mode == "p25") reqBody.trunkFile = 'none'; + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = "/client/addPreset"; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Added!`); + location.reload(); + } +} + +function updateSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + const systemMode = document.getElementById(`${systemName}_systemMode`).value; + const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`); + let systemFreqs = []; + for (const inputFreq of inputSystemFreqs) { + systemFreqs.push(inputFreq.value); + } + + const reqBody = { + 'systemName': systemName, + 'mode': systemMode, + 'frequencies': systemFreqs + } + if (reqBody.mode == "p25") reqBody.trunkFile = 'none'; + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = "/client/updatePreset"; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Updated!`); + location.reload(); + } +} + +function removeSystem(systemName) { + const nodeId = document.getElementById("nodeId").value; + + const reqBody = { + 'systemName': systemName, + } + + console.log("Request Body: ", reqBody); + const Http = new XMLHttpRequest(); + const url = '/client/removePreset'; + Http.open("POST", url); + Http.setRequestHeader("Content-Type", "application/json"); + Http.send(JSON.stringify(reqBody)); + Http.onloadend = (e) => { + const responseObject = Http.responseText; + console.log(Http.status); + console.log(responseObject); + createToast(`${systemName} Removed!`); + location.reload(); + } +} + +function requestNodeUpdate() { + +} + +function removeFrequencyInput(elementId) { + const element = document.getElementById(elementId); + element.remove(); +} \ No newline at end of file diff --git a/Client/public/stylesheets/style.css b/Client/public/stylesheets/style.css deleted file mode 100644 index 9453385..0000000 --- a/Client/public/stylesheets/style.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - padding: 50px; - font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; -} - -a { - color: #00B7FF; -} diff --git a/Client/routes/bot.js b/Client/routes/bot.js index 805f8a1..3af6afa 100644 --- a/Client/routes/bot.js +++ b/Client/routes/bot.js @@ -15,7 +15,9 @@ router.get('/status', botController.getStatus); * * @param req The request sent from the master * @param req.body.channelId The channel ID to join +* @param req.body.clientId The discord Client ID to use when connecting to the server * @param req.body.presetName The name of the preset to start listening to +* @param req.body.NGThreshold [OPTIONAL] The noisegate threshold, this will default to 50 */ router.post('/join', botController.joinServer); diff --git a/Client/routes/client.js b/Client/routes/client.js index c3a7ed0..36d6bd5 100644 --- a/Client/routes/client.js +++ b/Client/routes/client.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); // Controllers -const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, updateClient } = require("../controllers/clientController"); +const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, updateClient, updateClientConfigWrapper } = require("../controllers/clientController"); /** GET Request a check in from the client * Queue the client to check in with the server @@ -11,16 +11,21 @@ const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, up */ router.get('/requestCheckIn', requestCheckIn); -/** GET Object of all known presets - * Query the client to get all the known presets - */ -router.put('/', ); - /** GET Object of all known presets * Query the client to get all the known presets */ router.get('/presets', getPresets); +/** + * PUT An update to the running client config (not radio config) + * @param {number} req.body.id The ID given to the node to update + * @param {string} req.body.name The name of the node + * @param {string} req.body.ip The IP the server can contact the node on + * @param {number} req.body.port The port the server can contact the node on + * @param {string} req.body.location The physical location of the node + */ +router.put('/', updateClientConfigWrapper); + /** POST Update to preset * * @param req The request sent from the master diff --git a/Client/routes/index.js b/Client/routes/index.js index ecca96a..bf776f0 100644 --- a/Client/routes/index.js +++ b/Client/routes/index.js @@ -1,9 +1,11 @@ var express = require('express'); var router = express.Router(); +const { getFullConfig } = require('../utilities/configHandler'); /* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', { title: 'Express' }); +router.get('/', async function(req, res, next) { + const clientConfig = await getFullConfig(); + res.render('index', { 'node': clientConfig }); }); module.exports = router; diff --git a/Client/utilities/updateConfig.js b/Client/utilities/updateConfig.js index 250d90c..9e90ba8 100644 --- a/Client/utilities/updateConfig.js +++ b/Client/utilities/updateConfig.js @@ -27,7 +27,12 @@ exports.updateId = (updatedId) => { /** * Wrapper to update any or all keys in the client config * - * @param {*} configObject Object with what keys you wish to update (node object format, will be converted) + * @param {Object} configObject Object with what keys you wish to update (node object format, will be converted) + * @param {number} configObject.id The ID given to the node to update + * @param {string} configObject.name The name of the node + * @param {string} configObject.ip The IP the server can contact the node on + * @param {number} configObject.port The port the server can contact the node on + * @param {string} configObject.location The physical location of the node * @returns */ exports.updateClientConfig = (configObject) => { @@ -39,6 +44,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.id != configObject.id) { this.updateConfig('CLIENT_ID', configObject.id); updatedKeys.push({'CLIENT_ID': configObject.id}); + process.env.CLIENT_ID = configObject.id; log.DEBUG("Updated ID to: ", configObject.id); } } @@ -46,6 +52,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.name != configObject.name) { this.updateConfig('CLIENT_NAME', configObject.name); updatedKeys.push({'CLIENT_NAME': configObject.name}); + process.env.CLIENT_NAME = configObject.name; log.DEBUG("Updated name to: ", configObject.name); } } @@ -53,6 +60,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.ip != configObject.ip) { this.updateConfig('CLIENT_IP', configObject.ip); updatedKeys.push({'CLIENT_IP': configObject.ip}); + process.env.CLIENT_IP = configObject.ip; log.DEBUG("Updated ip to: ", configObject.ip); } } @@ -60,6 +68,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.port != configObject.port) { this.updateConfig('CLIENT_PORT', configObject.port); updatedKeys.push({'CLIENT_PORT': configObject.port}); + process.env.CLIENT_PORT = configObject.port; log.DEBUG("Updated port to: ", configObject.port); } } @@ -67,6 +76,7 @@ exports.updateClientConfig = (configObject) => { if (runningConfig.location != configObject.location) { this.updateConfig('CLIENT_LOCATION', configObject.location); updatedKeys.push({'CLIENT_LOCATION': configObject.location}); + process.env.CLIENT_LOCATION = configObject.location; log.DEBUG("Updated location to: ", configObject.location); } } diff --git a/Client/views/index.ejs b/Client/views/index.ejs index 7b7a1d6..11a4460 100644 --- a/Client/views/index.ejs +++ b/Client/views/index.ejs @@ -1,11 +1,167 @@ - - - <%= title %> - - - -

    <%= title %>

    -

    Welcome to <%= title %>

    - - + + + + + Bootstrap demo + + + + + + + <%- include('partials/navbar.ejs') %> +
    + + + + +
    +
    +
    +
    +
    +
    +

    + + Node Details + +

    +
    +
    +
    + + Online + +
    +
    + + Join Server + + Leave Server + + Check-in + with Node + + Update Node +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +

    + Nearby Systems +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + <% for(const system in node.nearbySystems){ %> + + + + + + + <% // Update system modal %> + <%- include("partials/modifySystemModal.ejs", {'system': system, 'frequencies' : + node.nearbySystems[system].frequencies, 'mode' : node.nearbySystems[system].mode}) %> + <% } %> + +
    System NameFrequenciesProtocol 
    + <%= system %> + + <% if(node.nearbySystems[system].frequencies.length> 1) { %> +
      + <% for(const frequency of node.nearbySystems[system].frequencies) { %> +
    • + <%=frequency%> MHz +
    • + <% } %> +
    + <% } else { const frequency=node.nearbySystems[system].frequencies[0] %> + <%=frequency%> MHz + <% } %> +
    + + <%= node.nearbySystems[system].mode %> + + + "> + + + + + +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    + + <% // new System Modal %> + <%- include("partials/modifySystemModal.ejs", {'system': "New System" , 'frequencies' : [], 'mode' : '' }) %> + + + + + + + + \ No newline at end of file diff --git a/Client/views/partials/joinModal.ejs b/Client/views/partials/joinModal.ejs new file mode 100644 index 0000000..d912911 --- /dev/null +++ b/Client/views/partials/joinModal.ejs @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/Client/views/partials/modifySystemModal.ejs b/Client/views/partials/modifySystemModal.ejs new file mode 100644 index 0000000..664a755 --- /dev/null +++ b/Client/views/partials/modifySystemModal.ejs @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/Client/views/partials/navbar.ejs b/Client/views/partials/navbar.ejs new file mode 100644 index 0000000..b9816fc --- /dev/null +++ b/Client/views/partials/navbar.ejs @@ -0,0 +1,42 @@ + \ No newline at end of file From a5996ccfc0b04aee7d46456cb19bdb4118b7c3ec Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 04:21:06 -0400 Subject: [PATCH 51/57] Added page titles to server webUI --- Server/routes/index.js | 26 +++----------------------- Server/views/index.ejs | 2 +- Server/views/partials/head.ejs | 15 ++++++++++++++- Server/views/partials/htmlHead.ejs | 2 +- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/Server/routes/index.js b/Server/routes/index.js index 1f43b2a..b79078f 100644 --- a/Server/routes/index.js +++ b/Server/routes/index.js @@ -6,27 +6,7 @@ const { getAllNodes, getNodeInfoFromId } = require("../utilities/mysqlHandler"); /* GET home page. */ router.get('/', (req, res) => { //var sources = libCore.getSources(); - return res.render('index'); - - var htmlOutput = ""; - - sources.forEach(source => { - htmlOutput += ` -
    - -
    Title: ${source.title}
    -
    Link: ${source.link}
    -
    category: ${source.category}
    - -
    -
    -
    - -
    - - ` - }); - res.send(htmlOutput); + return res.render('index', {'page': 'index'}); }); /* GET node controller page. */ @@ -38,7 +18,7 @@ router.get('/controller', async (req, res) => { }); //var sources = libCore.getSources(); - return res.render('controller', {'nodes' : nodes}); + return res.render('controller', {'nodes' : nodes, 'page': 'controller'}); }); /* GET individual node page. */ @@ -46,7 +26,7 @@ router.get('/node/:id', async (req, res) => { var node = await getNodeInfoFromId(req.params.id); //var sources = libCore.getSources(); - return res.render('node', {'node' : node}); + return res.render('node', {'node' : node, 'page': 'node'}); }); module.exports = router; diff --git a/Server/views/index.ejs b/Server/views/index.ejs index b6524a3..8e72b3f 100644 --- a/Server/views/index.ejs +++ b/Server/views/index.ejs @@ -1,4 +1,4 @@ -<%- include('partials/htmlHead.ejs') %> +<%- include('partials/htmlHead.ejs', {'page': page}) %>
    diff --git a/Server/views/partials/head.ejs b/Server/views/partials/head.ejs index 3429389..2aa5d1a 100644 --- a/Server/views/partials/head.ejs +++ b/Server/views/partials/head.ejs @@ -1,7 +1,20 @@ - Bootstrap demo + <% switch (page) { + case "index":%> + Node Dashboard + <% break; + case "controller":%> + Node Controller + <% break; + case "node":%> + Node Configuration + <% break; + default:%> + DRB_CnC Server + <%break; + } %> diff --git a/Server/views/partials/htmlHead.ejs b/Server/views/partials/htmlHead.ejs index a1b1d37..89af0ef 100644 --- a/Server/views/partials/htmlHead.ejs +++ b/Server/views/partials/htmlHead.ejs @@ -1,6 +1,6 @@ - <%- include('head.ejs') %> + <%- include('head.ejs', {'page': page}) %> <%- include('navbar.ejs') %> <%- include('sidebar.ejs') %> \ No newline at end of file From ec091c00179a669b45acf2eea93c20b21de5746d Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 14:46:51 -0400 Subject: [PATCH 52/57] Only concat the stored toasts with new toast when there are stored toasts --- Client/public/res/js/node.js | 16 +++++++++------- Server/public/res/js/node.js | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Client/public/res/js/node.js b/Client/public/res/js/node.js index dd2cf94..7b8d45d 100644 --- a/Client/public/res/js/node.js +++ b/Client/public/res/js/node.js @@ -30,13 +30,15 @@ function addToastToStorage(time, message) { var toasts = [{ 'time': time, 'message': message }] var storedToasts = getStoredToasts(); console.log("Adding new notification to storage: ", toasts); - toasts = toasts.concat(storedToasts); - console.log("Combined new and stored notifications: ", toasts); - toasts = toasts.filter((value, index, self) => - index === self.findIndex((t) => ( - t.time === value.time && t.message === value.message - )) - ) + if (storedToasts) { + toasts = toasts.concat(storedToasts); + console.log("Combined new and stored notifications: ", toasts); + toasts = toasts.filter((value, index, self) => + index === self.findIndex((t) => ( + t.time === value.time && t.message === value.message + )) + ) + } console.log("Deduped stored notifications: ", toasts); localStorage.setItem("toasts", JSON.stringify(toasts)); navbarUpdateNotificationBellCount(toasts); diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 67ea019..41e8f6c 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -30,13 +30,15 @@ function addToastToStorage(time, message) { var toasts = [{ 'time': time, 'message': message }] var storedToasts = getStoredToasts(); console.log("Adding new notification to storage: ", toasts); - toasts = toasts.concat(storedToasts); - console.log("Combined new and stored notifications: ", toasts); - toasts = toasts.filter((value, index, self) => - index === self.findIndex((t) => ( - t.time === value.time && t.message === value.message - )) - ) + if (storedToasts) { + toasts = toasts.concat(storedToasts); + console.log("Combined new and stored notifications: ", toasts); + toasts = toasts.filter((value, index, self) => + index === self.findIndex((t) => ( + t.time === value.time && t.message === value.message + )) + ) + } console.log("Deduped stored notifications: ", toasts); localStorage.setItem("toasts", JSON.stringify(toasts)); navbarUpdateNotificationBellCount(toasts); From 8a0baa5bc96644bdccea733d8428f1c426198768 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Sat, 22 Jul 2023 15:01:48 -0400 Subject: [PATCH 53/57] Small tweaks - Update local webUI to refresh when joining server to clear modal - Updated client webUI page title - Re-added join modal to node page (client & server) - updated join modal to use node.nearbySystems --- Client/public/res/js/node.js | 4 ++-- Client/views/index.ejs | 6 ++++-- Client/views/partials/joinModal.ejs | 2 +- Server/public/res/js/node.js | 2 +- Server/views/node.ejs | 2 ++ Server/views/partials/joinModal.ejs | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Client/public/res/js/node.js b/Client/public/res/js/node.js index 7b8d45d..746de7b 100644 --- a/Client/public/res/js/node.js +++ b/Client/public/res/js/node.js @@ -253,8 +253,8 @@ function joinServer() { const responseObject = JSON.parse(Http.responseText) console.log(Http.status); console.log(responseObject); - createToast(`${responseObject.name} will join shortly`, { showNow: true }); - $("#joinModal").modal('toggle'); + createToast(`${responseObject.name} will join shortly`); + location.reload(); } } diff --git a/Client/views/index.ejs b/Client/views/index.ejs index 11a4460..84aa23d 100644 --- a/Client/views/index.ejs +++ b/Client/views/index.ejs @@ -3,7 +3,7 @@ - Bootstrap demo + '<%=node.name%>' - Configuration @@ -153,7 +153,9 @@
    <% // new System Modal %> - <%- include("partials/modifySystemModal.ejs", {'system': "New System" , 'frequencies' : [], 'mode' : '' }) %> + <%- include("partials/modifySystemModal.ejs", {'system': "New System" , 'frequencies' : [], 'mode' : '' }) %> + <% // Join Server Modal %> + <%- include("partials/joinModal.ejs", {'node': node}) %> diff --git a/Client/views/partials/joinModal.ejs b/Client/views/partials/joinModal.ejs index d912911..1cc6feb 100644 --- a/Client/views/partials/joinModal.ejs +++ b/Client/views/partials/joinModal.ejs @@ -24,7 +24,7 @@
    diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index 41e8f6c..2fe1e6a 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -256,7 +256,7 @@ function joinServer() { console.log(Http.status); console.log(responseObject); createToast(`${responseObject.name} will join shortly`, { showNow: true }); - $("#joinModal").modal('toggle'); + location.reload(); } } diff --git a/Server/views/node.ejs b/Server/views/node.ejs index e56ba5e..4d8c5d7 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -134,5 +134,7 @@ <% // new System Modal %> <%- include("partials/modifySystemModal.ejs", {'system': "New System", 'frequencies': [], 'mode': ''}) %> + <% // Join Server Modal %> + <%- include("partials/joinModal.ejs", {'node': node}) %> <%- include('partials/bodyEnd.ejs') %> <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/joinModal.ejs b/Server/views/partials/joinModal.ejs index d912911..1cc6feb 100644 --- a/Server/views/partials/joinModal.ejs +++ b/Server/views/partials/joinModal.ejs @@ -24,7 +24,7 @@
    From 2260deee01a77be7db10a717c073f7b1e530a368 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Fri, 4 Aug 2023 22:10:09 -0400 Subject: [PATCH 54/57] Added filter presets function to utils and code formatted --- Server/utilities/utils.js | 44 +++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/Server/utilities/utils.js b/Server/utilities/utils.js index 06aa75b..5dc3737 100644 --- a/Server/utilities/utils.js +++ b/Server/utilities/utils.js @@ -23,7 +23,7 @@ exports.BufferToJson = (buffer) => { * @returns {string} The sanitized preset name to be used elsewhere */ exports.SanitizePresetName = (presetName) => { - return String(presetName).toLowerCase().replace(/[\W_]+/g,"-") + return String(presetName).toLowerCase().replace(/[\W_]+/g, "-") } /** @@ -32,16 +32,16 @@ exports.SanitizePresetName = (presetName) => { * @param interaction Discord interaction object * @param param0.roleName {OPTIONAL} The role name to check the members in; Defaults to 'Bots' */ -exports.getMembersInRole = async (interaction, roleName = "Bots" ) => { +exports.getMembersInRole = async (interaction, roleName = "Bots") => { log.DEBUG("Fetching all members"); var guild = await interaction.client.guilds.fetch({ guild: interaction.guild.id, cache: false }); //cache all members in the server - await guild.members.fetch({cache: false}); - await guild.roles.fetch({cache: false}); + await guild.members.fetch({ cache: false }); + await guild.roles.fetch({ cache: false }); log.VERBOSE("Guild: ", guild); const role = await guild.roles.cache.find(role => role.name === roleName); //the role to check log.DEBUG("Role to check members from: ", role); log.DEBUG("Members of role: ", role.members); - + // This is not working, can't get the status of the users, rest of join is untested const onlineMembers = await role.members.filter(member => member.voice.channel !== null); const offlineMembers = await role.members.filter(member => member.voice.channel === null); @@ -50,8 +50,8 @@ exports.getMembersInRole = async (interaction, roleName = "Bots" ) => { log.VERBOSE("All members: ", allMembers, onlineMembers, offlineMembers) return { - 'online': onlineMembers, - 'offline': offlineMembers, + 'online': onlineMembers, + 'offline': offlineMembers, 'all': allMembers } } @@ -64,9 +64,9 @@ exports.getMembersInRole = async (interaction, roleName = "Bots" ) => { */ exports.getKeyByArrayValue = (object, value) => { if (typeof value == "string") return Object.keys(object).find(key => object[key].includes(value)); - const valueKey = Object.keys(value)[0]; + const valueKey = Object.keys(value)[0]; return Object.keys(object).find(key => (object[key][valueKey] == value[valueKey])); - } +} /** * Check to see if the input is a valid JSON string @@ -91,7 +91,7 @@ exports.isJsonString = (str) => { exports.getAllClientIds = () => { const jsonClientIds = JSON.parse(readFileSync(path.resolve(__dirname, '../clientIds.json'))); var clientObjects = []; - for (const jsonClientId of Object.keys(jsonClientIds)){ + for (const jsonClientId of Object.keys(jsonClientIds)) { clientObjects.push(new clientObject({ _discord_id: jsonClientId, _name: jsonClientIds[jsonClientId].name, @@ -110,7 +110,7 @@ exports.getAllClientIds = () => { exports.getClientObjectByClientID = (clientId) => { const clientObjects = this.getAllClientIds(); log.DEBUG("All client IDs: ", clientObjects); - for (const clientObject of clientObjects){ + for (const clientObject of clientObjects) { if (clientObject.clientId == clientId) { log.DEBUG("Found client ID from given ID: ", clientObject); return clientObject @@ -138,4 +138,26 @@ exports.filterAutocompleteValues = async (interaction, options) => { await interaction.respond( filtered.map(option => ({ name: option, value: option })), ); +} + +/** + * Filter an array of nodeObjects to get all unique presets within + * + * @param {Array} nodeObjects An array of nodeObjects to get the presets from + * @returns {Array} Presets available from given nodeObjects + */ +exports.filterPresetsAvailable = async (nodeObjects) => { + log.DEBUG("Node objects: ", nodeObjects); + var presetsAvailable = []; + for (const nodeObject of nodeObjects) { + log.DEBUG("Node object: ", nodeObject); + presetsAvailable.push.apply(presetsAvailable, nodeObject.presets); + } + + log.DEBUG("All Presets available: ", presetsAvailable); + + // Remove duplicates + presetsAvailable = [...new Set(presetsAvailable)]; + log.DEBUG("DeDuped Presets available: ", presetsAvailable); + return presetsAvailable; } \ No newline at end of file From 76c4d002a0fd3b50f50ec38d2024d56cf7a1783f Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Fri, 4 Aug 2023 22:10:28 -0400 Subject: [PATCH 55/57] Update to filter presets function in utils --- Server/commands/join.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/Server/commands/join.js b/Server/commands/join.js index d608d39..5de2aa5 100644 --- a/Server/commands/join.js +++ b/Server/commands/join.js @@ -1,7 +1,7 @@ // Modules const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); -const { filterAutocompleteValues } = require("../utilities/utils"); +const { filterAutocompleteValues, filterPresetsAvailable } = require("../utilities/utils"); const { getOnlineNodes, getAllConnections } = require("../utilities/mysqlHandler"); const { joinServerWrapper } = require("../controllers/adminController"); @@ -28,18 +28,7 @@ module.exports = { recordResolve(nodeRows); }); }); - log.DEBUG("Node objects: ", nodeObjects); - var presetsAvailable = []; - for (const nodeObject of nodeObjects) { - log.DEBUG("Node object: ", nodeObject); - presetsAvailable.push.apply(presetsAvailable, nodeObject.presets); - } - - log.DEBUG("All Presets available: ", presetsAvailable); - - // Remove duplicates - options = [...new Set(presetsAvailable)]; - log.DEBUG("DeDuped Presets available: ", options); + const options = await filterPresetsAvailable(nodeObjects); // Filter the results to what the user is entering filterAutocompleteValues(interaction, options); From 880f1ccb01cb463407f66b6ede2ff4cec3681c76 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Fri, 4 Aug 2023 23:21:35 -0400 Subject: [PATCH 56/57] Implemented v1 admin dashboard --- Server/public/res/css/main.css | 1 + Server/routes/index.js | 27 +++- Server/views/index.ejs | 177 +++++++---------------- Server/views/partials/connectioncard.ejs | 29 ++++ Server/views/partials/valueChip.ejs | 32 ++++ 5 files changed, 136 insertions(+), 130 deletions(-) create mode 100644 Server/views/partials/connectioncard.ejs create mode 100644 Server/views/partials/valueChip.ejs diff --git a/Server/public/res/css/main.css b/Server/public/res/css/main.css index 5c44612..a0aeba6 100644 --- a/Server/public/res/css/main.css +++ b/Server/public/res/css/main.css @@ -82,6 +82,7 @@ a { position: relative; margin-bottom: 30px; box-shadow: 0 0.46875rem 2.1875rem rgba(90, 97, 105, 0.1), 0 0.9375rem 1.40625rem rgba(90, 97, 105, 0.1), 0 0.25rem 0.53125rem rgba(90, 97, 105, 0.12), 0 0.125rem 0.1875rem rgba(90, 97, 105, 0.1); + min-height: 85%; } .info-card .card-statistic .card-icon-large .bi { diff --git a/Server/routes/index.js b/Server/routes/index.js index b79078f..87c0a60 100644 --- a/Server/routes/index.js +++ b/Server/routes/index.js @@ -1,12 +1,25 @@ var express = require('express'); var router = express.Router(); -const { getAllNodes, getNodeInfoFromId } = require("../utilities/mysqlHandler"); +const { getAllNodes, getNodeInfoFromId, getAllConnections } = require("../utilities/mysqlHandler"); +const { filterPresetsAvailable } = require("../utilities/utils"); /* GET home page. */ -router.get('/', (req, res) => { - //var sources = libCore.getSources(); - return res.render('index', {'page': 'index'}); +router.get('/', async (req, res) => { + var nodes = await new Promise((recordResolve, recordReject) => { + getAllNodes((nodeRows) => { + recordResolve(nodeRows); + }); + }); + var connections = await getAllConnections(); + var presets = await new Promise((recordResolve, recordReject) => { + getAllNodes((nodeRows) => { + recordResolve(filterPresetsAvailable(nodeRows)); + }); + }); + + //var sources = libCore.getSources(); + return res.render('index', { 'page': 'index', 'nodes': nodes, 'connections': connections, 'presets': presets }); }); /* GET node controller page. */ @@ -15,10 +28,10 @@ router.get('/controller', async (req, res) => { getAllNodes((nodeRows) => { recordResolve(nodeRows); }); - }); + }); //var sources = libCore.getSources(); - return res.render('controller', {'nodes' : nodes, 'page': 'controller'}); + return res.render('controller', { 'nodes': nodes, 'page': 'controller' }); }); /* GET individual node page. */ @@ -26,7 +39,7 @@ router.get('/node/:id', async (req, res) => { var node = await getNodeInfoFromId(req.params.id); //var sources = libCore.getSources(); - return res.render('node', {'node' : node, 'page': 'node'}); + return res.render('node', { 'node': node, 'page': 'node' }); }); module.exports = router; diff --git a/Server/views/index.ejs b/Server/views/index.ejs index 8e72b3f..4eb1c60 100644 --- a/Server/views/index.ejs +++ b/Server/views/index.ejs @@ -1,133 +1,64 @@ <%- include('partials/htmlHead.ejs', {'page': page}) %>
    +
    + <%- include('partials/valueChip.ejs', { + 'title': 'Nodes in the Network', + 'bgColor': "orange-dark", + 'value': nodes.length, + 'progressPercent': false, + 'icon': 'server' + }) %> + + <%- include('partials/valueChip.ejs', { + 'title': 'Nodes Online', + 'bgColor': "green", + 'value': nodes.filter(node => node.online).length, + 'progressPercent': false, + 'icon': 'cpu-fill' + }) %> + + <%- include('partials/valueChip.ejs', { + 'title': 'Nodes with Discord Connections', + 'bgColor': "blue-dark", + 'value': connections.length, + 'progressPercent': false, + 'icon': 'gear-wide-connected' + }) %> + +
    + +
    +

    Current Connections

    +
    + +
    +
    -
    -
    -
    -
    -
    -
    New Orders
    -
    -
    -
    -

    - 3,243 -

    -
    -
    - 12.5% -
    -
    -
    -
    -
    -
    -
    -
    + <% for(const conn of connections) { %> + <%- include('partials/connectionCard.ejs', {'connection': conn}) %> + <%}%> +
    -
    -
    -
    -
    -
    -
    New Orders
    -
    -
    -
    -

    - 3,243 -

    -
    -
    - 12.5% -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    Phyllis Gatlin
    - Full Stack Developer -
    -
    -
    -

    070 2860 5375 -

    -

    - PhyllisGatlin@spy.com

    -

    52 - Ilchester MYBSTER 9WX

    -
    -
    -
    -
    - -
    -
    -
    - -
    -
    -
    -
    Diana Owens
    - UI/UX Designer -
    -
    -
    -

    087 6321 3235 -

    -

    - DianaOwens@spy.com

    -

    52 - Ilchester MYBSTER 9WX

    -
    -
    - - -
    -
    -
    -
    +
    +

    Online Nodes

    +
    +
    +
    + <% for(const node of nodes.filter(node => node.online)) { %> + <%- include('partials/nodeCard.ejs', {'node': node}) %> + <%}%> +
    +
    +

    Offline Nodes

    +
    +
    +
    + <% for(const node of nodes.filter(node => node.online == false)) { %> + <%- include('partials/nodeCard.ejs', {'node': node}) %> + <%}%>
    <%- include('partials/bodyEnd.ejs') %> - <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/connectioncard.ejs b/Server/views/partials/connectioncard.ejs new file mode 100644 index 0000000..d1a0711 --- /dev/null +++ b/Server/views/partials/connectioncard.ejs @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/Server/views/partials/valueChip.ejs b/Server/views/partials/valueChip.ejs new file mode 100644 index 0000000..12b11ea --- /dev/null +++ b/Server/views/partials/valueChip.ejs @@ -0,0 +1,32 @@ +
    +
    +
    +
    +
    +
    + <%=title%> +
    +
    +
    +
    +

    + <%=value%> +

    +
    + <% if (progressPercent) {%> +
    + + <%=progressPercent%>% + +
    + <%}%> +
    + <% if (progressPercent) {%> +
    +
    +
    + <%}%> +
    +
    +
    \ No newline at end of file From 75580c0547e35c85edbdc35e08b14ffb550340e0 Mon Sep 17 00:00:00 2001 From: Logan Cusano Date: Fri, 4 Aug 2023 23:44:28 -0400 Subject: [PATCH 57/57] Fix bug in httprequests, hostname was overwritten --- Client/utilities/httpRequests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Client/utilities/httpRequests.js b/Client/utilities/httpRequests.js index f3ddc4b..beecbaf 100644 --- a/Client/utilities/httpRequests.js +++ b/Client/utilities/httpRequests.js @@ -12,8 +12,8 @@ exports.requestOptions = class requestOptions { if (["POST", "PUT"].includes(method)){ log.VERBOSE("Hostname Vars: ", hostname, process.env.SERVER_HOSTNAME, process.env.SERVER_IP); if (hostname) this.hostname = hostname; - if (process.env.SERVER_HOSTNAME) this.hostname = process.env.SERVER_HOSTNAME; - if (process.env.SERVER_IP) this.hostname = process.env.SERVER_IP; + if (!this.hostname && process.env.SERVER_HOSTNAME) this.hostname = process.env.SERVER_HOSTNAME; + if (!this.hostname && process.env.SERVER_IP) this.hostname = process.env.SERVER_IP; if (!this.hostname) throw new Error("No server hostname / IP was given when creating a request"); this.path = path; this.port = port ?? process.env.SERVER_PORT;