diff --git a/Server/commands/join.js b/Server/commands/join.js index e7f59b3..d608d39 100644 --- a/Server/commands/join.js +++ b/Server/commands/join.js @@ -1,98 +1,13 @@ // Modules const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); -const { getMembersInRole, getAllClientIds, filterAutocompleteValues, getKeyByArrayValue } = require("../utilities/utils"); -const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests"); -const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId, getAllConnections } = require("../utilities/mysqlHandler"); +const { filterAutocompleteValues } = require("../utilities/utils"); +const { getOnlineNodes, getAllConnections } = require("../utilities/mysqlHandler"); +const { joinServerWrapper } = require("../controllers/adminController"); // Global Vars const log = new DebugBuilder("server", "join"); -/** - * * This wrapper will check if there is an available node with the requested preset and if so checks for an available client ID to join with - * - * @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, 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)); - log.DEBUG("Filtered Online Nodes: ", onlineNodes); - - // Check if any nodes with this preset are available - var nodesCurrentlyAvailable = []; - for (const node of onlineNodes) { - const currentConnection = await getConnectionByNodeId(node.id); - log.DEBUG("Checking to see if there is a connection for Node: ", node, currentConnection); - if(!currentConnection) nodesCurrentlyAvailable.push(node); - } - log.DEBUG("Nodes Currently Available: ", nodesCurrentlyAvailable); - - // If not, let the user know - if (!nodesCurrentlyAvailable.length > 0) return Error("All nodes with this channel are unavailable, consider swapping one of the currently joined bots."); - - // If so, join with the first node - var availableClientIds = await getAllClientIds(); - log.DEBUG("All clients: ", Object.keys(availableClientIds)); - - var selectedClientId; - if (typeof connections === 'string') { - for (const availableClientId of availableClientIds) { - if (availableClientId.discordId != connections ) selectedClientId = availableClientId; - } - } - else { - log.DEBUG("Open connections: ", connections); - for (const connection of connections) { - log.DEBUG("Used Client ID: ", connection); - availableClientIds = availableClientIds.filter(cid => cid.discordId != connection.clientObject.discordId); - } - - log.DEBUG("Available Client IDs: ", availableClientIds); - - if (!Object.keys(availableClientIds).length > 0) return log.ERROR("All client ID have been used, consider swapping one of the curretly joined bots or adding more Client IDs to the pool.") - selectedClientId = availableClientIds[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 = { - "channelId": channelId, - "clientId": selectedClientId.clientId, - "presetName": presetName - }; - log.INFO("Post Object: ", postObject); - sendHttpRequest(reqOptions, JSON.stringify(postObject), async (responseObj) => { - log.VERBOSE("Response Object from node ", selectedNode, responseObj); - if (!responseObj || !responseObj.statusCode == 200) return false; - // Node has connected to discord - // Updating node Object in DB - const updatedNode = await updateNodeInfo(selectedNode); - log.DEBUG("Updated Node: ", updatedNode); - - // Adding a new node connection - const nodeConnection = await addNodeConnection(selectedNode, selectedClientId); - log.DEBUG("Node Connection: ", nodeConnection); - }); - - return selectedClientId; -} -exports.joinServerWrapper = joinServerWrapper; - module.exports = { data: new SlashCommandBuilder() .setName('join') diff --git a/Server/commands/leave.js b/Server/commands/leave.js index e65de07..760c5b0 100644 --- a/Server/commands/leave.js +++ b/Server/commands/leave.js @@ -2,41 +2,12 @@ const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); const { getAllClientIds, getKeyByArrayValue, filterAutocompleteValues } = require("../utilities/utils"); -const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests"); -const { checkNodeConnectionByClientId, removeNodeConnectionByNodeId, getAllConnections } = require('../utilities/mysqlHandler'); +const { getAllConnections } = require('../utilities/mysqlHandler'); +const { leaveServerWrapper } = require('../controllers/adminController'); // 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"); - - const node = await checkNodeConnectionByClientId(clientIdObject); - - reqOptions = new requestOptions("/bot/leave", "POST", node.ip, node.port); - - const responseObj = await new Promise((recordResolve, recordReject) => { - sendHttpRequest(reqOptions, JSON.stringify({}), async (responseObj) => { - recordResolve(responseObj); - }); - }); - - log.VERBOSE("Response Object from node ", node, responseObj); - if (!responseObj || !responseObj.statusCode == 202) return false; - // Node has disconnected from discor - // Removing the node connection from the DB - const removedConnection = removeNodeConnectionByNodeId(node.id); - log.DEBUG("Removed Node Connection: ", removedConnection); - - return; -} -exports.leaveServerWrapper = leaveServerWrapper; - module.exports = { data: new SlashCommandBuilder() .setName('leave') @@ -64,7 +35,6 @@ module.exports = { log.DEBUG("Client names: ", clinetIds); const clientDiscordId = getKeyByArrayValue(clinetIds, {'name': botName}); log.DEBUG("Selected bot: ", clinetIds[clientDiscordId]); - // Need to create a table in DB to keep track of what bots have what IDs or an endpoint on the clients to return what ID they are running with await leaveServerWrapper(clinetIds[clientDiscordId]); await interaction.editReply(`**${clinetIds[clientDiscordId].name}** has been disconnected`); // This will reply to the initial interaction diff --git a/Server/controllers/adminController.js b/Server/controllers/adminController.js index fdef282..bc699b2 100644 --- a/Server/controllers/adminController.js +++ b/Server/controllers/adminController.js @@ -4,12 +4,9 @@ require('dotenv').config(); const { DebugBuilder } = require("../utilities/debugBuilder.js"); const log = new DebugBuilder("server", "adminController"); // Utilities -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"); - +const { getAllClientIds } = require("../utilities/utils"); +const { getOnlineNodes, updateNodeInfo, addNodeConnection, getConnectionByNodeId, getNodeInfoFromId, checkNodeConnectionByClientId, removeNodeConnectionByNodeId } = require("../utilities/mysqlHandler"); +const { requestOptions, sendHttpRequest } = require("../utilities/httpRequests"); /** Get the presets of all online nodes, can be used for functions * @@ -17,15 +14,15 @@ const { joinServerWrapper } = require("../commands/join"); * @returns {*} A list of the systems online */ async function getPresetsOfOnlineNodes(callback) { - mysqlHandler.getOnlineNodes((onlineNodes) => { + getOnlineNodes((onlineNodes) => { return callback(onlineNodes); }); } async function getNodeBotStatus(nodeId, callback) { - mysqlHandler.getNodeInfoFromId(nodeId, (nodeObject) =>{ - reqOptions = new requests.requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); - requests.sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { + getNodeInfoFromId(nodeId, (nodeObject) =>{ + reqOptions = new requestOptions("/bot/status", "GET", nodeObject.ip, nodeObject.port, undefined, 5); + sendHttpRequest(reqOptions, JSON.stringify({}), (responseObject) => { if (responseObject === false) { // Bot is joined } @@ -73,21 +70,141 @@ exports.joinPreset = async (req, res) => { const joinedClient = await joinServerWrapper(preset, channelId, clientId, nodeId); if (!joinedClient) return res.send(400).json("No joined client"); - return res.send(200).json(joinedClient); + return res.status(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 + * @var {*} req.body.nodeId The ID of the node to disconnect */ exports.leaveServer = async (req, res) => { - if (!req.body.clientId) return res.status(400).json("No clientID specified"); + if (!req.body.nodeId) return res.status(400).json("No node ID specified"); - const clientId = req.body.clientId; + const nodeId = req.body.nodeId; + const currentConnection = await getConnectionByNodeId(nodeId); + log.DEBUG("Current Connection for node: ", currentConnection); - await leaveServerWrapper(clientId) + await leaveServerWrapper(currentConnection.clientObject) - return res.send(200); -} \ No newline at end of file + return res.status(200).json(currentConnection.clientObject.name); +} + +/** + * * This wrapper will check if there is an available node with the requested preset and if so checks for an available client ID to join with + * + * @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, 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)); + log.DEBUG("Filtered Online Nodes: ", onlineNodes); + + // Check if any nodes with this preset are available + var nodesCurrentlyAvailable = []; + for (const node of onlineNodes) { + const currentConnection = await getConnectionByNodeId(node.id); + log.DEBUG("Checking to see if there is a connection for Node: ", node, currentConnection); + if(!currentConnection) nodesCurrentlyAvailable.push(node); + } + log.DEBUG("Nodes Currently Available: ", nodesCurrentlyAvailable); + + // If not, let the user know + if (!nodesCurrentlyAvailable.length > 0) return Error("All nodes with this channel are unavailable, consider swapping one of the currently joined bots."); + + // If so, join with the first node + var availableClientIds = await getAllClientIds(); + log.DEBUG("All clients: ", Object.keys(availableClientIds)); + + var selectedClientId; + if (typeof connections === 'string') { + for (const availableClientId of availableClientIds) { + if (availableClientId.discordId != connections ) selectedClientId = availableClientId; + } + } + else { + log.DEBUG("Open connections: ", connections); + for (const connection of connections) { + log.DEBUG("Used Client ID: ", connection); + availableClientIds = availableClientIds.filter(cid => cid.discordId != connection.clientObject.discordId); + } + + log.DEBUG("Available Client IDs: ", availableClientIds); + + if (!Object.keys(availableClientIds).length > 0) return log.ERROR("All client ID have been used, consider swapping one of the curretly joined bots or adding more Client IDs to the pool.") + selectedClientId = availableClientIds[0]; + } + + let selectedNode; + if (nodeId > 0) { + for(const availableNode of nodesCurrentlyAvailable){ + if (availableNode.id == nodeId) selectedNode = availableNode; + } + } + + if (!selectedNode) selectedNode = nodesCurrentlyAvailable[0]; + + const reqOptions = new requestOptions("/bot/join", "POST", selectedNode.ip, selectedNode.port); + const postObject = { + "channelId": channelId, + "clientId": selectedClientId.clientId, + "presetName": presetName + }; + log.INFO("Post Object: ", postObject); + sendHttpRequest(reqOptions, JSON.stringify(postObject), async (responseObj) => { + log.VERBOSE("Response Object from node ", selectedNode, responseObj); + if (!responseObj || !responseObj.statusCode == 200) return false; + // Node has connected to discord + // Updating node Object in DB + const updatedNode = await updateNodeInfo(selectedNode); + log.DEBUG("Updated Node: ", updatedNode); + + // Adding a new node connection + const nodeConnection = await addNodeConnection(selectedNode, selectedClientId); + log.DEBUG("Node Connection: ", nodeConnection); + }); + + return selectedClientId; +} +exports.joinServerWrapper = joinServerWrapper; + +/** + * + * @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"); + + const node = await checkNodeConnectionByClientId(clientIdObject); + + reqOptions = new requestOptions("/bot/leave", "POST", node.ip, node.port); + + const responseObj = await new Promise((recordResolve, recordReject) => { + sendHttpRequest(reqOptions, JSON.stringify({}), async (responseObj) => { + recordResolve(responseObj); + }); + }); + + log.VERBOSE("Response Object from node ", node, responseObj); + if (!responseObj || !responseObj.statusCode == 202) return false; + // Node has disconnected from discor + // Removing the node connection from the DB + const removedConnection = removeNodeConnectionByNodeId(node.id); + log.DEBUG("Removed Node Connection: ", removedConnection); + + return; +} +exports.leaveServerWrapper = leaveServerWrapper; \ No newline at end of file diff --git a/Server/controllers/nodesController.js b/Server/controllers/nodesController.js index c1999ed..fa7b622 100644 --- a/Server/controllers/nodesController.js +++ b/Server/controllers/nodesController.js @@ -192,7 +192,8 @@ 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); + await checkInWithNode(node); + res.sendStatus(200); } /** diff --git a/Server/public/res/js/node.js b/Server/public/res/js/node.js index a1530f4..41b4a4a 100644 --- a/Server/public/res/js/node.js +++ b/Server/public/res/js/node.js @@ -38,6 +38,48 @@ function addFrequencyInput(system){ document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent); } +function createToast(notificationMessage){ + 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(Date.now().toLocaleString())); + + const toastClose = document.createElement('button'); + toastClose.type = 'button'; + toastClose.classList.add('btn-close'); + toastClose.ariaLabel = 'Close'; + toastClose.setAttribute('data-bs-dismiss', 'toast'); + + 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.role ='alert'; + wrapperDiv.ariaLive = 'assertive'; + wrapperDiv.ariaAtomic = true; + wrapperDiv.appendChild(toastHeader); + wrapperDiv.appendChild(toastMessage); + + document.getElementById("toastZone").appendChild(wrapperDiv); + + $('.toast').toast('show'); +} + function checkInByNodeId(nodeId){ const Http = new XMLHttpRequest(); const url='/nodes/'+nodeId; @@ -46,5 +88,56 @@ function checkInByNodeId(nodeId){ Http.onreadystatechange = (e) => { console.log(Http.responseText) + createToast(Http.responseText); + } +} + +function joinServer(){ + const preset = document.getElementById("selectRadioPreset").value; + const nodeId = document.getElementById("nodeId").value; + const clientId = document.getElementById("inputDiscordClientId").value; + const channelId = document.getElementById("inputDiscordChannelId").value; + + const reqBody = { + 'preset': preset, + 'nodeId': nodeId, + 'clientId': clientId, + 'channelId': channelId + }; + + console.log(reqBody); + + const Http = new XMLHttpRequest(); + const url='/admin/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`); + $("#joinModal").modal('toggle'); + } +} + +function leaveServer(){ + const nodeId = document.getElementById("nodeId").value; + const reqBody = { + 'nodeId': nodeId + }; + + const Http = new XMLHttpRequest(); + const url='/admin/leave'; + 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} is leaving`); } } \ No newline at end of file diff --git a/Server/utilities/recordHelper.js b/Server/utilities/recordHelper.js index eb26ae3..2b68db3 100644 --- a/Server/utilities/recordHelper.js +++ b/Server/utilities/recordHelper.js @@ -118,7 +118,8 @@ class nodeObject { this.ip = _ip; this.port = _port; this.location = _location; - this.nearbySystems = _nearbySystems; + this.nearbySystems = _nearbySystems; + if (this.nearbySystems) this.presets = Object.keys(_nearbySystems); this.online = _online; } } diff --git a/Server/views/node.ejs b/Server/views/node.ejs index 05cd447..2a1bb49 100644 --- a/Server/views/node.ejs +++ b/Server/views/node.ejs @@ -9,7 +9,7 @@

-
+
@@ -26,9 +26,9 @@ <% } %>
- Join Server + Join Server - Leave Server + Leave Server Check-in with Node
@@ -149,6 +149,7 @@
+ <%- include('partials/joinModal.ejs', {'nearbySystems': node.nearbySystems}) %> <%- include('partials/bodyEnd.ejs') %> <%- include('partials/htmlFooter.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/htmlHead.ejs b/Server/views/partials/htmlHead.ejs index 5a3ef3b..9641f52 100644 --- a/Server/views/partials/htmlHead.ejs +++ b/Server/views/partials/htmlHead.ejs @@ -2,5 +2,6 @@ <%- include('head.ejs') %> +
<%- include('navbar.ejs') %> <%- include('sidebar.ejs') %> \ No newline at end of file diff --git a/Server/views/partials/joinModal.ejs b/Server/views/partials/joinModal.ejs new file mode 100644 index 0000000..d912911 --- /dev/null +++ b/Server/views/partials/joinModal.ejs @@ -0,0 +1,44 @@ + \ No newline at end of file