Semi functional client webUI
- can update client info for sure - Working on notifications now
This commit is contained in:
183
Client/public/res/css/main.css
Normal file
183
Client/public/res/css/main.css
Normal file
@@ -0,0 +1,183 @@
|
||||
.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: 95vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 5vh;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
400
Client/public/res/js/node.js
Normal file
400
Client/public/res/js/node.js
Normal file
@@ -0,0 +1,400 @@
|
||||
$(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;
|
||||
// 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.onclick = () => { removeFrequencyInput(`${system}_systemFreqRow_${inputId}`) }
|
||||
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 = `${system}_systemFreq_${inputId}`;
|
||||
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.id = `${system}_systemFreqRow_${inputId}`
|
||||
colParent.appendChild(childRow);
|
||||
|
||||
document.getElementById(`frequencyRow_${system.replaceAll(" ", "_")}`).appendChild(colParent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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');
|
||||
toastHeader.appendChild(toastTitle);
|
||||
toastHeader.appendChild(toastTime);
|
||||
toastHeader.appendChild(toastClose);
|
||||
|
||||
const toastMessage = document.createElement('p');
|
||||
toastMessage.classList.add("px-2");
|
||||
toastMessage.appendChild(document.createTextNode(notificationMessage));
|
||||
|
||||
const toastBody = document.createElement('div');
|
||||
toastBody.classList.add('toast-body');
|
||||
toastBody.appendChild(toastMessage);
|
||||
|
||||
const wrapperDiv = document.createElement('div');
|
||||
wrapperDiv.classList.add('toast');
|
||||
//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);
|
||||
addToastToStorage(time.toISOString(), notificationMessage);
|
||||
if (showNow) {
|
||||
const toastElement = bootstrap.Toast.getOrCreateInstance(document.getElementById(`${time.toISOString()}-toast`));
|
||||
toastElement.show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
function sendNodeHeartbeat(nodeId) {
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/client/requestCheckIn' + nodeId;
|
||||
Http.open("GET", url);
|
||||
Http.send();
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
console.log(Http.responseText)
|
||||
createToast(Http.responseText, { showNow: true });
|
||||
}
|
||||
}
|
||||
|
||||
function joinServer() {
|
||||
const preset = document.getElementById("selectRadioPreset").value;
|
||||
const clientId = document.getElementById("inputDiscordClientId").value;
|
||||
const channelId = document.getElementById("inputDiscordChannelId").value;
|
||||
|
||||
const reqBody = {
|
||||
'preset': preset,
|
||||
'clientId': clientId,
|
||||
'channelId': channelId
|
||||
};
|
||||
|
||||
console.log(reqBody);
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/bot/join';
|
||||
Http.open("POST", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
const responseObject = JSON.parse(Http.responseText)
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`${responseObject.name} will join shortly`, { showNow: true });
|
||||
$("#joinModal").modal('toggle');
|
||||
}
|
||||
}
|
||||
|
||||
function leaveServer() {
|
||||
const reqBody = {};
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/bot/leave';
|
||||
Http.open("POST", 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(`${responseObject} is leaving`, { showNow: true });
|
||||
}
|
||||
}
|
||||
|
||||
function saveNodeDetails() {
|
||||
const nodeName = document.getElementById("inputNodeName").value;
|
||||
const nodeIp = document.getElementById("inputNodeIp").value;
|
||||
const nodePort = document.getElementById("inputOrgName").value;
|
||||
const nodeLocation = document.getElementById("inputNodeLocation").value;
|
||||
|
||||
const reqBody = {
|
||||
'name': nodeName,
|
||||
'ip': nodeIp,
|
||||
'port': nodePort,
|
||||
'location': nodeLocation
|
||||
}
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/client';
|
||||
Http.open("PUT", url);
|
||||
Http.setRequestHeader("Content-Type", "application/json");
|
||||
Http.send(JSON.stringify(reqBody));
|
||||
|
||||
Http.onloadend = (e) => {
|
||||
const responseObject = JSON.parse(Http.responseText);
|
||||
console.log(Http.status);
|
||||
console.log(responseObject);
|
||||
createToast(`Node Updated!`);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function addNewSystem() {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
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) {
|
||||
systemFreqs.push(inputFreq.value);
|
||||
}
|
||||
|
||||
const reqBody = {
|
||||
'systemName': systemName,
|
||||
'mode': systemMode,
|
||||
'frequencies': systemFreqs
|
||||
}
|
||||
if (reqBody.mode == "p25") reqBody.trunkFile = 'none';
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = "/client/addPreset";
|
||||
Http.open("POST", 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} Added!`);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function updateSystem(systemName) {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
const systemMode = document.getElementById(`${systemName}_systemMode`).value;
|
||||
const inputSystemFreqs = $(`[id^="${systemName}_systemFreq_"]`);
|
||||
let systemFreqs = [];
|
||||
for (const inputFreq of inputSystemFreqs) {
|
||||
systemFreqs.push(inputFreq.value);
|
||||
}
|
||||
|
||||
const reqBody = {
|
||||
'systemName': systemName,
|
||||
'mode': systemMode,
|
||||
'frequencies': systemFreqs
|
||||
}
|
||||
if (reqBody.mode == "p25") reqBody.trunkFile = 'none';
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = "/client/updatePreset";
|
||||
Http.open("POST", 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} Updated!`);
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
function removeSystem(systemName) {
|
||||
const nodeId = document.getElementById("nodeId").value;
|
||||
|
||||
const reqBody = {
|
||||
'systemName': systemName,
|
||||
}
|
||||
|
||||
console.log("Request Body: ", reqBody);
|
||||
const Http = new XMLHttpRequest();
|
||||
const url = '/client/removePreset';
|
||||
Http.open("POST", 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() {
|
||||
|
||||
}
|
||||
|
||||
function removeFrequencyInput(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
element.remove();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
Reference in New Issue
Block a user