Still needed: - Way to update clients' versions - Way to delete nodes - working dashboard - working search function
359 lines
14 KiB
JavaScript
359 lines
14 KiB
JavaScript
// Debug
|
|
const { DebugBuilder } = require("../utilities/debugBuilder.js");
|
|
const log = new DebugBuilder("server", "nodesController");
|
|
// Utilities
|
|
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 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
|
|
*
|
|
* @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: true, _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
|
|
* @param {*} res Defualt express res from router
|
|
*/
|
|
exports.listAllNodes = async (req, res) => {
|
|
getAllNodes((allNodes) => {
|
|
res.status(200).json({
|
|
"nodes_online": allNodes
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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");
|
|
|
|
try {
|
|
// Try to add the new user with defaults if missing options
|
|
const newNode = new nodeObject({
|
|
_name: req.body.name,
|
|
_ip: req.body.ip ?? null,
|
|
_port: req.body.port ?? null,
|
|
_location: req.body.location ?? null,
|
|
_nearbySystems: req.body.nearbySystems ?? null,
|
|
_online: (req.body.online == "true" || req.body.online == "True") ? true : false
|
|
});
|
|
|
|
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 });
|
|
})
|
|
}
|
|
catch (err) {
|
|
// Catch any errors
|
|
if (err === "No name provided") {
|
|
return res.sendStatus(400);
|
|
}
|
|
else log.ERROR(err)
|
|
return res.sendStatus(500);
|
|
}
|
|
}
|
|
|
|
/** 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.params.id) return res.status(400).json("No id specified");
|
|
getNodeInfoFromId(req.params.id, (nodeInfo) => {
|
|
res.status(200).json(nodeInfo);
|
|
})
|
|
}
|
|
|
|
/** Adds 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 add the preset/system to
|
|
* @param {*} req.body.systemName The name of the system to add
|
|
* @param {*} req.body.mode The radio mode of the preset
|
|
* @param {*} req.body.frequencies The frequencies of the preset
|
|
* @param {*} req.body.trunkFile The trunk file to use for digital stations
|
|
*/
|
|
exports.addNodeSystem = 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("Adding system for node: ", req.params.nodeId, req.body);
|
|
getNodeInfoFromId(req.params.nodeId, (node) => {
|
|
const reqOptions = new requestOptions("/client/addPreset", "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 adding node system: ", reqBody, reqOptions);
|
|
sendHttpRequest(reqOptions, JSON.stringify(reqBody), async (responseObj) => {
|
|
if(responseObj){
|
|
// Good
|
|
log.DEBUG("Response from adding node system: ", reqBody, responseObj);
|
|
return res.sendStatus(200)
|
|
} else {
|
|
// Bad
|
|
log.DEBUG("No Response from adding Node system");
|
|
return res.status(400).json("No Response from adding Node, could be offline");
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
/** 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 system: ", reqBody, responseObj);
|
|
return res.sendStatus(200)
|
|
} else {
|
|
// Bad
|
|
log.DEBUG("No Response from updating Node system");
|
|
return res.status(400).json("No Response from updating Node, could be offline");
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
/** Deletes a specific system/preset from 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
|
|
*/
|
|
exports.removeNodeSystem = 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/removePreset", "POST", node.ip, node.port);
|
|
const reqBody = {
|
|
'systemName': req.body.systemName
|
|
}
|
|
|
|
log.DEBUG("Request body for deleting preset: ", reqBody, reqOptions);
|
|
sendHttpRequest(reqOptions, JSON.stringify(reqBody), async (responseObj) => {
|
|
if(responseObj){
|
|
// Good
|
|
log.DEBUG("Response from deleting preset: ", reqBody, responseObj);
|
|
return res.sendStatus(200)
|
|
} else {
|
|
// Bad
|
|
log.DEBUG("No Response from deleting preset");
|
|
return res.status(400).json("No Response from deleting preset, could be offline");
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
|
|
/** 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.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);
|
|
|
|
var isObjectUpdated = false;
|
|
|
|
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;
|
|
isObjectUpdated = true;
|
|
}
|
|
|
|
if (req.body.port && req.body.port != nodeInfo.port) {
|
|
checkInObject._port = req.body.port;
|
|
isObjectUpdated = true;
|
|
}
|
|
|
|
if (req.body.location && req.body.location != nodeInfo.location) {
|
|
checkInObject._location = req.body.location;
|
|
isObjectUpdated = true;
|
|
}
|
|
|
|
if (req.body.nearbySystems && JSON.stringify(req.body.nearbySystems) !== JSON.stringify(nodeInfo.nearbySystems)) {
|
|
checkInObject._nearbySystems = req.body.nearbySystems;
|
|
isObjectUpdated = true;
|
|
}
|
|
|
|
if (req.body.online != nodeInfo.online || req.body.online && (req.body.online === "true") != nodeInfo.online) {
|
|
checkInObject._online = req.body.online;
|
|
isObjectUpdated = true;
|
|
}
|
|
|
|
// 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.params.nodeId, checkInObject);
|
|
|
|
checkInObject._id = req.params.nodeId;
|
|
checkInObject = new nodeObject(checkInObject);
|
|
|
|
if (!nodeInfo) {
|
|
log.WARN("No existing node found with this ID, adding node: ", checkInObject);
|
|
addNewNode(checkInObject, async (newNode) => {
|
|
await checkInWithNode(newNode);
|
|
return res.status(201).json({ "updatedKeys": newNode });
|
|
});
|
|
}
|
|
else {
|
|
updateNodeInfo(checkInObject, async () => {
|
|
await checkInWithNode(nodeInfo);
|
|
return res.status(202).json({ "updatedKeys": checkInObject });
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/** 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) return this.newNode(req, res);
|
|
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
|
|
*
|
|
* @param {*} req Default express req from router
|
|
* @param {*} res Defualt express res from router
|
|
*/
|
|
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");
|
|
await checkInWithNode(node);
|
|
if (res) res.sendStatus(200);
|
|
}
|
|
|
|
/**
|
|
* The node monitor service, this will periodically check in on the online nodes to make sure they are still online
|
|
*/
|
|
exports.nodeMonitorService = class nodeMonitorService {
|
|
constructor() {
|
|
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));
|
|
|
|
log.INFO("Starting Node Monitor Service");
|
|
// Check in before starting the infinite loop
|
|
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 checkInWithOnlineNodes();
|
|
await new Promise(resolve => setTimeout(resolve, refreshInterval / 4));
|
|
continue;
|
|
}
|
|
}
|
|
}
|