Gettign going

This commit is contained in:
Luke Berndt
2023-03-15 22:35:34 -04:00
parent 417539e3ea
commit 5bfff7bb90
34 changed files with 8327 additions and 9226 deletions

View File

@@ -11,7 +11,7 @@ var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.e
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com'; var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic'; var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic'; var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var dev_server = frontend_server + ":3000" var dev_server = "http://admin.openmhz.test:3000"
module.exports = function(app, passport) { module.exports = function(app, passport) {
app.set("port", 3009) app.set("port", 3009)

View File

@@ -14,10 +14,13 @@ module.exports = function (app, passport) {
done(null, user.id) done(null, user.id)
}) })
passport.deserializeUser((id, done) => { passport.deserializeUser(async (id, done) => {
User.findById(id, (err, user) => { const user = await User.findById(id).exec();
done(err, user) if (user) {
}) done(null,user);
} else {
done("User not found", null);
}
}) })
// use the following strategies // use the following strategies

View File

@@ -5,58 +5,52 @@ const System = require("../models/system");
// ------------------------------------------- // -------------------------------------------
exports.getUserSystems = function(_id) { exports.getUserSystems = async function (_id) {
console.log("Listing Systems for: " + _id); console.log("Listing Systems for: " + _id);
System.find( const userId = new mongoose.Types.ObjectId(req.user._id);
{ const systems = await System.find({ userId: userId });
userId: mongoose.Types.ObjectId(_id) if (systems == null) {
}, return ({ success: false, message: err });
function(err, systems) { }
if (err) {
console.error("Error - ListSystems: " + err); var returnSys = systems.map(obj => {
return({ success: false, message: err }); var rObj = (({
} name,
shortName,
description,
var returnSys = systems.map(obj => { systemType,
var rObj = (({ city,
name, state,
shortName, county,
description, country,
systemType, userId,
city, key,
state, planType,
county, showScreenName
country, }) => ({
userId, name,
key, shortName,
planType, description,
showScreenName systemType,
}) => ({ city,
name, state,
shortName, county,
description, country,
systemType, userId,
city, key,
state, planType,
county, showScreenName
country, }))(obj);
userId, rObj.id = obj._id;
key, return rObj;
planType, });
showScreenName
}))(obj);
rObj.id = obj._id; return ({ success: true, systems: returnSys });
return rObj;
});
return({ success: true, systems: returnSys });
}
);
} }
exports.listSystems = function(req, res, next) {
return listSystems(req.user._id); exports.listSystems = function (req, res, next) {
} return listSystems(req.user._id);
}

View File

@@ -1,5 +1,5 @@
# build environment # build environment
FROM node:13.12.0-alpine as build FROM node:19-alpine3.16 as build
WORKDIR /app WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH ENV PATH /app/node_modules/.bin:$PATH
ARG REACT_APP_BACKEND_SERVER ARG REACT_APP_BACKEND_SERVER
@@ -15,7 +15,7 @@ COPY . ./
RUN npm run build RUN npm run build
# production environment # production environment
FROM node:13.12.0-alpine FROM node:19-alpine3.16
RUN mkdir -p /app/public RUN mkdir -p /app/public
COPY ./package.json /tmp COPY ./package.json /tmp
RUN cd /tmp && npm install RUN cd /tmp && npm install

View File

@@ -3,41 +3,42 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@visx/axis": "1.4.0", "@lagunovsky/redux-react-router": "^4.3.0",
"@visx/curve": "1.0.0", "@visx/axis": "3.0.1",
"@visx/glyph": "1.0.0", "@visx/curve": "3.0.0",
"@visx/group": "1.0.0", "@visx/glyph": "3.0.0",
"@visx/legend": "1.4.0", "@visx/group": "3.0.0",
"@visx/responsive": "1.3.0", "@visx/legend": "3.0.0",
"@visx/scale": "1.4.0", "@visx/responsive": "3.0.0",
"@visx/shape": "1.4.0", "@visx/scale": "3.0.0",
"bcrypt": "5.0.0", "@visx/shape": "3.0.0",
"bcrypt": "5.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"connect-mongo": "3.2.0", "connect-mongo": "^4.6.0",
"csv-parse": "4.14.2", "csv-parse": "5.3.6",
"d3-time-format": "3.0.0", "d3-time-format": "4.1.0",
"express": "4.17.1", "express": "4.18.2",
"express-session": "1.17.1", "express-session": "1.17.3",
"mongoose": "5.11.11", "history": "^5.3.0",
"multer": "1.4.2", "mongoose": "7.0.0",
"passport": "0.4.1", "multer": "1.4.5-lts.1",
"passport": "0.6.0",
"passport-local": "1.0.0" "passport-local": "1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@reduxjs/toolkit": "^1.1.0", "@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^14.4.3",
"axios": "0.21.1", "axios": "1.3.4",
"connected-react-router": "6.8.0", "react": "^18.2.0",
"react": "^17.0.1", "react-dom": "^18.2.0",
"react-dom": "^17.0.1", "react-redux": "^8.0.5",
"react-redux": "^7.1.3", "react-router-dom": "6.8.2",
"react-router-dom": "5.2.0", "react-scripts": "5.0.1",
"react-scripts": "4.0.1", "redux-thunk": "2.4.2",
"redux-thunk": "2.3.0", "semantic-ui-css": "2.5.0",
"semantic-ui-css": "2.4.1", "semantic-ui-react": "2.1.4"
"semantic-ui-react": "2.0.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@@ -2,16 +2,15 @@ const path = require("path");
const express = require("express"); const express = require("express");
const session = require("express-session"); const session = require("express-session");
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
const connectMongo = require("connect-mongo"); const MongoStore = require("connect-mongo");
const secrets = require("./secrets"); const secrets = require("./secrets");
const MongoStore = connectMongo(session)
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic'; var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com'; var backend_server = process.env['REACT_APP_BACKEND_SERVER'] != null ? process.env['REACT_APP_BACKEND_SERVER'] : 'https://api.openmhz.com';
var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com'; var frontend_server = process.env['REACT_APP_FRONTEND_SERVER'] != null ? process.env['REACT_APP_FRONTEND_SERVER'] : 'https://openmhz.com';
var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic'; var socket_server = process.env['REACT_APP_SOCKET_SERVER'] != null ? process.env['REACT_APP_SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic'; var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var dev_server = frontend_server + ":3000" var dev_server = "http://admin.openmhz.test:3000"
module.exports = function(app, passport) { module.exports = function(app, passport) {
app.set("port", 3008) app.set("port", 3008)
@@ -36,9 +35,12 @@ module.exports = function(app, passport) {
secure: false, secure: false,
domain: cookie_domain domain: cookie_domain
}, },
store: new MongoStore({ store: MongoStore.create({
url: secrets.db, mongoUrl: secrets.db,
autoReconnect: true autoReconnect: true,
autoRemove: 'interval',
autoRemoveInterval: 240,
touchAfter: 24 * 3600 // time period in seconds
}) })
} }

View File

@@ -14,10 +14,13 @@ module.exports = function (app, passport) {
done(null, user.id) done(null, user.id)
}) })
passport.deserializeUser((id, done) => { passport.deserializeUser(async (id, done) => {
User.findById(id, (err, user) => { const user = await User.findById(id).exec();
done(err, user) if (user) {
}) done(null,user);
} else {
done("User not found", null);
}
}) })

View File

@@ -4,286 +4,241 @@ const mongoose = require("mongoose");
//router.get('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) { //router.get('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
exports.getGroups = function(req, res, next) { exports.getGroups = async function (req, res, next) {
process.nextTick(function() { process.nextTick(async function () {
System.findOne( const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
{ if (!system) {
shortName: req.params.shortName.toLowerCase() res.status(404);
}, res.json({ success: false });
function(err, system) { return;
if (err) { }
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
if (req.params.groupId) { if (!system.userId.equals(req.user._id)) {
var groupId = req.params.groupId.toLowerCase(); res.status(401)
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
Group.findOne( if (req.params.groupId) {
{ var groupId = req.params.groupId.toLowerCase();
_id: new mongoose.Types.ObjectId(groupId),
shortName: req.params.shortName.toLowerCase() const group = await Group.findOne(
}, {
{ sort: { position: -1 } }, _id: new mongoose.Types.ObjectId(groupId),
function(err, group) { shortName: req.params.shortName.toLowerCase()
if (err) { },
res.json({ { sort: { position: -1 } });
success: false, if (!group) {
message: err res.status(404);
}); res.json({ success: false });
return; return;
} else {
res.json({ } else {
success: true, res.json(group);
group: group return;
});
return;
}
}
);
} else {
Group.find(
{
shortName: req.params.shortName.toLowerCase()
}).sort("position").exec(
function(err, groups) {
if (err) {
res.json({
success: false,
message: err
});
return;
} else {
res.json({
success: true,
groups: groups
});
return;
}
}
);
}
} }
); } else {
}); const group = await Group.findOne(
{
shortName: req.params.shortName.toLowerCase()
}).sort("position").exec();
if (!group) {
res.status(404);
res.json({ success: false });
return;
} else {
let result = group;
if (!Array.isArray(group)) {
result = [group]
}
res.json(result);
return;
}
}
}
);
}; };
exports.deleteGroup = function(req, res, next) { exports.deleteGroup = function (req, res, next) {
//router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) { //router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) {
System.findOne({ System.findOne({
'shortName': req.params.shortName.toLowerCase() 'shortName': req.params.shortName.toLowerCase()
}, function(err, system) { }, function (err, system) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
Group.findOneAndRemove({
shortName: req.params.shortName.toLowerCase(),
'_id': new mongoose.Types.ObjectId(req.params.groupId)
}, function (err) {
if (err) { if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({ res.json({
success: false, success: false,
message: "That Short Name does not exist." message: err
}); });
return; return;
} } else {
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
Group.findOneAndRemove({
shortName: req.params.shortName.toLowerCase(),
'_id': new mongoose.Types.ObjectId(req.params.groupId)
}, function(err) {
if (err) {
res.json({
success: false,
message: err
});
return;
} else {
res.json({
success: true
});
return;
}
// removed!
});
});
};
exports.reorderGroups = function(req, res, next) {
//router.post('/system/:shortName/group_order', isLoggedIn, function(req, res) {
process.nextTick(function() {
System.findOne(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, system) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var groupOrder = JSON.parse(req.body.groupOrder);
for (var i = 0; i < groupOrder.length; i++) {
console.log("in group: " + groupOrder[i] + " position: " + i);
Group.findOneAndUpdate(
{
_id: new mongoose.Types.ObjectId(groupOrder[i]),
shortName: req.params.shortName.toLowerCase()
},
{
position: i
},
{
new: true,
upsert: false
},
function(err, group) {
console.log("Save group: " + group._id + " position: " + group.position);
if (err) {
res.json({
success: false,
message: err
});
console.log(
"Error: Group Order - " +
err +
" Shortname: " +
req.params.shortName.toLowerCase()
);
return;
}
}
);
}
res.json({ res.json({
success: true success: true
}); });
return;
} }
); // removed!
});
}); });
}; };
exports.upsertGroup = function(req, res, next) { exports.reorderGroups = function (req, res, next) {
//router.post('/system/:shortName/group_order', isLoggedIn, function(req, res) {
process.nextTick(async function () {
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
if (!system) {
res.status(404);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.status(401);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var groupOrder = JSON.parse(req.body.groupOrder);
for (var i = 0; i < groupOrder.length; i++) {
console.log("in group: " + groupOrder[i] + " position: " + i);
const group = await Group.findOneAndUpdate(
{
_id: new mongoose.Types.ObjectId(groupOrder[i]),
shortName: req.params.shortName.toLowerCase()
},
{
position: i
},
{
new: true,
upsert: false
});
if (!group) {
res.status(404)
res.json({
success: false,
message: err
});
console.log(
"Error: Group Order - Shortname: " +
req.params.shortName.toLowerCase()
);
return;
}
}
res.json({
success: true
});
}
);
};
exports.upsertGroup = function (req, res, next) {
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) { //router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
process.nextTick(function() { process.nextTick(async function () {
System.findOne( let system = System.findOne({ shortName: req.params.shortName.toLowerCase() });
if (!system) {
res.status(404);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.status(401);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var message = "";
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
var talkgroups = JSON.parse(req.body.talkgroups);
if (!talkgroups.length || !Array.isArray(talkgroups)) {
message = "Talkgroups need to be selected, group can not be empty ";
}
if (!groupName.length) {
message = message + "Group Name is required";
}
if (message.length) {
res.json({
success: false,
message: message
});
return;
}
// if we got here, that means that everything is good for updating/inserting
var groupId = req.params.groupId;
let group = Group.findOneAndUpdate(
{ {
_id: new mongoose.Types.ObjectId(groupId),
shortName: req.params.shortName.toLowerCase() shortName: req.params.shortName.toLowerCase()
}, },
function(err, system) { {
if (err) { groupName: groupName,
console.error(err); talkgroups: talkgroups
res.json({ success: false, message: err }); },
return; {
} new: true,
if (!system) { upsert: true
console.error(err); });
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var message = "";
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
var talkgroups = JSON.parse(req.body.talkgroups);
if (!talkgroups.length || !Array.isArray(talkgroups)) {
message = "Talkgroups need to be selected, group can not be empty ";
}
if (!groupName.length) {
message = message + "Group Name is required";
}
if (message.length) { if (!group) {
res.json({ res.status(500);
success: false, res.json({
message: message success: false,
}); message: err
return; });
} return;
// if we got here, that means that everything is good for updating/inserting } else {
res.json(group);
var groupId = req.params.groupId; return;
}
Group.findOneAndUpdate( }
{ );
_id: new mongoose.Types.ObjectId(groupId), }
shortName: req.params.shortName.toLowerCase()
},
{
groupName: groupName,
talkgroups: talkgroups
},
{
new: true,
upsert: true
},
function(err, group) {
if (err) {
res.json({
success: false,
message: err
});
return;
} else {
res.json({
success: true,
group: group
});
return;
}
}
);
}
);
});
};

View File

@@ -12,91 +12,76 @@ exports.isLoggedIn = function (req, res, next) {
// ------------------------------------------- // -------------------------------------------
exports.listSystems = function (req, res, next) { exports.listSystems = async function (req, res, next) {
console.log("Listing Systems for: " + req.user._id); console.log("Listing Systems for: " + req.user._id);
System.find({ const userId = new mongoose.Types.ObjectId(req.user._id);
userId: mongoose.Types.ObjectId(req.user._id) const systems = await System.find({ userId: userId }).exec();
}, if (systems == null) {
function (err, systems) { res.status(400);
if (err) { res.json({ success: false, message: err });
console.error("Error - ListSystems: " + err); return;
res.json({ }
success: false,
message: err
});
return;
}
var shortNames = new Array(); let shortNames = new Array();
for (var i = 0; i < systems.length; i++) { for (var i = 0; i < systems.length; i++) {
shortNames.push(systems[i].shortName); shortNames.push(systems[i].shortName);
} }
SystemStat.find({ const stats = await SystemStat.find({ shortName: { $in: shortNames } }).exec();
shortName: {
$in: shortNames
}
},
function (err, stats) {
var returnSys = systems.map(obj => {
var rObj = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}))(obj);
if (obj.showScreenName) {
rObj.screenName = req.user.screenName;
} else {
rObj.screenName = null;
}
rObj.id = obj._id;
return rObj;
});
var sys_stats = {}
var MS_PER_MINUTE = 60000;
var now = new Date();
for (var i = 0; i < stats.length; i++) {
sys_stats[stats[i].shortName] = {};
sys_stats[stats[i].shortName].callTotals = stats[i].callTotals; let returnSys = systems.map(obj => {
sys_stats[stats[i].shortName].errorTotals = stats[i].errorTotals; var rObj = (({
sys_stats[stats[i].shortName].errorStats = stats[i].errorStats; name,
sys_stats[stats[i].shortName].usageBytes = stats[i].usageBytes; shortName,
sys_stats[stats[i].shortName].usageMb = stats[i].usageMb; description,
} systemType,
city,
res.json({ state,
success: true, county,
systems: returnSys, country,
stats: sys_stats userId,
}); key,
return; showScreenName,
} ignoreUnknownTalkgroup
); }) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}))(obj);
if (obj.showScreenName) {
rObj.screenName = req.user.screenName;
} else {
rObj.screenName = null;
} }
); rObj.id = obj._id;
return rObj;
});
var sys_stats = {}
for (var i = 0; i < stats.length; i++) {
sys_stats[stats[i].shortName] = {};
sys_stats[stats[i].shortName].callTotals = stats[i].callTotals;
sys_stats[stats[i].shortName].errorTotals = stats[i].errorTotals;
sys_stats[stats[i].shortName].errorStats = stats[i].errorStats;
sys_stats[stats[i].shortName].usageBytes = stats[i].usageBytes;
sys_stats[stats[i].shortName].usageMb = stats[i].usageMb;
}
res.json({
success: true,
systems: returnSys,
stats: sys_stats
});
return;
}; };
function remove_system(shortName) { function remove_system(shortName) {
@@ -115,8 +100,8 @@ function remove_system(shortName) {
}); });
*/ */
System.remove({ System.remove({
shortName: shortName shortName: shortName
}, },
function (err) { function (err) {
if (err) return console.error(err); if (err) return console.error(err);
// removed! // removed!
@@ -124,8 +109,8 @@ function remove_system(shortName) {
); );
Talkgroup.remove({ Talkgroup.remove({
shortName: shortName shortName: shortName
}, },
function (err) { function (err) {
if (err) return console.error(err); if (err) return console.error(err);
// removed! // removed!
@@ -133,8 +118,8 @@ function remove_system(shortName) {
); );
Group.remove({ Group.remove({
shortName: shortName shortName: shortName
}, },
function (err) { function (err) {
if (err) return console.error(err); if (err) return console.error(err);
// removed! // removed!
@@ -145,8 +130,8 @@ function remove_system(shortName) {
exports.deleteSystem = function (req, res, next) { exports.deleteSystem = function (req, res, next) {
//router.get('/delete_system/:shortName', isLoggedIn, function(req, res) { //router.get('/delete_system/:shortName', isLoggedIn, function(req, res) {
System.findOne({ System.findOne({
shortName: req.params.shortName.toLowerCase() shortName: req.params.shortName.toLowerCase()
}, },
function (err, system) { function (err, system) {
if (err) { if (err) {
res.json({ res.json({
@@ -224,7 +209,7 @@ exports.uniqueShortName = async function (req, res, next) {
var shortName = res.locals.shortName; var shortName = res.locals.shortName;
if (typeof shortName == 'undefined') { if (typeof shortName == 'undefined') {
console.error("locals: " + res.locals.shortName + " body: " + req.body.shortName ) console.error("locals: " + res.locals.shortName + " body: " + req.body.shortName)
console.error("ShortName is not in res.local, pulling from req.body instead") console.error("ShortName is not in res.local, pulling from req.body instead")
shortName = req.body.shortName; shortName = req.body.shortName;
} }
@@ -299,7 +284,7 @@ exports.updateSystem = async function (req, res, next) {
userId, userId,
key, key,
showScreenName, showScreenName,
ignoreUnknownTalkgroup ignoreUnknownTalkgroup
}) => ({ }) => ({
name, name,
shortName, shortName,
@@ -312,7 +297,7 @@ exports.updateSystem = async function (req, res, next) {
userId, userId,
key, key,
showScreenName, showScreenName,
ignoreUnknownTalkgroup ignoreUnknownTalkgroup
}))(res.locals.system); }))(res.locals.system);
returnSys.id = res.locals.system._id; returnSys.id = res.locals.system._id;
res.json({ res.json({
@@ -329,7 +314,7 @@ exports.updateSystem = async function (req, res, next) {
exports.validateSystem = async function (req, res, next) { exports.validateSystem = async function (req, res, next) {
try { try {
res.locals.showScreenName = req.body.showScreenName; res.locals.showScreenName = req.body.showScreenName;
res.locals.ignoreUnknownTalkgroup = req.body.ignoreUnknownTalkgroup ; res.locals.ignoreUnknownTalkgroup = req.body.ignoreUnknownTalkgroup;
if (!req.body.name || (req.body.name.length < 2)) { if (!req.body.name || (req.body.name.length < 2)) {
console.error("ERROR: Validate System - req.body.name"); console.error("ERROR: Validate System - req.body.name");
res.json({ res.json({
@@ -457,7 +442,7 @@ exports.createSystem = async function (req, res, next) {
county, county,
country, country,
showScreenName, showScreenName,
ignoreUnknownTalkgroup ignoreUnknownTalkgroup
}) => ({ }) => ({
name, name,
shortName, shortName,
@@ -468,10 +453,10 @@ exports.createSystem = async function (req, res, next) {
county, county,
country, country,
showScreenName, showScreenName,
ignoreUnknownTalkgroup ignoreUnknownTalkgroup
}))(res.locals); }))(res.locals);
system.key = key; system.key = key;
system.userId = mongoose.Types.ObjectId(req.user._id); system.userId = new mongoose.Types.ObjectId(req.user._id);
System.create(system, function (err, newSys) { System.create(system, function (err, newSys) {
if (err) { if (err) {
console.error(err); console.error(err);
@@ -492,7 +477,7 @@ exports.createSystem = async function (req, res, next) {
country, country,
userId, userId,
showScreenName, showScreenName,
ignoreUnknownTalkgroup , ignoreUnknownTalkgroup,
key key
}) => ({ }) => ({
name, name,
@@ -505,7 +490,7 @@ exports.createSystem = async function (req, res, next) {
country, country,
userId, userId,
showScreenName, showScreenName,
ignoreUnknownTalkgroup , ignoreUnknownTalkgroup,
key key
}))(newSys); }))(newSys);
returnSys.id = newSys._id; returnSys.id = newSys._id;

View File

@@ -5,251 +5,217 @@ const fs = require('fs-extra');
const crypto = require("crypto"); const crypto = require("crypto");
var parse = require('csv-parse'); var parse = require('csv-parse');
exports.isLoggedIn = function(req, res, next) { exports.isLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) return next(); if (req.isAuthenticated()) return next();
res.redirect(account_server + "/login"); res.redirect(account_server + "/login");
}; };
//router.get("/tg_export/:shortName", isLoggedIn, function(req, res) { //router.get("/tg_export/:shortName", isLoggedIn, function(req, res) {
exports.exportTalkgroups = function(req, res, next) { exports.exportTalkgroups = function (req, res, next) {
process.nextTick(function() { process.nextTick(async function () {
System.findOne( const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
{ if (!system) {
shortName: req.params.shortName.toLowerCase() res.status(404);
}, res.json({
function(err, system) { success: false,
if (err) { message: "That Short Name does not exist."
console.error(err); });
res.json({ success: false, message: err }); return;
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
Talkgroup.find(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, talkgroups) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!talkgroups || !Array.isArray(talkgroups)) {
console.error(err);
res.json({
success: false,
message: "Talkgroups were not found."
});
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "text/csv");
talkgroups.forEach(function(item) {
/* res.write('"' + item.num + '", "'
+ item.mode + '", "'
+ item.alpha + '", "'
+ item.description + '", "'
+ item.tag + '", "'
+ item.group + '", "'
+ item.priority+ '"\r\n');*/
if (item) {
var sending = "";
sending = item.num + ", ";
if (item.mode) {
sending = sending + item.mode.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.alpa) {
sending = sending + item.alpha.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.description) {
sending = sending + item.description.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.tag) {
sending = sending + item.tag.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.group) {
sending = sending + item.group.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.priority) {
sending = sending + item.priority.toString().replace(/\,/g, "") + "\r\n";
} else {
sending = sending + "\r\n";
}
res.write(sending);
}
});
res.end();
}
);
}
);
});
};
function csv_import(shortName, filename, callback) {
var talkgroups = [];
Talkgroup.remove(
{
shortName: shortName
},
function(err) {
if (err) {
console.error("Error: csv import: " + err);
callback(err, null);
} else {
var parser = parse(
{
columns: [
"num",
"mode",
"alpha",
"description",
"tag",
"group",
"priority"
]
},
async function(err, data) {
if (err) {
console.error("Error: csv import 2: " + err);
callback(err, null);
} else {
var response = [];
for (var i = 0; i < data.length; i++) {
var row = data[i];
row.num = parseInt(row.num);
row.tag = row.tag.toLowerCase();
row.group = row.group.toLowerCase();
var newTg = new Talkgroup();
newTg.num = row.num;
newTg.alpha = row.alpha;
newTg.mode = row.mode;
newTg.description = row.description;
newTg.tag = row.tag;
newTg.group = row.group;
newTg.priority = row.priority;
newTg.shortName = shortName;
await newTg.save(function(err) {
if (err) {
console.log("Error: CSV import: " + err);
callback(err, null);
}
});
response.push(row);
}
callback(null, response);
}
}
);
}
fs.createReadStream(filename).pipe(parser);
// removed!
} }
if (!system.userId.equals(req.user._id)) {
res.status(401);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
const talkgroups = await Talkgroup.find({ shortName: req.params.shortName.toLowerCase() })
if (!talkgroups || !Array.isArray(talkgroups)) {
console.error(err);
res.json({
success: false,
message: "Talkgroups were not found."
});
return;
}
res.statusCode = 200;
res.setHeader("Content-Type", "text/csv");
talkgroups.forEach(function (item) {
/* res.write('"' + item.num + '", "'
+ item.mode + '", "'
+ item.alpha + '", "'
+ item.description + '", "'
+ item.tag + '", "'
+ item.group + '", "'
+ item.priority+ '"\r\n');*/
if (item) {
var sending = "";
sending = item.num + ", ";
if (item.mode) {
sending = sending + item.mode.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.alpa) {
sending = sending + item.alpha.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.description) {
sending = sending + item.description.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.tag) {
sending = sending + item.tag.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.group) {
sending = sending + item.group.toString().replace(/\,/g, "") + ", ";
} else {
sending = sending + " ,";
}
if (item.priority) {
sending = sending + item.priority.toString().replace(/\,/g, "") + "\r\n";
} else {
sending = sending + "\r\n";
}
res.write(sending);
}
});
res.end();
}
); );
} }
//router.post("/tg_import/:shortName", isLoggedIn, upload.single('file'), function(req, res, next) { async function csv_import(shortName, filename, callback) {
exports.importTalkgroups = function(req, res, next) { var talkgroups = [];
process.nextTick(function() { const count = await Talkgroup.deleteMany({ shortName: shortName });
System.findOne(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, system) {
if (err) {
console.error("Error - importTalkgroups: " + err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error("Error - importTalkgroups: System not found - " + req.params.shortName.toLowerCase());
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
console.error("Error - importTalkgroups: you are not the user for hte system: " + req.user._id);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
if (!req.file || req.file.size === 0) {
res.json({ success: false, message: "Please select a file." });
return;
}
fs.exists(req.file.path, function(exists) { var parser = parse(
if (exists) { {
csv_import(system.shortName, req.file.path, function(err, data) { columns: [
if (err) { "num",
console.error("Erorr - CSV import, callback: " + err); "mode",
res.json({ success: false, message: err }); "alpha",
res.end(); "description",
return; "tag",
} else { "group",
res.json({ success: true, talkgroups: data }); "priority"
return; ]
} },
}); async function (err, data) {
if (err) {
console.error("Error: csv import 2: " + err);
callback(err, null);
} else {
var response = [];
for (var i = 0; i < data.length; i++) {
var row = data[i];
row.num = parseInt(row.num);
row.tag = row.tag.toLowerCase();
row.group = row.group.toLowerCase();
var newTg = new Talkgroup();
newTg.num = row.num;
newTg.alpha = row.alpha;
newTg.mode = row.mode;
newTg.description = row.description;
newTg.tag = row.tag;
newTg.group = row.group;
newTg.priority = row.priority;
newTg.shortName = shortName;
await newTg.save().catch(err => {
console.log("Error: CSV import: " + err);
callback(err, null);
});
response.push(row);
}
callback(null, response);
}
}
);
fs.createReadStream(filename).pipe(parser);
}
//router.post("/tg_import/:shortName", isLoggedIn, upload.single('file'), function(req, res, next) {
exports.importTalkgroups = function (req, res, next) {
process.nextTick(async function () {
const system = System.findOne({ shortName: req.params.shortName.toLowerCase() }).catch(err => {
res.status(500);
console.error("Error - importTalkgroups: " + err);
res.json({ success: false, message: err });
return;
});
if (!system) {
console.error("Error - importTalkgroups: System not found - " + req.params.shortName.toLowerCase());
res.status(404);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
console.error("Error - importTalkgroups: you are not the user for hte system: " + req.user._id);
res.status(401);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
if (!req.file || req.file.size === 0) {
res.status(500);
res.json({ success: false, message: "Please select a file." });
return;
}
fs.exists(req.file.path, function (exists) {
if (exists) {
csv_import(system.shortName, req.file.path, function (err, data) {
if (err) {
console.error("Erorr - CSV import, callback: " + err);
res.status(500);
res.json({ success: false, message: err });
res.end();
return;
} else { } else {
console.error("Error - importTalkgroups: no file"); res.json( data );
res.end( return;
"Well, there is no magic for those who don’t believe in it!"
);
} }
}); });
} else {
res.status(500);
console.error("Error - importTalkgroups: no file");
res.end(
"Well, there is no magic for those who don’t believe in it!"
);
} }
); });
}); }
);
}; };
// ------------------------------------------- // -------------------------------------------
exports.fetchTalkgroups = function(req, res, next) { exports.fetchTalkgroups = async function (req, res, next) {
Talkgroup.find( const talkgroups = await Talkgroup.find({ shortName: req.params.shortName.toLowerCase() }).catch(err => {
{ console.error(err);
shortName: req.params.shortName.toLowerCase() res.status(500);
}, res.json({ success: false, message: err });
function(err, talkgroups) { return;
if (err) { });
console.error(err);
res.json({ success: false, message: err }); res.json(talkgroups );
return; return;
} }
res.json({ success: true, talkgroups: talkgroups });
return;
}
);
};

View File

@@ -42,13 +42,7 @@ async function connect() {
// Connect to a MongoDB server running on 'localhost:27017' and use the // Connect to a MongoDB server running on 'localhost:27017' and use the
// 'test' database. // 'test' database.
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => { await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true });
if (err) {
console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Mongoose Successfully connected to ${secrets.db}.`)
}
})
console.log("All Done"); console.log("All Done");
} }
connect(); connect();

View File

@@ -1,15 +1,48 @@
import React, { Component } from "react" import React, { Component } from "react"
import NavigationContainer from "./NavigationContainer" import NavigationContainer from "./NavigationContainer"
import { Routes, Route } from 'react-router-dom';
class App extends Component { import Restricted from "./Restricted"
render() {
return( import About from "../Components/About"
import System from "../System/System"
import ListSystems from "../System/ListSystems"
import UpdateSystemContainer from "../System/UpdateSystemContainer"
import CreateSystemContainer from "../System/CreateSystemContainer"
const App = (params) => {
const {store} = params;
return (
<div> <div>
<NavigationContainer /> <NavigationContainer />
{this.props.children} <Routes>
<Route path="/about" element={ <Restricted><About/></Restricted>} />
<Route path="/system/:shortName" element={ <Restricted><System/></Restricted>} />
<Route path="/update-system/:shortName" component={Restricted(UpdateSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" element={ <Restricted><ListSystems/></Restricted>} />
<Route path="/" element={ <Restricted><ListSystems/></Restricted>} />
<Route render={() => (<div>Miss</div>)} />
</Routes>
</div> </div>
) )
} /*
} return (
<div>
<NavigationContainer />
<Routes>
<Route path="/about" element={<About />} component={Restricted(About, store)} />
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={Restricted(UpdateSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
<Route path="/" component={Restricted(ListSystemsContainer, store)} />
<Route render={() => (<div>Miss</div>)} />
</Routes>
</div>
)*/
};
export default App export default App

View File

@@ -1,12 +1,46 @@
// in src/restricted.js // in src/restricted.js
import React, { Component } from 'react'; //import React, { Component } from 'react';
import { withRouter } from 'react-router-dom'; import React, { useEffect, useLayoutEffect, useState, useRef, useCallback, useMemo } from "react";
import { useSelector, useDispatch } from 'react-redux'
import * as types from "../User/user-constants"; import * as types from "../User/user-constants";
import axios from "axios"; import axios from "axios";
import {
Routes,
Route,
Link,
Navigate,
Outlet,
} from 'react-router-dom';
import { authenticateUser } from "../features/user/userSlice";
/** /**
* Higher-order component (HOC) to wrap restricted pages * Higher-order component (HOC) to wrap restricted pages
*/ */
const Restricted = ({children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const dispatch = useDispatch();
useEffect(() => {
dispatch(authenticateUser({}));
},[]);
useEffect(() => {
if (!authenticated) {
console.log("I loaded things and it is not good")
}
}, [hasAuthenticated])
if (authenticated) {
return children;;
}
return <div/>
};
/*
const restricted = (BaseComponent, store) => { const restricted = (BaseComponent, store) => {
class Restricted extends Component { class Restricted extends Component {
componentDidMount() { componentDidMount() {
this.checkAuthentication(this.props); this.checkAuthentication(this.props);
@@ -32,8 +66,6 @@ const restricted = (BaseComponent, store) => {
}); });
} else { } else {
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin"; window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
/*history.replace({ pathname: '/login',
state: { nextPathname: params.location.pathname } });*/
} }
}) })
.catch(response => { .catch(response => {
@@ -51,4 +83,6 @@ const restricted = (BaseComponent, store) => {
} }
return withRouter(Restricted); return withRouter(Restricted);
} }
export default restricted */
export default Restricted

View File

@@ -1,10 +1,10 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Container, Header } from "semantic-ui-react"; import { Container, Header } from "semantic-ui-react";
class About extends Component { const About = () => {
render() {
return ( return (
<Container text> <Container text>
<Header size='large'>Share the Air</Header> <Header size='large'>Share the Air</Header>
@@ -18,7 +18,7 @@ class About extends Component {
<p> - Luke <a href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}" target="_top">{process.env.REACT_APP_ADMIN_EMAIL}</a> </p> <p> - Luke <a href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}" target="_top">{process.env.REACT_APP_ADMIN_EMAIL}</a> </p>
</Container> </Container>
); );
}
} }
export default About; export default About;

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
//import { browserHistory } from "react-router" //import { browserHistory } from "react-router"
import * as types from "./group-constants"; import * as types from "./group-constants";
import { push } from 'connected-react-router'; import { push } from '@lagunovsky/redux-react-router'
// "Log In" action creators // "Log In" action creators
function beginCreateGroup() { function beginCreateGroup() {

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react"; import { Component, useRef } from "react";
import SystemCard from "./SystemCard"; import SystemCard from "./SystemCard";
import { import {
Container, Container,
@@ -8,37 +8,40 @@ import {
Icon, Icon,
Button, Button,
} from "semantic-ui-react"; } from "semantic-ui-react";
import { useGetSystemsQuery, } from '../features/api/apiSlice'
import { useDispatch } from 'react-redux'
import { push } from '@lagunovsky/redux-react-router'
// ---------------------------------------------------- // ----------------------------------------------------
class ListSystems extends Component { const ListSystems = (props) => {
const { data, isSuccess } = useGetSystemsQuery();
state = { const dispatch = useDispatch();
let systems = false;
if (isSuccess) {
systems = data.systems;
} }
componentDidMount() {
this.props.fetchSystems();
}
//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux //https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux
//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter //https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter
render() {
const systems = this.props.system.items;
return ( return (
<div> <div>
<Container> <Container>
<Header as="h1">Radio Systems</Header> <Header as="h1">Radio Systems</Header>
<Button onClick={(e) => this.props.changeUrl("/create-system")}><Icon name='plus' />Add System</Button> <Button onClick={(e) => dispatch(push("/create-system"))}><Icon name='plus' />Add System</Button>
<Divider /> <Divider />
<Card.Group itemsPerRow={4}> <Card.Group itemsPerRow={4}>
{systems.map((system) => {systems &&
<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/> systems.map((system) =>
<SystemCard system={system} key={system.shortName} onClick={(e) => dispatch(push("/system/" + system.shortName))}/>
)} )}
</Card.Group> </Card.Group>
</Container> </Container>
</div> </div>
); );
}
} }
export default ListSystems; export default ListSystems;

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,13 @@ import {
class SystemCard extends Component { const SystemCard = (props) => {
render() {
var location = ""; var location = "";
const system = this.props.system; const { system } = props;
if (system) { if (system) {
switch (system.systemType) { switch (system.systemType) {
case "state": case "state":
@@ -37,26 +35,26 @@ render() {
} }
} }
return ( return (
<Card link onClick={this.props.onClick}> <Card link onClick={props.onClick}>
<Card.Content>
<Header size='medium'>
<Header.Content>
{system.name}
<Header.Subheader>{system.screenName}</Header.Subheader>
</Header.Content>
</Header>
</Card.Content><Card.Content>
<Card.Description>{system.description}</Card.Description> <Card.Content>
</Card.Content> <Header size='medium'>
<Card.Content extra> <Header.Content>
<Icon name='marker' /> {system.name}
{location} <Header.Subheader>{system.screenName}</Header.Subheader>
</Card.Content> </Header.Content>
</Card> </Header>
</Card.Content><Card.Content>
<Card.Description>{system.description}</Card.Description>
</Card.Content>
<Card.Content extra>
<Icon name='marker' />
{location}
</Card.Content>
</Card>
); );
}
} }
export default SystemCard; export default SystemCard;

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
//import { browserHistory } from "react-router" //import { browserHistory } from "react-router"
import * as types from "./error-constants"; import * as types from "./error-constants";
import { push } from 'connected-react-router'; import { push } from '@lagunovsky/redux-react-router';
function beginFetch() { function beginFetch() {

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
//import { browserHistory } from "react-router" //import { browserHistory } from "react-router"
import * as types from "./system-constants"; import * as types from "./system-constants";
import { push } from 'connected-react-router'; import { push } from '@lagunovsky/redux-react-router';
// "Log In" action creators // "Log In" action creators

View File

@@ -1,7 +1,7 @@
import axios, { post } from 'axios'; import axios from 'axios';
//import { browserHistory } from "react-router" //import { browserHistory } from "react-router"
import * as types from "./talkgroup-constants"; import * as types from "./talkgroup-constants";
import { push } from 'connected-react-router'; import { push } from '@lagunovsky/redux-react-router';
// "Log In" action creators // "Log In" action creators
function beginImportTalkgroup() { function beginImportTalkgroup() {
@@ -58,7 +58,7 @@ function fileUpload(shortName, file){
}, },
withCredentials: true withCredentials: true
} }
return post(url, formData,config) return axios.post(url, formData,config)
} }
// Example of an Async Action Creator // Example of an Async Action Creator

View File

@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
//import { browserHistory } from "react-router" //import { browserHistory } from "react-router"
import * as types from "./user-constants"; import * as types from "./user-constants";
import { push } from 'connected-react-router'; import { push } from '@lagunovsky/redux-react-router';
// "Log Out" action creators // "Log Out" action creators

View File

@@ -0,0 +1,73 @@
// Import the RTK Query methods from the React-specific entry point
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createSelector} from '@reduxjs/toolkit'
// Define our single API slice object
export const apiSlice = createApi({
// The cache reducer expects to be added at `state.api` (already default - this is optional)
reducerPath: 'api',
// All of our requests will have URLs starting with '/fakeApi'
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_ADMIN_SERVER}),
// The "endpoints" represent operations and requests for this server
endpoints: builder => ({
// The `getPosts` endpoint is a "query" operation that returns data
getTalkgroups: builder.query({
// The URL for the request is '/fakeApi/posts'
query: (shortName) => ({ url: `/talkgroups/${shortName}`, credentials: "include" })
}),
getGroups: builder.query({
// The URL for the request is '/fakeApi/posts'
query: (shortName) => ({ url: `/groups/${shortName}`, credentials: "include" })
}),
getErrors: builder.query({
// The URL for the request is '/fakeApi/posts'
query: (shortName) => ({ url: `${process.env.REACT_APP_BACKEND_SERVER}/${shortName}/errors`})
}),
updateGroup: builder.mutation({
query: (group) => ({
url: `/groups/${group.shortName}/${group._id}`,
method: 'POST',
credentials: "include",
body: group,
}),
async onQueryStarted(group, { dispatch, queryFulfilled }) {
try {
const { data: updatedGroup } = await queryFulfilled
const patchResult = dispatch(
apiSlice.util.updateQueryData('getGroups', updatedGroup.shortName, (groups) => {
const groupIndex = groups.findIndex((obj => obj.groupId == updatedGroup.groupId));
groups[groupIndex] = updatedGroup;
})
)
} catch {}
},
}),
createGroup: builder.mutation({
query: (group) => ({
url: `/groups/${group.shortName}`,
method: 'POST',
credentials: "include",
body: group,
}),
async onQueryStarted(group, { dispatch, queryFulfilled }) {
try {
const { data: newGroup } = await queryFulfilled;
const patchResult = dispatch(
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
groups.push(newGroup);
})
)
} catch {}
},
}),
getSystems: builder.query({
// The URL for the request is '/fakeApi/posts'
query: () => ({ url: `/systems`, credentials: "include" })
})
})
})
// Export the auto-generated hook for the `getPosts` query endpoint
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery} = apiSlice

View File

@@ -1,60 +0,0 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
selectCount,
} from './counterSlice';
import styles from './Counter.module.css';
export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={e => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() =>
dispatch(incrementByAmount(Number(incrementAmount) || 0))
}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))}
>
Add Async
</button>
</div>
</div>
);
}

View File

@@ -1,74 +0,0 @@
.row {
display: flex;
align-items: center;
justify-content: center;
}
.row:not(:last-child) {
margin-bottom: 16px;
}
.value {
font-size: 78px;
padding-left: 16px;
padding-right: 16px;
margin-top: 2px;
font-family: 'Courier New', Courier, monospace;
}
.button {
appearance: none;
background: none;
font-size: 32px;
padding-left: 12px;
padding-right: 12px;
outline: none;
border: 2px solid transparent;
color: rgb(112, 76, 182);
padding-bottom: 4px;
cursor: pointer;
background-color: rgba(112, 76, 182, 0.1);
border-radius: 2px;
transition: all 0.15s;
}
.textbox {
font-size: 32px;
padding: 2px;
width: 64px;
text-align: center;
margin-right: 8px;
}
.button:hover, .button:focus {
border: 2px solid rgba(112, 76, 182, 0.4);
}
.button:active {
background-color: rgba(112, 76, 182, 0.2);
}
.asyncButton {
composes: button;
position: relative;
margin-left: 8px;
}
.asyncButton:after {
content: "";
background-color: rgba(112, 76, 182, 0.15);
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
transition: width 1s linear, opacity 0.5s ease 1s;
}
.asyncButton:active:after {
width: 0%;
opacity: 1;
transition: 0s
}

View File

@@ -1,42 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: state => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decrement: state => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched
export const incrementAsync = amount => dispatch => {
setTimeout(() => {
dispatch(incrementByAmount(amount));
}, 1000);
};
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
export const selectCount = state => state.counter.value;
export default counterSlice.reducer;

View File

@@ -0,0 +1,15 @@
const groupSlice = createSlice({
name: 'group',
initialState: {
orderChange: false,
isWaiting: false,
items: {}
},
reducers: {
},
})
// Extract the action creators object and the reducer
const { actions, reducer } = groupSlice
// Export the reducer, either as a default or named export
export default reducer

View File

@@ -0,0 +1,81 @@
import {
createSlice,
createEntityAdapter,
createSelector,
createAsyncThunk
} from '@reduxjs/toolkit'
export const authenticateUser = createAsyncThunk(
'user/authenticate',
async ({ getState }) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated"
const res = await fetch(url, {
method: 'GET',
credentials: "include"
}).then(
(data) => data.json()
)
return res;
}
)
export const logoutUser = createAsyncThunk(
'user/logout',
async ({ getState }) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/logout"
const res = await fetch(url, {
method: 'GET',
credentials: "include"
}).then(
(data) => data.json()
)
return res;
}
)
export const userSlice = createSlice({
name: 'user',
initialState: {
isWaiting: false,
authenticated: false,
hasAuthenticated: false,
userId: ""
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
},
extraReducers: {
[authenticateUser.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
state.authenticated = true;
state.hasAuthenticated = true;
state.userId = payload.user.userId;
} else {
state.authenticated = false;
state.hasAuthenticated = true;
}
},
[authenticateUser.rejected]: (state, { payload }) => {
state.authenticated = false;
state.hasAuthenticated = true;
},
[logoutUser.fulfilled]: (state, { payload }) => {
if (payload.success) {
state.authenticated = false;
state.userId = null;
}
},
}
})
// Extract the action creators object and the reducer
export const userReducer = userSlice.reducer;

View File

@@ -1,40 +1,23 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4/v5 import { createRoot } from 'react-dom/client';
import { ConnectedRouter } from 'connected-react-router' import { BrowserRouter } from "react-router-dom";
import configureStore, { history } from './redux-router/configureStore' import configureStore, { history } from './redux-router/configureStore'
import 'semantic-ui-css/semantic.min.css' import 'semantic-ui-css/semantic.min.css'
import App from "./App/App" import App from "./App/App"
import Restricted from "./App/Restricted"
import About from "./Components/About"
import SystemContainer from "./System/SystemContainer"
import ListSystemsContainer from "./System/ListSystemsContainer"
import UpdateSystemContainer from "./System/UpdateSystemContainer"
import CreateSystemContainer from "./System/CreateSystemContainer"
const store = configureStore(/* provide initial state if any */) const store = configureStore(/* provide initial state if any */)
ReactDOM.render(
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ } <BrowserRouter>
<> <App />
<App> </BrowserRouter>
<Switch> </Provider>
<Route path="/about" component={Restricted(About, store)} />
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={Restricted(UpdateSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" component={Restricted(ListSystemsContainer,store)} />
<Route path="/" component={Restricted(ListSystemsContainer, store)} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</App>
</>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
) )

View File

@@ -3,9 +3,10 @@
import { createBrowserHistory } from 'history' import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux' import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router' import { createRouterMiddleware } from '@lagunovsky/redux-react-router'
import createRootReducer from './reducers' import createRootReducer from './reducers'
import thunk from 'redux-thunk'; import thunk from 'redux-thunk';
import { apiSlice } from '../features/api/apiSlice'
export const history = createBrowserHistory() export const history = createBrowserHistory()
@@ -15,12 +16,14 @@ export default function configureStore(preloadedState) {
preloadedState, preloadedState,
compose( compose(
applyMiddleware( applyMiddleware(
routerMiddleware(history), // for dispatching history actions createRouterMiddleware(history), // for dispatching history actions
thunk, thunk,
apiSlice.middleware,
// ... other middlewares ... // ... other middlewares ...
), ),
), ),
) )
return store return store
} }

View File

@@ -1,24 +1,27 @@
// from https://github.com/supasate/connected-react-router // from https://github.com/supasate/connected-react-router
import { combineReducers } from 'redux' import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router' import { createRouterReducer } from '@lagunovsky/redux-react-router'
import systemReducer from "../System/system-reducer" import systemReducer from "../System/system-reducer"
import talkgroupReducer from "../Talkgroups/talkgroup-reducer" import talkgroupReducer from "../Talkgroups/talkgroup-reducer"
import groupReducer from "../Group/group-reducer" import groupReducer from "../Group/group-reducer"
import errorReducer from "../System/error-reducer" import errorReducer from "../System/error-reducer"
import statisticReducer from "../System/statistic-reducer" import statisticReducer from "../System/statistic-reducer"
import permissionReducer from "../Permission/permission-reducer" import permissionReducer from "../Permission/permission-reducer"
import userReducer from "../User/user-reducer" import { userSlice, userReducer } from '../features/user/userSlice'
import {apiSlice} from "../features/api/apiSlice"
const createRootReducer = (history) => combineReducers({ const createRootReducer = (history) => combineReducers({
router: connectRouter(history), router: createRouterReducer (history),
system: systemReducer, system: systemReducer,
statistic: statisticReducer, statistic: statisticReducer,
talkgroup: talkgroupReducer, talkgroup: talkgroupReducer,
group: groupReducer, group: groupReducer,
user: userReducer, user: userReducer,
error: errorReducer, error: errorReducer,
permission: permissionReducer permission: permissionReducer,
[apiSlice.reducerPath]: apiSlice.reducer,
}) })
export default createRootReducer export default createRootReducer

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ const root = createRoot(container);
root.render( root.render(
<Provider store={store}> <Provider store={store}>
<BrowserRouter> <BrowserRouter>
<App /> <App store={store}/>
</BrowserRouter> </BrowserRouter>
</Provider> </Provider>
) )

View File

@@ -2,18 +2,14 @@
# #
# docker build -t mongo . # docker build -t mongo .
FROM mongo:4.4 FROM mongo:5.0
MAINTAINER Luke Berndt <lukekb@gmail.com> MAINTAINER Luke Berndt <lukekb@gmail.com>
# Set timezone # Set timezone
ENV TZ 'America/New_York' ENV TZ=America/New_York
RUN echo $TZ > /etc/timezone && \ RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
apt-get update && apt-get install -y tzdata && \
rm /etc/localtime && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \
apt-get clean
# Add main NGINX config # Add main NGINX config
RUN mkdir -p /app RUN mkdir -p /app