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