Finalizing Server webUI
Still needed: - Way to update clients' versions - Way to delete nodes - working dashboard - working search function
This commit is contained in:
@@ -98,4 +98,4 @@ log.DEBUG(`Starting HTTP Server`);
|
||||
runHTTPServer();
|
||||
|
||||
log.DEBUG("Checking in with the master server")
|
||||
checkIn();
|
||||
checkIn(true);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"},"coppies":{"frequencies":[154690000],"mode":"nbfm","trunkFile":"none"},"poopoo":{"frequencies":[479135500],"mode":"nbfm","trunkFile":"none"},"ppeeeeeeeeee":{"frequencies":[479135500,133990000,133000000,555999000],"mode":"p25","trunkFile":"none"}}
|
||||
{"Westchester Cty. Simulcast":{"frequencies":[470575000,470375000,470525000,470575000,470550000],"mode":"p25","trunkFile":"trunk.tsv"},"coppies":{"frequencies":[154690000],"mode":"nbfm","trunkFile":"none"},"asdadadadasd":{"frequencies":[123321000],"mode":"nbfm","trunkFile":"none"}}
|
||||
@@ -5,13 +5,14 @@ const log = new DebugBuilder("client", "clientController");
|
||||
require('dotenv').config();
|
||||
const modes = require("../config/modes");
|
||||
// Modules
|
||||
const { executeAsyncConsoleCommand, nodeObject, BufferToJson } = require("../utilities/utilities");
|
||||
const { executeAsyncConsoleCommand, BufferToJson } = require("../utilities/utilities");
|
||||
// Utilities
|
||||
const { updateId, updateConfig } = require("../utilities/updateConfig");
|
||||
const { getFullConfig } = require("../utilities/configHandler");
|
||||
const { updateId, updateConfig, updateClientConfig } = require("../utilities/updateConfig");
|
||||
const { updatePreset, addNewPreset, getPresets, removePreset } = require("../utilities/updatePresets");
|
||||
const { onHttpError, requestOptions, sendHttpRequest } = require("../utilities/httpRequests");
|
||||
|
||||
var runningClientConfig = new nodeObject({_id: process.env.CLIENT_ID, _ip: process.env.CLIENT_IP, _name: process.env.CLIENT_NAME, _port: process.env.CLIENT_PORT, _location: process.env.CLIENT_LOCATION, _nearbySystems: getPresets(), _online: process.env.CLIENT_ONLINE});
|
||||
var runningClientConfig = getFullConfig()
|
||||
|
||||
/**
|
||||
* Check the body for the required fields to update or add a preset
|
||||
@@ -97,12 +98,11 @@ exports.checkConfig = async function checkConfig() {
|
||||
* If the bot has a saved ID, check in with the server to get any updated information or just check back in
|
||||
* If the bot does not have a saved ID, it will attempt to request a new ID from the server
|
||||
*
|
||||
* @param {boolean} init If set to true, the client will update the server to it's config, instead of taking the server's config
|
||||
* @param {boolean} update If set to true, the client will update the server to it's config, instead of taking the server's config
|
||||
*/
|
||||
exports.checkIn = async (init = false) => {
|
||||
exports.checkIn = async (update = false) => {
|
||||
let reqOptions;
|
||||
await this.checkConfig();
|
||||
runningClientConfig.online = true;
|
||||
// Check if there is an ID found, if not add the node to the server. If there was an ID, check in with the server to make sure it has the correct information
|
||||
try {
|
||||
if (!runningClientConfig?.id || runningClientConfig.id == 0) {
|
||||
@@ -136,7 +136,7 @@ exports.checkIn = async (init = false) => {
|
||||
}
|
||||
else {
|
||||
// ID is in the config, checking in with the server
|
||||
if (init) reqOptions = new requestOptions(`/nodes/${runningClientConfig.id}`, "PUT");
|
||||
if (update) reqOptions = new requestOptions(`/nodes/${runningClientConfig.id}`, "PUT");
|
||||
else reqOptions = new requestOptions(`/nodes/nodeCheckIn/${runningClientConfig.id}`, "POST");
|
||||
sendHttpRequest(reqOptions, JSON.stringify(runningClientConfig), (responseObject) => {
|
||||
log.DEBUG("Check In Respose: ", responseObject);
|
||||
@@ -156,6 +156,8 @@ exports.checkIn = async (init = false) => {
|
||||
}
|
||||
if (responseObject.statusCode === 200) {
|
||||
// Server accepted the response but there were no keys to be updated
|
||||
const tempUpdatedConfig = updateClientConfig(responseObject.body);
|
||||
if (!tempUpdatedConfig.length > 0) return;
|
||||
}
|
||||
if (responseObject.statusCode >= 300) {
|
||||
// Server threw an error
|
||||
@@ -192,6 +194,7 @@ exports.updatePreset = async (req, res) => {
|
||||
checkBodyForPresetFields(req, res, () => {
|
||||
updatePreset(req.body.systemName, () => {
|
||||
runningClientConfig.nearbySystems = getPresets();
|
||||
this.checkIn(true);
|
||||
return res.sendStatus(200);
|
||||
}, {frequencies: req.body.frequencies, mode: req.body.mode, trunkFile: req.body.trunkFile});
|
||||
})
|
||||
@@ -204,6 +207,7 @@ exports.addNewPreset = async (req, res) => {
|
||||
checkBodyForPresetFields(req, res, () => {
|
||||
addNewPreset(req.body.systemName, req.body.frequencies, req.body.mode, () => {
|
||||
runningClientConfig.nearbySystems = getPresets();
|
||||
this.checkIn(true);
|
||||
return res.sendStatus(200);
|
||||
}, req.body.trunkFile);
|
||||
});
|
||||
@@ -217,6 +221,7 @@ exports.removePreset = async (req, res) => {
|
||||
if (!req.body.systemName) return res.status("500").json({"message": "You must specify a system name to delete, this must match exactly to how the system name is saved."})
|
||||
removePreset(req.body.systemName, () => {
|
||||
runningClientConfig.nearbySystems = getPresets();
|
||||
this.checkIn(true);
|
||||
return res.sendStatus(200);
|
||||
}, req.body.trunkFile);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,11 @@ const { requestCheckIn, getPresets, updatePreset, addNewPreset, removePreset, up
|
||||
*/
|
||||
router.get('/requestCheckIn', requestCheckIn);
|
||||
|
||||
/** GET Object of all known presets
|
||||
* Query the client to get all the known presets
|
||||
*/
|
||||
router.put('/', );
|
||||
|
||||
/** GET Object of all known presets
|
||||
* Query the client to get all the known presets
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
const { DebugBuilder } = require("../utilities/debugBuilder.js");
|
||||
const log = new DebugBuilder("client", "configController");
|
||||
// Modules
|
||||
const { nodeObject } = require("./utilities.js");
|
||||
const { getPresets } = require("../utilities/updatePresets");
|
||||
const { readFileSync } = require('fs');
|
||||
const path = require("path");
|
||||
require('dotenv').config();
|
||||
@@ -33,4 +35,8 @@ function getDeviceName(){
|
||||
log.DEBUG("Device Name: ", DeviceName);
|
||||
return DeviceName;
|
||||
}
|
||||
exports.getDeviceName = getDeviceID;
|
||||
exports.getDeviceName = getDeviceID;
|
||||
|
||||
exports.getFullConfig = () => {
|
||||
return new nodeObject({_id: process.env.CLIENT_ID, _ip: process.env.CLIENT_IP, _name: process.env.CLIENT_NAME, _port: process.env.CLIENT_PORT, _location: process.env.CLIENT_LOCATION, _nearbySystems: getPresets()});
|
||||
}
|
||||
@@ -9,7 +9,7 @@ const { isJsonString } = require("./utilities.js");
|
||||
|
||||
exports.requestOptions = class requestOptions {
|
||||
constructor(path, method, hostname = undefined, headers = undefined, port = undefined) {
|
||||
if (method === "POST"){
|
||||
if (["POST", "PUT"].includes(method)){
|
||||
log.VERBOSE("Hostname Vars: ", hostname, process.env.SERVER_HOSTNAME, process.env.SERVER_IP);
|
||||
if (hostname) this.hostname = hostname;
|
||||
if (process.env.SERVER_HOSTNAME) this.hostname = process.env.SERVER_HOSTNAME;
|
||||
|
||||
@@ -3,6 +3,7 @@ const { DebugBuilder } = require("../utilities/debugBuilder.js");
|
||||
const log = new DebugBuilder("client", "updateConfig");
|
||||
// Modules
|
||||
const replace = require('replace-in-file');
|
||||
const { getFullConfig } = require("./configHandler.js");
|
||||
|
||||
class Options {
|
||||
constructor(key, updatedValue) {
|
||||
@@ -23,6 +24,56 @@ exports.updateId = (updatedId) => {
|
||||
this.updateConfig('CLIENT_ID', updatedId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to update any or all keys in the client config
|
||||
*
|
||||
* @param {*} configObject Object with what keys you wish to update (node object format, will be converted)
|
||||
* @returns
|
||||
*/
|
||||
exports.updateClientConfig = (configObject) => {
|
||||
const runningConfig = getFullConfig();
|
||||
var updatedKeys = []
|
||||
const configKeys = Object.keys(configObject);
|
||||
|
||||
if (configKeys.includes("id")) {
|
||||
if (runningConfig.id != configObject.id) {
|
||||
this.updateConfig('CLIENT_ID', configObject.id);
|
||||
updatedKeys.push({'CLIENT_ID': configObject.id});
|
||||
log.DEBUG("Updated ID to: ", configObject.id);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("name")) {
|
||||
if (runningConfig.name != configObject.name) {
|
||||
this.updateConfig('CLIENT_NAME', configObject.name);
|
||||
updatedKeys.push({'CLIENT_NAME': configObject.name});
|
||||
log.DEBUG("Updated name to: ", configObject.name);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("ip")) {
|
||||
if (runningConfig.ip != configObject.ip) {
|
||||
this.updateConfig('CLIENT_IP', configObject.ip);
|
||||
updatedKeys.push({'CLIENT_IP': configObject.ip});
|
||||
log.DEBUG("Updated ip to: ", configObject.ip);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("port")) {
|
||||
if (runningConfig.port != configObject.port) {
|
||||
this.updateConfig('CLIENT_PORT', configObject.port);
|
||||
updatedKeys.push({'CLIENT_PORT': configObject.port});
|
||||
log.DEBUG("Updated port to: ", configObject.port);
|
||||
}
|
||||
}
|
||||
if (configKeys.includes("location")) {
|
||||
if (runningConfig.location != configObject.location) {
|
||||
this.updateConfig('CLIENT_LOCATION', configObject.location);
|
||||
updatedKeys.push({'CLIENT_LOCATION': configObject.location});
|
||||
log.DEBUG("Updated location to: ", configObject.location);
|
||||
}
|
||||
}
|
||||
|
||||
return updatedKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} key The config file key to update with the value
|
||||
@@ -37,7 +88,6 @@ exports.updateConfig = function updateConfig(key, value) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper to write changes to the file
|
||||
* @param options An instance of the Objects class specified to the key being updated
|
||||
|
||||
@@ -25,17 +25,15 @@ exports.nodeObject = class nodeObject {
|
||||
* @param {*} param0._ip The IP that the master can contact the node at
|
||||
* @param {*} param0._port The port that the client is listening on
|
||||
* @param {*} param0._location The physical location of the node
|
||||
* @param {*} param0._online An integer representation of the online status of the bot, ie 0=off, 1=on
|
||||
* @param {*} param0._nearbySystems An object array of nearby systems
|
||||
*/
|
||||
constructor({ _id = null, _name = null, _ip = null, _port = null, _location = null, _nearbySystems = null, _online = null }) {
|
||||
constructor({ _id = null, _name = null, _ip = null, _port = null, _location = null, _nearbySystems = null }) {
|
||||
this.id = _id;
|
||||
this.name = _name;
|
||||
this.ip = _ip;
|
||||
this.port = _port;
|
||||
this.location = _location;
|
||||
this.nearbySystems = _nearbySystems;
|
||||
this.online = _online;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ exports.leaveServer = async (req, res) => {
|
||||
const currentConnection = await getConnectionByNodeId(nodeId);
|
||||
log.DEBUG("Current Connection for node: ", currentConnection);
|
||||
|
||||
if (!currentConnection) return res.status(400).json("Node is not connected")
|
||||
|
||||
await leaveServerWrapper(currentConnection.clientObject)
|
||||
|
||||
return res.status(200).json(currentConnection.clientObject.name);
|
||||
|
||||
@@ -20,7 +20,7 @@ async function checkInWithNode(node) {
|
||||
sendHttpRequest(reqOptions, "", (responseObj) => {
|
||||
if (responseObj) {
|
||||
log.DEBUG("Response from: ", node.name, responseObj);
|
||||
const onlineNode = new nodeObject({ _online: false, _id: node.id });
|
||||
const onlineNode = new nodeObject({ _online: true, _id: node.id });
|
||||
log.DEBUG("Node update object: ", onlineNode);
|
||||
updateNodeInfo(onlineNode, (sqlResponse) => {
|
||||
if (!sqlResponse) this.log.ERROR("No response from SQL object");
|
||||
@@ -193,6 +193,39 @@ exports.updateNodeSystem = async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
/** Deletes a specific system/preset from a given node
|
||||
*
|
||||
* @param {*} req Default express req from router
|
||||
* @param {*} res Defualt express res from router
|
||||
* @param {*} req.params.nodeId The Node ID to update the preset/system on
|
||||
* @param {*} req.body.systemName The name of the system to update
|
||||
*/
|
||||
exports.removeNodeSystem = async (req, res) => {
|
||||
if (!req.params.nodeId) return res.status(400).json("No id specified");
|
||||
if (!req.body.systemName) return res.status(400).json("No system specified");
|
||||
log.DEBUG("Updating system for node: ", req.params.nodeId, req.body);
|
||||
getNodeInfoFromId(req.params.nodeId, (node) => {
|
||||
const reqOptions = new requestOptions("/client/removePreset", "POST", node.ip, node.port);
|
||||
const reqBody = {
|
||||
'systemName': req.body.systemName
|
||||
}
|
||||
|
||||
log.DEBUG("Request body for deleting preset: ", reqBody, reqOptions);
|
||||
sendHttpRequest(reqOptions, JSON.stringify(reqBody), async (responseObj) => {
|
||||
if(responseObj){
|
||||
// Good
|
||||
log.DEBUG("Response from deleting preset: ", reqBody, responseObj);
|
||||
return res.sendStatus(200)
|
||||
} else {
|
||||
// Bad
|
||||
log.DEBUG("No Response from deleting preset");
|
||||
return res.status(400).json("No Response from deleting preset, could be offline");
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/** Updates the information received from the client based on ID
|
||||
*
|
||||
* @param {*} req Default express req from router
|
||||
@@ -247,14 +280,14 @@ exports.updateExistingNode = async = (req, res) => {
|
||||
|
||||
if (!nodeInfo) {
|
||||
log.WARN("No existing node found with this ID, adding node: ", checkInObject);
|
||||
addNewNode(checkInObject, (newNode) => {
|
||||
this.requestNodeCheckIn({"params": {"nodeId": checkInObject.id}});
|
||||
addNewNode(checkInObject, async (newNode) => {
|
||||
await checkInWithNode(newNode);
|
||||
return res.status(201).json({ "updatedKeys": newNode });
|
||||
});
|
||||
}
|
||||
else {
|
||||
updateNodeInfo(checkInObject, () => {
|
||||
this.requestNodeCheckIn({"params": {"nodeId": checkInObject.id}});
|
||||
updateNodeInfo(checkInObject, async () => {
|
||||
await checkInWithNode(nodeInfo);
|
||||
return res.status(202).json({ "updatedKeys": checkInObject });
|
||||
});
|
||||
}
|
||||
@@ -291,7 +324,7 @@ exports.requestNodeCheckIn = async (req, res) => {
|
||||
const node = await getNodeInfoFromId(req.params.nodeId);
|
||||
if (!node) return res.status(400).json("No Node with the ID given");
|
||||
await checkInWithNode(node);
|
||||
res.sendStatus(200);
|
||||
if (res) res.sendStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,11 +1,119 @@
|
||||
$(document).ready(async () => {
|
||||
console.log("Loading stored notifications...");
|
||||
await loadStoredToasts();
|
||||
console.log("Showing stored notifications...");
|
||||
await showStoredToasts();
|
||||
});
|
||||
|
||||
/**
|
||||
* Gets all toasts stored in local storage
|
||||
*
|
||||
* @returns {Object} Object of toasts in storage
|
||||
*/
|
||||
function getStoredToasts() {
|
||||
if (localStorage.getItem("toasts")) {
|
||||
const storedToasts = JSON.parse(localStorage.getItem("toasts"));
|
||||
console.log("LOADED STORED TOASTS: ", storedToasts);
|
||||
navbarUpdateNotificationBellCount(storedToasts);
|
||||
return storedToasts;
|
||||
}
|
||||
else return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a toast to storage, will not allow duplicates
|
||||
*
|
||||
* @param {Date} time The date object from when the toast was created
|
||||
* @param {*} message The message of the toast
|
||||
*/
|
||||
function addToastToStorage(time, message) {
|
||||
var toasts = [{ 'time': time, 'message': message }]
|
||||
var storedToasts = getStoredToasts();
|
||||
console.log("Adding new notification to storage: ", toasts);
|
||||
toasts = toasts.concat(storedToasts);
|
||||
console.log("Combined new and stored notifications: ", toasts);
|
||||
toasts = toasts.filter((value, index, self) =>
|
||||
index === self.findIndex((t) => (
|
||||
t.time === value.time && t.message === value.message
|
||||
))
|
||||
)
|
||||
console.log("Deduped stored notifications: ", toasts);
|
||||
localStorage.setItem("toasts", JSON.stringify(toasts));
|
||||
navbarUpdateNotificationBellCount(toasts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a toast from the local storage
|
||||
*
|
||||
* @param {Date} time The date object from when the toast was created
|
||||
* @param {*} message The message of the toast
|
||||
*/
|
||||
function removeToastFromStorage(time, message) {
|
||||
const toastToRemove = { 'time': time, 'message': message }
|
||||
console.log("Toast to remove: ", toastToRemove);
|
||||
var toasts = getStoredToasts();
|
||||
console.log("Stored toasts: ", toasts);
|
||||
if (toasts.indexOf(toastToRemove)) toasts.splice(toasts.indexOf(toastToRemove) - 1, 1)
|
||||
console.log("Toasts with selected toast removed: ", toasts);
|
||||
localStorage.setItem("toasts", JSON.stringify(toasts));
|
||||
navbarUpdateNotificationBellCount(toasts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows all stored toasts
|
||||
*/
|
||||
function showStoredToasts() {
|
||||
const storedToasts = getStoredToasts();
|
||||
if (!storedToasts) return
|
||||
console.log("Loaded stored notifications to show: ", storedToasts);
|
||||
for (const toast of storedToasts) {
|
||||
const toastId = `${toast.time}-toast`;
|
||||
console.log("Showing stored toast: ", toast, toastId);
|
||||
const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(toastId));
|
||||
toastElement.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all toasts stored in the local storage into the DOM of the webpage
|
||||
*/
|
||||
function loadStoredToasts() {
|
||||
const storedToasts = getStoredToasts();
|
||||
if (!storedToasts) return
|
||||
console.log("Loaded stored notifications: ", storedToasts);
|
||||
for (const toast of storedToasts) {
|
||||
createToast(toast.message, { time: toast.time })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will update the count of notifications on the bell icon in the navbar
|
||||
*
|
||||
* @param {Array} storedToasts An array of stored toasts to be counted and updated in the navbar
|
||||
*/
|
||||
function navbarUpdateNotificationBellCount(storedToasts) {
|
||||
const notificationBellIcon = document.getElementById("navbar-notification-bell");
|
||||
var notificationBellCount = document.getElementById("notification-bell-icon-count");
|
||||
if (!notificationBellCount) {
|
||||
notificationBellCount = document.createElement('span');
|
||||
notificationBellCount.id = "notification-bell-icon-count";
|
||||
notificationBellCount.classList.add('badge');
|
||||
notificationBellCount.classList.add('text-bg-secondary');
|
||||
notificationBellCount.appendChild(document.createTextNode(storedToasts.length));
|
||||
}
|
||||
else notificationBellCount.innerHTML = storedToasts.length;
|
||||
|
||||
notificationBellIcon.appendChild(notificationBellCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a frequency input from the DOM
|
||||
*
|
||||
* @param {string} system The system name to add the frequency to
|
||||
* @param {string} inputId [OPTIONAL] The ID of input, this can be anything unique to this input. If this is not provided the number of frequencies will be used as the ID
|
||||
*/
|
||||
function addFrequencyInput(system, inputId = null){
|
||||
if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length;
|
||||
function addFrequencyInput(system, inputId = null) {
|
||||
if (!inputId) inputId = $(`[id^="${system}_systemFreqRow_"]`).length;
|
||||
// Create new input
|
||||
var icon = document.createElement('i');
|
||||
icon.classList.add('bi');
|
||||
@@ -16,7 +124,7 @@ function addFrequencyInput(system, inputId = null){
|
||||
remove.classList.add('align-middle');
|
||||
remove.classList.add('float-left');
|
||||
remove.href = '#'
|
||||
remove.onclick = () => {removeFrequencyInput(`${system}_systemFreqRow_${inputId}`)}
|
||||
remove.onclick = () => { removeFrequencyInput(`${system}_systemFreqRow_${inputId}`) }
|
||||
remove.appendChild(icon);
|
||||
|
||||
var childColRemoveIcon = document.createElement('div');
|
||||
@@ -31,7 +139,7 @@ function addFrequencyInput(system, inputId = null){
|
||||
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");
|
||||
@@ -47,19 +155,30 @@ function addFrequencyInput(system, inputId = null){
|
||||
document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent);
|
||||
}
|
||||
|
||||
function createToast(notificationMessage){
|
||||
/**
|
||||
* Add a toast element to the DOM
|
||||
*
|
||||
* @param {*} notificationMessage The message of the notification
|
||||
* @param {Date} param1.time The date object for when the toast was created, blank if creating new
|
||||
* @param {boolean} param1.showNow Show the toast now or just store it
|
||||
* @returns
|
||||
*/
|
||||
function createToast(notificationMessage, { time = undefined, showNow = false } = {}) {
|
||||
if (!time) time = new Date(Date.now());
|
||||
else time = new Date(Date.parse(time));
|
||||
const toastTitle = document.createElement('strong');
|
||||
toastTitle.classList.add('me-auto');
|
||||
toastTitle.appendChild(document.createTextNode("Server Notification"));
|
||||
|
||||
const toastTime = document.createElement('small');
|
||||
toastTime.appendChild(document.createTextNode(new Date(Date.now()).toLocaleString()));
|
||||
toastTime.appendChild(document.createTextNode(time.toLocaleString()));
|
||||
|
||||
const toastClose = document.createElement('button');
|
||||
toastClose.type = 'button';
|
||||
toastClose.classList.add('btn-close');
|
||||
toastClose.ariaLabel = 'Close';
|
||||
toastClose.setAttribute('data-bs-dismiss', 'toast');
|
||||
toastClose.onclick = () => { removeToastFromStorage(time.toISOString(), notificationMessage); };
|
||||
|
||||
const toastHeader = document.createElement('div');
|
||||
toastHeader.classList.add('toast-header');
|
||||
@@ -67,7 +186,6 @@ function createToast(notificationMessage){
|
||||
toastHeader.appendChild(toastTime);
|
||||
toastHeader.appendChild(toastClose);
|
||||
|
||||
|
||||
const toastMessage = document.createElement('p');
|
||||
toastMessage.classList.add("px-2");
|
||||
toastMessage.appendChild(document.createTextNode(notificationMessage));
|
||||
@@ -78,31 +196,39 @@ function createToast(notificationMessage){
|
||||
|
||||
const wrapperDiv = document.createElement('div');
|
||||
wrapperDiv.classList.add('toast');
|
||||
wrapperDiv.role ='alert';
|
||||
//wrapperDiv.classList.add('position-fixed');
|
||||
wrapperDiv.id = `${time.toISOString()}-toast`;
|
||||
wrapperDiv.role = 'alert';
|
||||
wrapperDiv.ariaLive = 'assertive';
|
||||
wrapperDiv.ariaAtomic = true;
|
||||
wrapperDiv.setAttribute('data-bs-delay', "7500");
|
||||
wrapperDiv.setAttribute('data-bs-animation', true);
|
||||
wrapperDiv.appendChild(toastHeader);
|
||||
wrapperDiv.appendChild(toastMessage);
|
||||
|
||||
document.getElementById("toastZone").appendChild(wrapperDiv);
|
||||
|
||||
$('.toast').toast('show');
|
||||
return $('.toast');
|
||||
addToastToStorage(time.toISOString(), notificationMessage);
|
||||
if (showNow) {
|
||||
const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(`${time.toISOString()}-toast`));
|
||||
toastElement.show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function sendNodeHeartbeat(nodeId){
|
||||
function sendNodeHeartbeat(nodeId) {
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/nodes/nodeCheckIn/'+nodeId;
|
||||
const url = '/nodes/nodeCheckIn/' + nodeId;
|
||||
Http.open("GET", url);
|
||||
Http.send();
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
console.log(Http.responseText)
|
||||
createToast(Http.responseText);
|
||||
console.log(Http.responseText)
|
||||
createToast(Http.responseText, { showNow: true });
|
||||
}
|
||||
}
|
||||
|
||||
function joinServer(){
|
||||
function joinServer() {
|
||||
const preset = document.getElementById("selectRadioPreset").value;
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
const clientId = document.getElementById("inputDiscordClientId").value;
|
||||
@@ -118,7 +244,7 @@ function joinServer(){
|
||||
console.log(reqBody);
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/admin/join';
|
||||
const url = '/admin/join';
|
||||
Http.open("POST", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
@@ -127,19 +253,19 @@ function joinServer(){
|
||||
const responseObject = JSON.parse(Http.responseText)
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${responseObject.name} will join shortly`);
|
||||
createToast(`${responseObject.name} will join shortly`, { showNow: true });
|
||||
$("#joinModal").modal('toggle');
|
||||
}
|
||||
}
|
||||
|
||||
function leaveServer(){
|
||||
function leaveServer() {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
const reqBody = {
|
||||
'nodeId': nodeId
|
||||
};
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/admin/leave';
|
||||
const url = '/admin/leave';
|
||||
Http.open("POST", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
@@ -148,8 +274,7 @@ function leaveServer(){
|
||||
const responseObject = JSON.parse(Http.responseText)
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${responseObject} is leaving`);
|
||||
setTimeout(() => {}, 45000);
|
||||
createToast(`${responseObject} is leaving`, { showNow: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +296,7 @@ function saveNodeDetails() {
|
||||
console.log("Request Body: ", reqBody);
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/nodes/'+nodeId;
|
||||
const url = '/nodes/' + nodeId;
|
||||
Http.open("PUT", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
@@ -181,15 +306,17 @@ function saveNodeDetails() {
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`Node Updated!`);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function addNewSystem(systemName) {
|
||||
function addNewSystem() {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
const systemMode = document.getElementById(`${systemName}_systemMode`).value;
|
||||
const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`);
|
||||
const systemName = document.getElementById(`New System_systemName`).value;
|
||||
const systemMode = document.getElementById(`New System_systemMode`).value;
|
||||
const inputSystemFreqs = $(`[id^="New System_systemFreq_"]`);
|
||||
let systemFreqs = [];
|
||||
for (const inputFreq of inputSystemFreqs){
|
||||
for (const inputFreq of inputSystemFreqs) {
|
||||
systemFreqs.push(inputFreq.value);
|
||||
}
|
||||
|
||||
@@ -201,13 +328,13 @@ function addNewSystem(systemName) {
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/nodes/'+nodeId+"/systems";
|
||||
const url = '/nodes/' + nodeId + "/systems";
|
||||
Http.open("POST", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
const responseObject = JSON.parse(Http.responseText)
|
||||
const responseObject = Http.responseText
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${systemName} Added!`);
|
||||
@@ -218,9 +345,9 @@ function addNewSystem(systemName) {
|
||||
function updateSystem(systemName) {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
const systemMode = document.getElementById(`${systemName}_systemMode`).value;
|
||||
const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`);
|
||||
const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`);
|
||||
let systemFreqs = [];
|
||||
for (const inputFreq of inputSystemFreqs){
|
||||
for (const inputFreq of inputSystemFreqs) {
|
||||
systemFreqs.push(inputFreq.value);
|
||||
}
|
||||
|
||||
@@ -232,13 +359,13 @@ function updateSystem(systemName) {
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url='/nodes/'+nodeId+"/systems";
|
||||
const url = '/nodes/' + nodeId + "/systems";
|
||||
Http.open("PUT", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
const responseObject = JSON.parse(Http.responseText)
|
||||
const responseObject = Http.responseText;
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${systemName} Updated!`);
|
||||
@@ -246,6 +373,28 @@ function updateSystem(systemName) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeSystem(systemName) {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
|
||||
const reqBody = {
|
||||
'systemName': systemName,
|
||||
}
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/nodes/' + nodeId + "/systems";
|
||||
Http.open("DELETE", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
Http.onloadend = (e) => {
|
||||
const responseObject = Http.responseText;
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${systemName} Removed!`);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function requestNodeUpdate() {
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ router.post('/:nodeId/systems', nodesController.addNodeSystem);
|
||||
// Update a system on an existing node
|
||||
router.put('/:nodeId/systems', nodesController.updateNodeSystem);
|
||||
|
||||
// Delete a system from an existing node
|
||||
router.delete('/:nodeId/systems', nodesController.removeNodeSystem);
|
||||
|
||||
// TODO Need to authenticate this request
|
||||
/* POST a new node to the server
|
||||
*
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<!-- Checkin with client button -->
|
||||
<a type="button" class="btn btn-secondary" href="#" onclick="sendNodeHeartbeat('<%=node.id%>')">Check-in with Node</a>
|
||||
<!-- Update Client button -->
|
||||
<a type="button" class="btn btn-warning" href="#" onclick="requestNodeUpdate('<%=node.id%>')">Update Node</a>
|
||||
<a type="button" class="btn btn-warning disabled" href="#" onclick="requestNodeUpdate('<%=node.id%>')">Update Node</a>
|
||||
</div>
|
||||
<hr>
|
||||
<form>
|
||||
@@ -107,7 +107,7 @@
|
||||
data-bs-target="#updateSystemModal_<%=system.replaceAll(" ", "_")%>">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
<a href="#" class="table-link text-danger label">
|
||||
<a class="table-link text-danger label" onclick="removeSystem('<%=system%>')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
@@ -135,5 +135,4 @@
|
||||
<% // new System Modal %>
|
||||
<%- include("partials/modifySystemModal.ejs", {'system': "New System", 'frequencies': [], 'mode': ''}) %>
|
||||
<%- include('partials/bodyEnd.ejs') %>
|
||||
<script src="/res/js/node.js"></script>
|
||||
<%- include('partials/htmlFooter.ejs') %>
|
||||
@@ -9,4 +9,6 @@
|
||||
|
||||
<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>
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="/res/js/node.js"></script>
|
||||
@@ -1,7 +1,6 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
<%- include('head.ejs') %>
|
||||
<body>
|
||||
<div class="toast-container mx-2" id="toastZone"></div>
|
||||
<body>
|
||||
<%- include('navbar.ejs') %>
|
||||
<%- include('sidebar.ejs') %>
|
||||
@@ -12,7 +12,7 @@
|
||||
<form>
|
||||
<div class="row gx-3 mb-3">
|
||||
<label class="small mb-1 fs-6" for="systemName">System Name</label>
|
||||
<input class="form-control" id="systemName" type="text" value="<%if (!system == "New System") {%><%= system %><%} else {%>Local Radio System<%}%>"></input>
|
||||
<input class="form-control" id="<%=system%>_systemName" type="text" value="<%if (!system == "New System") {%><%= system %><%} else {%>Local Radio System<%}%>"></input>
|
||||
</div>
|
||||
<div class="row gx-3 mb-3" id="frequencyRow_<%=system.replaceAll(" ", "_")%>">
|
||||
<label class="small mb-1 fs-6" for="systemFreq">Frequencies</label>
|
||||
@@ -54,7 +54,7 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="location.reload()">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" <%if(!system == "New System") {%>onclick="updateSystem('<%=system%>')"<%} else {%>onclick="addNewSystem('<%=system%>')"<%}%>>Save changes</button>
|
||||
<button type="button" class="btn btn-primary" <%if(!system == "New System") {%>onclick="updateSystem('<%=system%>')"<%} else {%>onclick="addNewSystem('<%=system%>')"<%}%>>Save changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
<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>
|
||||
*/%>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="navbar-notification-bell" onclick="showStoredToasts()"><i class="bi bi-bell-fill"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="d-flex" role="search">
|
||||
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<div class="container-fluid mt-5">
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative">
|
||||
<!-- Position it: -->
|
||||
<!-- - `.toast-container` for spacing between toasts -->
|
||||
<!-- - `top-0` & `end-0` to position the toasts in the upper right corner -->
|
||||
<!-- - `.p-3` to prevent the toasts from sticking to the edge of the container -->
|
||||
<div class="toast-container top-0 end-0 p-3 max" id="toastZone">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-nowrap">
|
||||
<div class="col-auto col-md-3 col-xl-2 px-sm-2 px-0 bg-dark sidebar-container">
|
||||
<div
|
||||
@@ -34,6 +42,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
*/%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col py-3">
|
||||
<div class="col py-3">
|
||||
|
||||
Reference in New Issue
Block a user