Compare commits
7 Commits
60b6eb7cda
...
e522326576
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e522326576 | ||
|
|
2e22fa66a6 | ||
|
|
6b37a48061 | ||
|
|
5d6d86fa47 | ||
|
|
c35d3f3fa7 | ||
|
|
c38bca4144 | ||
|
|
e6332dffc9 |
@@ -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,9 +14,10 @@ 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) => {
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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,25 +18,8 @@ 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(onlineNodes);
|
||||
});
|
||||
|
||||
return callback(systems);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -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
|
||||
*
|
||||
@@ -88,42 +56,38 @@ 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.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.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);
|
||||
}
|
||||
@@ -2,18 +2,65 @@
|
||||
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 refreshInterval = process.env.NODE_MONITOR_REFRESH_INTERVAL ?? 1200000;
|
||||
|
||||
/**
|
||||
* Check in with a singular node, mark it offline if it's offline and
|
||||
*
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @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
|
||||
* @param {*} res Defualt express res from router
|
||||
*/
|
||||
exports.listAllNodes = async (req, res) => {
|
||||
getAllNodes((allNodes) => {
|
||||
@@ -23,7 +70,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");
|
||||
|
||||
@@ -40,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) {
|
||||
@@ -53,7 +104,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 +116,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) => {
|
||||
@@ -112,27 +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 {
|
||||
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.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
|
||||
* @param {*} req Default express req from router
|
||||
* @param {*} res Defualt express res from router
|
||||
*/
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,46 +203,24 @@ exports.nodeMonitorService = class nodeMonitorService {
|
||||
this.log = new DebugBuilder("server", "nodeMonitorService");
|
||||
}
|
||||
|
||||
async start(){
|
||||
/**
|
||||
* 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));
|
||||
await new Promise(resolve => setTimeout(resolve, refreshInterval / 10));
|
||||
|
||||
log.INFO("Starting Node Monitor Service");
|
||||
// Check in before starting the infinite loop
|
||||
await this.checkInWithOnlineNodes();
|
||||
await checkInWithOnlineNodes();
|
||||
|
||||
|
||||
while(true){
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
176
Server/public/res/css/main.css
Normal file
176
Server/public/res/css/main.css
Normal file
@@ -0,0 +1,176 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
3
Server/public/res/js/controller.js
Normal file
3
Server/public/res/js/controller.js
Normal file
@@ -0,0 +1,3 @@
|
||||
function sendNodeHeartbeat(nodeId) {
|
||||
console.log(nodeId);
|
||||
}
|
||||
50
Server/public/res/js/node.js
Normal file
50
Server/public/res/js/node.js
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -119,7 +119,6 @@ class nodeObject {
|
||||
this.port = _port;
|
||||
this.location = _location;
|
||||
this.nearbySystems = _nearbySystems;
|
||||
if (this.nearbySystems) this.presets = Object.keys(_nearbySystems);
|
||||
this.online = _online;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
11
Server/views/controller.ejs
Normal file
11
Server/views/controller.ejs
Normal file
@@ -0,0 +1,11 @@
|
||||
<%- include('partials/htmlHead.ejs') %>
|
||||
<div class="container">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
||||
<% for(const node of nodes) {%>
|
||||
<%- include('partials/nodeCard.ejs', {'node': node}) %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/bodyEnd.ejs') %>
|
||||
<script src="/res/js/controller.js"></script>
|
||||
<%- include('partials/htmlFooter.ejs') %>
|
||||
@@ -1,11 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
<%- include('partials/htmlHead.ejs') %>
|
||||
<div class="container">
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-4">
|
||||
<div class="col-xl-3 col-lg-6">
|
||||
<div class="info-card l-bg-cherry">
|
||||
<div class="card-statistic p-4">
|
||||
<div class="card-icon card-icon-large"><i class="bi bi-cart"></i></div>
|
||||
<div class="mb-4">
|
||||
<h5 class="card-title mb-0">New Orders</h5>
|
||||
</div>
|
||||
<div class="row align-items-center mb-2 d-flex">
|
||||
<div class="col-8">
|
||||
<h2 class="d-flex align-items-center mb-0">
|
||||
3,243
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
<span>12.5% <i class="fa fa-arrow-up"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress mt-1 " data-height="8" style="height: 8px;">
|
||||
<div class="progress-bar l-bg-cyan" role="progressbar" data-width="25%" aria-valuenow="25"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width: 25%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-lg-6">
|
||||
<div class="info-card l-bg-blue-dark">
|
||||
<div class="card-statistic p-4">
|
||||
<div class="card-icon card-icon-large"><i class="bi bi-cart"></i></div>
|
||||
<div class="mb-4">
|
||||
<h5 class="card-title mb-0">New Orders</h5>
|
||||
</div>
|
||||
<div class="row align-items-center mb-2 d-flex">
|
||||
<div class="col-8">
|
||||
<h2 class="d-flex align-items-center mb-0">
|
||||
3,243
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-4 text-right">
|
||||
<span>12.5% <i class="fa fa-arrow-up"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress mt-1 " data-height="8" style="height: 8px;">
|
||||
<div class="progress-bar l-bg-cyan" role="progressbar" data-width="25%" aria-valuenow="25"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width: 25%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-sm-6">
|
||||
<div class="card node-card">
|
||||
<div class="card-body">
|
||||
<div class="dropdown float-end">
|
||||
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button"
|
||||
data-bs-toggle="dropdown" aria-haspopup="true"><i
|
||||
class="bx bx-dots-horizontal-rounded"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-end"><a class="dropdown-item" href="#">Edit</a><a
|
||||
class="dropdown-item" href="#">Action</a><a class="dropdown-item"
|
||||
href="#">Remove</a></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div><img src="https://bootdey.com/img/Content/avatar/avatar1.png" alt=""
|
||||
class="avatar-md rounded-circle img-thumbnail" /></div>
|
||||
<div class="flex-1 ms-3">
|
||||
<h5 class="font-size-16 mb-1"><a href="#" class="text-dark">Phyllis Gatlin</a></h5>
|
||||
<span class="badge badge-soft-success mb-0">Full Stack Developer</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-1">
|
||||
<p class="text-muted mb-0"><i
|
||||
class="mdi mdi-phone font-size-15 align-middle pe-2 text-primary"></i> 070 2860 5375
|
||||
</p>
|
||||
<p class="text-muted mb-0 mt-2"><i
|
||||
class="mdi mdi-email font-size-15 align-middle pe-2 text-primary"></i>
|
||||
PhyllisGatlin@spy.com</p>
|
||||
<p class="text-muted mb-0 mt-2"><i
|
||||
class="mdi mdi-google-maps font-size-15 align-middle pe-2 text-primary"></i> 52
|
||||
Ilchester MYBSTER 9WX</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-sm-6">
|
||||
<div class="card node-card">
|
||||
<div class="card-body">
|
||||
<div class="dropdown float-end">
|
||||
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button"
|
||||
data-bs-toggle="dropdown" aria-haspopup="true"><i
|
||||
class="bx bx-dots-horizontal-rounded"></i></a>
|
||||
<div class="dropdown-menu dropdown-menu-end"><a class="dropdown-item" href="#">Edit</a><a
|
||||
class="dropdown-item" href="#">Action</a><a class="dropdown-item"
|
||||
href="#">Remove</a></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div><img src="https://bootdey.com/img/Content/avatar/avatar5.png" alt=""
|
||||
class="avatar-md rounded-circle img-thumbnail" /></div>
|
||||
<div class="flex-1 ms-3">
|
||||
<h5 class="font-size-16 mb-1"><a href="#" class="text-dark">Diana Owens</a></h5>
|
||||
<span class="badge badge-soft-danger mb-0">UI/UX Designer</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-1">
|
||||
<p class="text-muted mb-0"><i
|
||||
class="mdi mdi-phone font-size-15 align-middle pe-2 text-primary"></i> 087 6321 3235
|
||||
</p>
|
||||
<p class="text-muted mb-0 mt-2"><i
|
||||
class="mdi mdi-email font-size-15 align-middle pe-2 text-primary"></i>
|
||||
DianaOwens@spy.com</p>
|
||||
<p class="text-muted mb-0 mt-2"><i
|
||||
class="mdi mdi-google-maps font-size-15 align-middle pe-2 text-primary"></i> 52
|
||||
Ilchester MYBSTER 9WX</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 pt-4">
|
||||
<button type="button" class="btn btn-soft-primary btn-sm w-50"><i
|
||||
class="bx bx-user me-1"></i> Profile</button>
|
||||
<button type="button" class="btn btn-primary btn-sm w-50"><i
|
||||
class="bx bx-message-square-dots me-1"></i> Contact</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/bodyEnd.ejs') %>
|
||||
|
||||
<%- include('partials/htmlFooter.ejs') %>
|
||||
154
Server/views/node.ejs
Normal file
154
Server/views/node.ejs
Normal file
@@ -0,0 +1,154 @@
|
||||
<%- include('partials/htmlHead.ejs') %>
|
||||
<div class="container">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<p>
|
||||
<span class="fs-2 fw-semibold">
|
||||
Node Details
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="row gx-3 mb-3">
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="small mb-1" for="nodeId">Node ID (this is the assigned Node ID and cannot be
|
||||
changed)</label>
|
||||
<input class="form-control" id="nodeId" type="text" value="<%=node.id%>" disabled></input>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="small mb-1" for="nodeStatus">Online Status:</label>
|
||||
<% if(node.online){%> <span class="badge badge-soft-success mb-0 align-middle fs-6" id="nodeStatus">Online</span>
|
||||
<% } else {%> <span class="badge badge-soft-danger mb-0 align-middle fs-6">Offline</span>
|
||||
<% } %>
|
||||
<hr>
|
||||
<!-- Join Server button-->
|
||||
<a type="button" class="btn btn-info <% if(!node.online) { %>disabled<% } %>" href="/join/<%=node.id%>">Join Server</a>
|
||||
<!-- Leave Server button -->
|
||||
<a type="button" class="btn btn-danger <% if(!node.online) { %>disabled<% } %>" href="/leave/<%=node.id%>">Leave Server</a>
|
||||
<!-- Checkin with client button -->
|
||||
<a type="button" class="btn btn-secondary" href="#" onclick="checkInByNodeId('<%=node.id%>')">Check-in with Node</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row gx-3 mb-3">
|
||||
<div class="col-md-12">
|
||||
<label class="small mb-1" for="inputNodeName">Node Name:</label>
|
||||
<input class="form-control" id="inputNodeName" type="text" value="<%=node.name%>"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row gx-3 mb-3">
|
||||
<div class="col-md-4">
|
||||
<label class="small mb-1" for="inputNodeIp">Node IP Address (that the server can
|
||||
contact):</label>
|
||||
<input class="form-control" id="inputNodeIp" type="text" value="<%=node.ip%>"></input>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="small mb-1" for="inputOrgName">Node Port (with the API):</label>
|
||||
<input class="form-control" id="inputOrgName" type="number" value="<%=node.port%>"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="small mb-1" for="inputNodeLocation">Node Location (physical location):</label>
|
||||
<input class="form-control" id="inputNodeLocation" type="location" value="<%=node.location%>"></input>
|
||||
</div>
|
||||
<h4>
|
||||
Nearby Systems
|
||||
</h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="main-box no-header clearfix">
|
||||
<div class="main-box-body clearfix">
|
||||
<div class="table-responsive">
|
||||
<table class="table user-list <% if(!node.online) { %>disabled<% } %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><span>System Name</span></th>
|
||||
<th><span>Frequencies</span></th>
|
||||
<th><span>Protocol</span></th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for(const system in node.nearbySystems){ %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= system %>
|
||||
</td>
|
||||
<td>
|
||||
<% if(node.nearbySystems[system].frequencies.length> 1) { %>
|
||||
<ul>
|
||||
<% for(const frequency of
|
||||
node.nearbySystems[system].frequencies) { %>
|
||||
<li>
|
||||
<%=frequency%> MHz
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } else { const
|
||||
frequency=node.nearbySystems[system].frequencies[0]
|
||||
%>
|
||||
<%=frequency%> MHz
|
||||
<% } %>
|
||||
</td>
|
||||
<td>
|
||||
<span class="label label-default text-uppercase">
|
||||
<%= node.nearbySystems[system].mode %>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="table-link text-info label"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updateSystemModal_<%=system.replaceAll(" ", "_")%>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="#" class="table-link text-danger label">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% // Update system modal %>
|
||||
<%- include("partials/modifySystemModal.ejs", {'system': system, 'frequencies': node.nearbySystems[system].frequencies}) %>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Save changes button-->
|
||||
<button class="btn btn-primary <% if(!node.online) { %>disabled<% } %>" type="button">Save changes</button>
|
||||
<!-- Button trigger modal -->
|
||||
<button type="button" class="btn btn-primary float-right <% if(!node.online) { %>disabled<% } %>" data-bs-toggle="modal"
|
||||
data-bs-target="#newSystemModal">Add New System</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% // new System Modal %>
|
||||
<div class="modal fade" id="newSystemModal" tabindex="-1" aria-labelledby="newSystemModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="newSystemModal">Add a New System</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
...
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('partials/bodyEnd.ejs') %>
|
||||
<script src="/res/js/node.js"></script>
|
||||
<%- include('partials/htmlFooter.ejs') %>
|
||||
12
Server/views/partials/bodyEnd.ejs
Normal file
12
Server/views/partials/bodyEnd.ejs
Normal file
@@ -0,0 +1,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"
|
||||
integrity="sha512-3gJwYpMe3QewGELv8k/BX9vcqhryRdzRMxVfq6ngyWXwo03GFEzjsUm8Q7RZcHPHksttq7/GFoxjCVUjkjvPdw=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
9
Server/views/partials/head.ejs
Normal file
9
Server/views/partials/head.ejs
Normal file
@@ -0,0 +1,9 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Bootstrap demo</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/res/css/main.css">
|
||||
</head>
|
||||
1
Server/views/partials/htmlFooter.ejs
Normal file
1
Server/views/partials/htmlFooter.ejs
Normal file
@@ -0,0 +1 @@
|
||||
</html>
|
||||
6
Server/views/partials/htmlHead.ejs
Normal file
6
Server/views/partials/htmlHead.ejs
Normal file
@@ -0,0 +1,6 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
<%- include('head.ejs') %>
|
||||
<body>
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('sidebar.ejs') %>
|
||||
54
Server/views/partials/modifySystemModal.ejs
Normal file
54
Server/views/partials/modifySystemModal.ejs
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="modal fade" id="updateSystemModal_<%=system.replaceAll(" ", "_")%>" tabindex="-1" aria-labelledby="updateSystemModal_<%=system.replaceAll(" ", "_")%>"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="updateSystemModal_<%=system.replaceAll(" ", "_")%>">Update <%=system%></h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="row gx-3 mb-3" id="frequencyRow_<%=system.replaceAll(" ", "_")%>">
|
||||
<label class="small mb-1 fs-6" for="nodeFreq">Frequencies</label>
|
||||
<% for(const frequency of frequencies) { %>
|
||||
<div class="col-md-6 mb-1">
|
||||
<div class="row px-1">
|
||||
<div class="col-10">
|
||||
<input class="form-control" id="nodeFreq" type="text" value="<%= frequency %>"></input>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<a class="align-middle float-left" href="#"><i class="bi bi-x-circle text-black"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<button type="button" class="btn btn-info text-white" onclick="addFrequencyInput('<%=system%>')">Add Frequency</button>
|
||||
<hr>
|
||||
<div class="row gx-3 mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="small mb-1 fs-6" for="modeSelect">Mode</label>
|
||||
<br>
|
||||
<select class="custom-select" id="modeSelect">
|
||||
<option value="<%= node.nearbySystems[system].mode %>" selected><span class="text-uppercase"><%= node.nearbySystems[system].mode %></span></option>
|
||||
<% if(node.nearbySystems[system].mode == "p25") { %>
|
||||
<option value="nbfm">NBFM</option>
|
||||
<% } else if (node.nearbySystems[system].mode == "nbfm") { %>
|
||||
<option value="p25">P25</option>
|
||||
<% } %>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
42
Server/views/partials/navbar.ejs
Normal file
42
Server/views/partials/navbar.ejs
Normal file
@@ -0,0 +1,42 @@
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary" data-bs-theme="dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Node Master</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<% /*
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="#">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Link</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
Dropdown
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">Action</a></li>
|
||||
<li><a class="dropdown-item" href="#">Another action</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled">Disabled</a>
|
||||
</li>
|
||||
*/%>
|
||||
</ul>
|
||||
<form class="d-flex" role="search">
|
||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
57
Server/views/partials/nodeCard.ejs
Normal file
57
Server/views/partials/nodeCard.ejs
Normal file
@@ -0,0 +1,57 @@
|
||||
<div class="col-xl-3 col-sm-6">
|
||||
<div class="card node-card">
|
||||
<div class="card-body">
|
||||
<div class="dropdown float-end">
|
||||
<a class="text-muted dropdown-toggle font-size-16" href="#" role="button" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true">
|
||||
<i class="bx bx-dots-horizontal-rounded"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a class="dropdown-item node-action" href="/node/<%=node.id%>" >Edit</a>
|
||||
<a class="dropdown-item node-action" href="#" onclick="sendNodeHeartbeat('<%=node.id%>')">Send Heartbeat</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<img src="https://bootdey.com/img/Content/avatar/avatar1.png" alt=""
|
||||
class="avatar-md rounded-circle img-thumbnail" />
|
||||
</div>
|
||||
<div class="flex-1 ms-3">
|
||||
<h5 class="font-size-16 mb-1"><a href="/node/<%=node.id%>" class="text-dark">
|
||||
<%= node.name %>
|
||||
</a></h5>
|
||||
<% if(node.online){%> <span class="badge badge-soft-success mb-0">Online</span>
|
||||
<% } else {%> <span class="badge badge-soft-danger mb-0">Offline</span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-1">
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-geo font-size-15 align-middle pe-2 text-primary"></i>
|
||||
<%= node.location %>
|
||||
</p>
|
||||
<p class="text-muted mb-0">
|
||||
<i class="bi bi-phone font-size-15 align-middle pe-2 text-primary"></i>
|
||||
<a href="#" target="_blank">
|
||||
<%= node.ip %>:<%= node.port %>
|
||||
</a>
|
||||
</p>
|
||||
<p class="text-muted mb-0">
|
||||
<div class="dropdown">
|
||||
<i class="bi bi-broadcast-pin font-size-15 align-middle pe-2 text-primary"></i>
|
||||
<a class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Nearby Systems
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<% for(const system in node.nearbySystems){ %>
|
||||
<li>
|
||||
<%= system %>
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
38
Server/views/partials/sidebar.ejs
Normal file
38
Server/views/partials/sidebar.ejs
Normal file
@@ -0,0 +1,38 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row flex-nowrap">
|
||||
<div class="col-auto col-md-3 col-xl-2 px-sm-2 px-0 bg-dark">
|
||||
<div class="d-flex flex-column align-items-center align-items-sm-start px-3 pt-2 text-white sidebar-container">
|
||||
<ul class="nav nav-pills flex-column mb-sm-auto mb-0 align-items-center align-items-sm-start" id="menu">
|
||||
<li>
|
||||
<a href="/" class="nav-link px-0 align-middle">
|
||||
<i class="fs-4 bi-speedometer2"></i> <span class="ms-1 d-none d-sm-inline">Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/controller" class="nav-link px-0 align-middle">
|
||||
<i class="fs-4 bi-grid"></i> <span class="ms-1 d-none d-sm-inline">Controller</span> </a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<% /*
|
||||
<div class="dropdown pb-4 fixed-bottom px-3">
|
||||
<a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle"
|
||||
id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<img src="https://github.com/mdo.png" alt="hugenerd" width="30" height="30"
|
||||
class="rounded-circle">
|
||||
<span class="d-none d-sm-inline mx-1">loser</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark text-small shadow">
|
||||
<li><a class="dropdown-item" href="#">New project...</a></li>
|
||||
<li><a class="dropdown-item" href="#">Settings</a></li>
|
||||
<li><a class="dropdown-item" href="#">Profile</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="#">Sign out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
*/%>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col py-3">
|
||||
Reference in New Issue
Block a user