From 57fa6be11030c7f0738d887ec82d79ef9145fb3a Mon Sep 17 00:00:00 2001
From: Logan Cusano
Date: Sat, 22 Jul 2023 03:34:37 -0400
Subject: [PATCH 01/27] Update Readmes (sort of)
---
Client/readme.md | 2 --
readme.md | 59 ++++++++++++++++++++++++++++--------------------
2 files changed, 35 insertions(+), 26 deletions(-)
diff --git a/Client/readme.md b/Client/readme.md
index ec2e6e0..77c2d40 100644
--- a/Client/readme.md
+++ b/Client/readme.md
@@ -7,8 +7,6 @@ Explanation here
## Requirements
---
-
-Requirements here (not modules, that will be installed with npm)
### Hardware
- SBC
diff --git a/readme.md b/readme.md
index e77d47b..de8bc5b 100644
--- a/readme.md
+++ b/readme.md
@@ -1,32 +1,43 @@
-# Discord Radio Bot: Command & Control
+# Project Overview
+
+This project is a multi-layered application consisting of client and server applications. Its main purpose is to enable the use of Software-Defined Radios (SDRs) and Raspberry Pi (or similar Single Board Computers) to listen to radio frequencies in Discord voice channels. The project is designed to provide a seamless integration between the SDR hardware and the server with Discord commands.
+
+## Server Application
+
+The server application acts as the central hub within Discord, providing various functionalities and serving as the main point of communication for the clients. Some of the key features and responsibilities of the server include:
+
+- **RSS Feed Updates**: The server periodically updates text channels with RSS feed updates, keeping users informed about the latest news or information.
+- **Server Management Functions / User Requests**: The server includes management functions that allow administrators to control and configure various aspects of the server environment. Users can interact with the server through Discord commands, which range from requesting specific radio presets to updating RSS feeds.
+- **API and Web Front End**: The server exposes an API and web front end, providing an interface to view and control all the online clients. This allows users to monitor and manage the available radio presets, as well as perform various administrative tasks.
+
+### Requirements
+#### Software
+
+### Installation
---
+## Client Application
-Project overview here
+The client application communicates with the server through the provided API. Each client instance waits for join requests sent by users through Discord. Once a join request is received, the client uses the SDR application to tune into the specified radio preset. It then establishes a connection to Discord, allowing users to listen to the selected radio preset in real-time.
-## Requirements Overview
+In addition to its interaction with the server, the client also has its own API and web application. This enables users to directly interface with the client, perform actions specific to the client application, and access relevant information about the connected SDR and radio presets.
----
+### Requirements
+#### Hardware
+- SBC
+ - [Orange Pi](https://www.amazon.com/dp/B0BN16ZLXB/r)
+ - [Raspberry Pi](https://www.canakit.com/raspberry-pi-4-4gb.html)
+ - [Rock Pi](https://www.okdo.com/us/p/okdo-rock-4-model-c-4gb-single-board-computer-rockchip-rk3399-t-arm-cortex-a72-cortex-a53/)
+- SDR
+ - [Nooelec RTL-SDR v5 Bundle ](https://www.amazon.com/dp/B01GDN1T4S)
+ - [RTL-SDR Blog V3](https://www.amazon.com/dp/B0BMKB3L47)
+ - [Nooelec NESDR Mini](https://www.amazon.com/dp/B009U7WZCA)
+- Proper Power Adapter (Sometimes comes in SBC Packs)
+- SD Card (Sometimes comes in SBC Packs)
-### Server Requirements
-#### Server: Discord Bot Requirements
-### Client Requirements
-#### Client: Discord Bot Requirements
+#### Software
-## Server
+### Installation
----
-
-Explanation and overview here
-
-## Client
-
----
-
-Explanation and overview here
-
-## Discord Bot
-
----
-
-Explanation and overview here
\ No newline at end of file
+## Troubleshooting
+Check the [wiki]()
From d05c266f75c132b9a7d9524a0ecdf27c24e16240 Mon Sep 17 00:00:00 2001
From: Logan Cusano
Date: Sat, 22 Jul 2023 03:51:46 -0400
Subject: [PATCH 02/27] Added basic info to readmes
---
Client/readme.md | 4 +++-
Server/readme.md | 6 +++++-
readme.md | 24 ++++--------------------
3 files changed, 12 insertions(+), 22 deletions(-)
diff --git a/Client/readme.md b/Client/readme.md
index 77c2d40..f90f0a4 100644
--- a/Client/readme.md
+++ b/Client/readme.md
@@ -2,7 +2,9 @@
---
-Explanation here
+The client application communicates with the server through the provided API. Each client instance waits for join requests sent by users through Discord. Once a join request is received, the client uses the SDR application to tune into the specified radio preset. It then establishes a connection to Discord, allowing users to listen to the selected radio preset in real-time.
+
+In addition to its interaction with the server, the client also has its own API and web application. This enables users to directly interface with the client, perform actions specific to the client application, and access relevant information about the connected SDR and radio presets.
## Requirements
diff --git a/Server/readme.md b/Server/readme.md
index 405f273..cfa5328 100644
--- a/Server/readme.md
+++ b/Server/readme.md
@@ -2,7 +2,11 @@
---
-Overview here
+The server application acts as the central hub within Discord, providing various functionalities and serving as the main point of communication for the clients. Some of the key features and responsibilities of the server include:
+
+- **RSS Feed Updates**: The server periodically updates text channels with RSS feed updates, keeping users informed about the latest news or information.
+- **Server Management Functions / User Requests**: The server includes management functions that allow administrators to control and configure various aspects of the server environment. Users can interact with the server through Discord commands, which range from requesting specific radio presets to updating RSS feeds.
+- **API and Web Front End**: The server exposes an API and web front end, providing an interface to view and control all the online clients. This allows users to monitor and manage the available radio presets, as well as perform various administrative tasks.
## Requirements
diff --git a/readme.md b/readme.md
index de8bc5b..424f2ba 100644
--- a/readme.md
+++ b/readme.md
@@ -10,10 +10,7 @@ The server application acts as the central hub within Discord, providing various
- **Server Management Functions / User Requests**: The server includes management functions that allow administrators to control and configure various aspects of the server environment. Users can interact with the server through Discord commands, which range from requesting specific radio presets to updating RSS feeds.
- **API and Web Front End**: The server exposes an API and web front end, providing an interface to view and control all the online clients. This allows users to monitor and manage the available radio presets, as well as perform various administrative tasks.
-### Requirements
-#### Software
-
-### Installation
+#### [Read more about the Server](https://git.vpn.cusano.net/logan/DRB-CnC/src/branch/master/Server)
---
## Client Application
@@ -22,22 +19,9 @@ The client application communicates with the server through the provided API. Ea
In addition to its interaction with the server, the client also has its own API and web application. This enables users to directly interface with the client, perform actions specific to the client application, and access relevant information about the connected SDR and radio presets.
-### Requirements
-#### Hardware
-- SBC
- - [Orange Pi](https://www.amazon.com/dp/B0BN16ZLXB/r)
- - [Raspberry Pi](https://www.canakit.com/raspberry-pi-4-4gb.html)
- - [Rock Pi](https://www.okdo.com/us/p/okdo-rock-4-model-c-4gb-single-board-computer-rockchip-rk3399-t-arm-cortex-a72-cortex-a53/)
-- SDR
- - [Nooelec RTL-SDR v5 Bundle ](https://www.amazon.com/dp/B01GDN1T4S)
- - [RTL-SDR Blog V3](https://www.amazon.com/dp/B0BMKB3L47)
- - [Nooelec NESDR Mini](https://www.amazon.com/dp/B009U7WZCA)
-- Proper Power Adapter (Sometimes comes in SBC Packs)
-- SD Card (Sometimes comes in SBC Packs)
+#### [Read more about the Client](https://git.vpn.cusano.net/logan/DRB-CnC/src/branch/master/Client)
-#### Software
-
-### Installation
+---
## Troubleshooting
-Check the [wiki]()
+Check the [wiki](https://git.vpn.cusano.net/logan/DRB-CnC/wiki)
From d4b974f81b2e28f3a35a0d252050525df07793f3 Mon Sep 17 00:00:00 2001
From: Logan Cusano
Date: Sat, 15 Jul 2023 17:58:12 -0400
Subject: [PATCH 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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') %>
+
+<%- 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 @@
+