Merge in upstream changes.

This commit is contained in:
Nate Carlson
2023-04-08 10:11:34 -04:00
116 changed files with 17581 additions and 41851 deletions

View File

@@ -216,6 +216,14 @@ Rough Playbook (use common sense, I may not have this exact):
- get into the shell of the mongo container
- `mongorestore --db scanner --drop /data/db/backup/`
### Privlege Port Error
```bash
Error response from daemon: driver failed programming external connectivity on endpoint trunk-server-nginx-1 (ec995a6333ce5b9869b9070e380d01b81333fdb3f2734bb93922c1ef7d84314d): Error starting userland proxy: error while calling PortManager.AddPort(): cannot expose privileged port 443, you can add 'net.ipv4.ip_unprivileged_port_start=443' to /etc/sysctl.conf (currently 1024), or set CAP_NET_BIND_SERVICE on rootlesskit binary, or choose a larger port number (>= 1024): listen tcp4 0.0.0.0:443: bind: permission denied
```
https://docs.docker.com/engine/security/rootless/#exposing-privileged-ports
## Setting up Logging
I have had good luck with Loggly. Their free tier provides enough capabilities for most small sites.
The Doocker Logging Driver works well and is easy to install:

View File

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

20140
account/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,19 +4,18 @@
"private": true,
"devDependencies": {
"@reduxjs/toolkit": "^1.1.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"axios": "0.21.1",
"connected-react-router": "6.8.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.1.3",
"react-router-dom": "5.2.0",
"react-scripts": "4.0.1",
"redux-thunk": "2.3.0",
"semantic-ui-css": "2.4.1",
"semantic-ui-react": "2.0.1"
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"axios": "1.3.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-router-dom": "6.9.0",
"react-scripts": "5.0.1",
"redux-thunk": "2.4.2",
"semantic-ui-css": "2.5.0",
"semantic-ui-react": "2.1.4"
},
"scripts": {
"start": "react-scripts start",
@@ -40,13 +39,13 @@
]
},
"dependencies": {
"bcrypt": "5.0.0",
"connect-mongo": "3.2.0",
"express": "4.17.1",
"express-session": "1.17.1",
"mongoose": "5.11.10",
"node-mailjet": "3.3.1",
"passport": "0.4.1",
"bcrypt": "5.1.0",
"connect-mongo": "5.0.0",
"express": "4.18.2",
"express-session": "1.17.3",
"mongoose": "7.0.3",
"node-mailjet": "6.0.2",
"passport": "0.6.0",
"passport-local": "1.0.0",
"passport-twitter": "1.0.4"
}

View File

@@ -2,16 +2,16 @@ const path = require("path");
const express = require("express");
const session = require("express-session");
const bodyParser = require("body-parser");
const connectMongo = require("connect-mongo");
const MongoStore = require("connect-mongo");
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 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 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 dev_server = frontend_server + ":3000"
var account_dev_server = "http://account.openmhz.test:3000"
var admin_dev_server = "http://admin.openmhz.test:3000"
module.exports = function(app, passport) {
app.set("port", 3009)
@@ -26,8 +26,6 @@ module.exports = function(app, passport) {
app.use(express.static(path.join(process.cwd(), 'public')));
const sess = {
/*saveUninitialized: false,
resave: false,*/
resave: false,
saveUninitialized: false,
secret: secrets.sessionSecret,
@@ -38,9 +36,12 @@ module.exports = function(app, passport) {
secure: false,
domain: cookie_domain
},
store: new MongoStore({
url: secrets.db,
autoReconnect: true
store: MongoStore.create({
mongoUrl: secrets.db,
autoReconnect: true,
autoRemove: 'interval',
autoRemoveInterval: 240,
touchAfter: 24 * 3600 // time period in seconds
})
}
@@ -65,7 +66,8 @@ module.exports = function(app, passport) {
allowedOrigins.push(frontend_server);
allowedOrigins.push(backend_server);
allowedOrigins.push(socket_server);
allowedOrigins.push(dev_server);
allowedOrigins.push(admin_dev_server);
allowedOrigins.push(account_dev_server);
var origin = req.headers.origin;

View File

@@ -5,34 +5,38 @@
const LocalStrategy = require("passport-local").Strategy;
const User = require("../../models/user");
/*
By default, LocalStrategy expects to find credentials in parameters named username and password.
If your site prefers to name these fields differently, options are available to change the defaults.
*/
/*
By default, LocalStrategy expects to find credentials in parameters named username and password.
If your site prefers to name these fields differently, options are available to change the defaults.
*/
const local = new LocalStrategy({
usernameField: "email"
}, (email, password, done) => {
User.findOne({
$or: [{
email
}, {
local: {
email
}
}]
}, (err, user) => {
if(!user) {
console.error("Auth Error - user not found: " + email);
return done(null, false, { message: `Invalid email or password` })
usernameField: "email"
}, async (email, password, done) => {
console.log(`${email} ${password}`);
const user = await User.findOne({
$or: [{
email
}, {
local: {
email
}
}]
});
if (!user) {
console.error("Auth Error - user not found: " + email);
return done(null, false, { message: `Invalid email or password` })
}
if (!user.confirmEmail) {
console.error("Auth Error - user has not confirmed email: " + email);
return done(null, false, { message: `User's email is not confirmed` })
}
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user)
} else {
console.error("Auth Error - password mismatch: " + email);
return done(null, false, { message: "Invalid email or password" })
}
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user)
} else {
console.error("Auth Error - password mismatch: " + email);
return done(null, false, { message: "Invalid email or password" })
}
})
})
})
})
})
module.exports = local;

View File

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

View File

@@ -5,58 +5,52 @@ const System = require("../models/system");
// -------------------------------------------
exports.getUserSystems = function(_id) {
console.log("Listing Systems for: " + _id);
System.find(
{
userId: mongoose.Types.ObjectId(_id)
},
function(err, systems) {
if (err) {
console.error("Error - ListSystems: " + err);
return({ success: false, message: err });
}
var returnSys = systems.map(obj => {
var rObj = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
planType,
showScreenName
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
planType,
showScreenName
}))(obj);
rObj.id = obj._id;
return rObj;
});
return({ success: true, systems: returnSys });
}
);
exports.getUserSystems = async function (_id) {
console.log("Listing Systems for: " + _id);
const userId = new mongoose.Types.ObjectId(req.user._id);
const systems = await System.find({ userId: userId });
if (systems == null) {
return ({ success: false, message: err });
}
var returnSys = systems.map(obj => {
var rObj = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
planType,
showScreenName
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
planType,
showScreenName
}))(obj);
rObj.id = obj._id;
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,10 +1,7 @@
const passport = require("passport");
const User = require("../models/user");
const Mailjet = require('node-mailjet');
const mailjet = require("node-mailjet").connect(
process.env['MAILJET_KEY'],
process.env['MAILJET_SECRET']
);
const crypto = require("crypto");
var admin_email = process.env['REACT_APP_ADMIN_EMAIL'] != null ? process.env['REACT_APP_ADMIN_EMAIL'] : "luke@openmhz.com";
@@ -12,16 +9,25 @@ var site_name = process.env['REACT_APP_SITE_NAME'] != null ? process.env['REACT_
var account_server = process.env['REACT_APP_ACCOUNT_SERVER'] != null ? process.env['REACT_APP_ACCOUNT_SERVER'] : "https://account.openmhz.com";
var cookie_domain = process.env['REACT_APP_COOKIE_DOMAIN'] != null ? process.env['REACT_APP_COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
exports.isLoggedIn = function(req, res, next) {
const mailjet = new Mailjet({
apiKey: process.env['MAILJET_KEY'],
apiSecret: process.env['MAILJET_SECRET']
});
exports.isLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect("/login");
};
// -------------------------------------------
exports.authenticated = function(req, res, next) {
exports.authenticated = function (req, res, next) {
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user + " is: " + req.isAuthenticated());
console.log("account/server/controllers/users.js - exports.authenticated() Auth check for user: " + req.user.email + " is: " + req.isAuthenticated());
if (req.isAuthenticated()) {
var clientUser = (({
firstName,
@@ -54,11 +60,13 @@ exports.authenticated = function(req, res, next) {
}
}
exports.login = function(req, res, next) {
exports.login = function (req, res, next) {
// Do email and password validation for the server
passport.authenticate("local", function(err, user, info) {
passport.authenticate("local", function (err, user, info) {
if (err) return next(err);
if (!user) {
console.log("No user");
return res.json({
success: false,
message: info.message
@@ -75,6 +83,7 @@ exports.login = function(req, res, next) {
req.login(user, loginErr => {
if (loginErr) {
console.log("error")
return res.json({
success: false,
message: loginErr
@@ -82,10 +91,14 @@ exports.login = function(req, res, next) {
}
if (!user.confirmEmail) {
req.logout();
return res.json({
success: false,
message: "unconfirmed email",
userId: user.id
req.logout(function (err) {
if (err) { return next(err); }
res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
return res.json({
success: false,
message: "unconfirmed email",
userId: user.id
});
});
}
//console.log("account/server/controllers/users.js - req.login() Authenicated: " + user.email);
@@ -118,199 +131,217 @@ exports.login = function(req, res, next) {
})(req, res, next);
};
exports.confirmEmail = function(req, res, next) {
exports.confirmEmail = async function (req, res, next) {
const userId = req.params["userId"];
const token = req.params["token"];
User.findById(userId, (err, user) => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
if (user.confirmEmail) {
console.log("User already confirmed email: " + userId);
res.json({
success: false,
message: "already confirmed email"
});
return;
}
const today = new Date();
if (user.confirmEmailTTL < today) {
res.json({
success: false,
message: "token expired"
});
return;
}
if (user.confirmEmailToken != token) {
console.log(
"Token Mismatch DB: " + user.confirmEmailToken + " submitted: " + token
);
res.json({
success: false,
message: "token mismatch"
});
return;
}
user.confirmEmail = true;
user.confirmEmailToken = "";
user.save(err => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
console.log("User: " + user.email + " confirmed email address");
res.json({
success: true
});
return;
let user = await User.findById(userId).catch(err => {
console.error(err);
res.status(404);
res.json({
success: false,
message: err
});
return;
});
if (!user) {
console.error("User not found: " + userId);
res.status(404);
res.json({
success: false,
message: "User not found"
});
return;
}
if (user.confirmEmail) {
console.log("User already confirmed email: " + userId);
res.status(500);
res.json({
success: false,
message: "already confirmed email"
});
return;
}
const today = new Date();
if (user.confirmEmailTTL < today) {
res.status(500);
console.error("Expired token for confirming email: " + userId);
res.json({
success: false,
message: "token expired"
});
return;
}
if (user.confirmEmailToken != token) {
console.error(
"Token Mismatch DB: " + user.confirmEmailToken + " submitted: " + token
);
res.json({
success: false,
message: "token mismatch"
});
return;
}
user.confirmEmail = true;
user.confirmEmailToken = "";
await user.save().catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
console.log("User: " + user.email + " confirmed email address");
res.json({
success: true
});
};
exports.resetPassword = function(req, res, next) {
exports.resetPassword = async function (req, res, next) {
const userId = req.params["userId"];
const token = req.params["token"];
User.findById(userId, (err, user) => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
const today = new Date();
if (user.resetPasswordTTL < today) {
res.json({
success: false,
message: "token expired"
});
return;
}
if (user.resetPasswordToken != token) {
console.log(
"Token Mismatch DB: " + user.resetPasswordToken + " submitted: " + token
);
res.json({
success: false,
message: "token mismatch"
});
return;
}
user.password = req.body.password
user.confirmEmail = true;
user.confirmEmailToken = "";
user.resetPasswordToken = "";
user.save(err => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
res.json({
success: true
});
return;
let user = await User.findById(userId).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
if (!user) {
console.error("User not found: " + userId);
res.status(404);
res.json({
success: false,
message: "User not found"
});
return;
}
const today = new Date();
if (user.resetPasswordTTL < today) {
res.status(500);
res.json({
success: false,
message: "token expired"
});
return;
}
if (user.resetPasswordToken != token) {
console.log(
"Token Mismatch DB: " + user.resetPasswordToken + " submitted: " + token
);
res.status(500);
res.json({
success: false,
message: "token mismatch"
});
return;
}
user.password = req.body.password
user.confirmEmail = true;
user.confirmEmailToken = "";
user.resetPasswordToken = "";
await user.save().catch(err => {
console.error(err);
res.json({
success: false,
message: err
});
return;
});
res.json({
success: true
});
return;
};
// -------------------------------------------
exports.sendResetPassword = function(req, res, next) {
User.findOne({
exports.sendResetPassword = async function (req, res, next) {
let user = await User.findOne({
email: req.body.email
}, (err, user) => {
if (err) {
}).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
if (!user) {
console.error("Reset password failed. No user: " + req.body.email);
res.status(404);
res.json({
success: false,
message: "No account register for " + req.body.email
});
return;
}
const buffer = crypto.randomBytes(20);
const token = buffer.toString("hex");
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
user.resetPasswordToken = token;
user.resetPasswordTTL = tomorrow;
await user.save().catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
const request = mailjet.post("send", {
version: "v3.1"
}).request({
Messages: [{
From: {
Email: admin_email,
Name: site_name + " Admin"
},
To: [{
Email: user.email,
Name: user.firstName + " " + user.lastName
}],
Subject: site_name + " - Password Reset",
TextPart: "It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset. /r" + account_server + "/reset-password/" + user.id + "/" + token,
HTMLPart: "<h3>Thanks for using " + site_name + "!</h3><br />It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset.<p>" + account_server + "/reset-password/" + user.id + "/" + token + "</p>"
}]
});
request
.then(result => {
console.log(`Password reset sent to: ${user.email}`)
})
.catch(err => {
console.error(err.statusCode);
console.error(err);
res.json({
success: false,
message: err
});
return;
}
if (!user) {
console.error("Reset password failed. No user: " + req.body.email);
res.json({
success: false,
message: "No account register for " + req.body.email
});
return;
}
const buffer = crypto.randomBytes(20);
const token = buffer.toString("hex");
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
user.resetPasswordToken = token;
user.resetPasswordTTL = tomorrow;
user.save(err => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
const request = mailjet.post("send", {
version: "v3.1"
}).request({
Messages: [{
From: {
Email: admin_email,
Name: site_name + " Admin"
},
To: [{
Email: user.email,
Name: user.firstName + " " + user.lastName
}],
Subject: site_name + " - Password Reset",
TextPart: "It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset. /r" + account_server + "/reset-password/" + user.id + "/" + token,
HTMLPart: "<h3>Thanks for using " + site_name + "!</h3><br />It looks like you may have forgot your password. Copy this link to your browser to reset your password. Let us know if you are receiving this but did not request a password reset.<p>" + account_server + "/reset-password/" + user.id + "/" + token + "</p>"
}]
});
request
.then(result => {
console.log(`Password reset sent to: ${user.email}`)
})
.catch(err => {
console.error(err.statusCode);
console.error(err);
res.json({
success: false,
message: err
});
return;
});
res.json({
success: true
});
return;
});
res.json({
success: true
});
return;
};
function handleSendConfirmEmail(user) {
return new Promise((resolve, reject) => {
return new Promise(async (resolve, reject) => {
if (user.confirmEmail) {
console.log("Error - Send Confirm Email - User already confirmed email: " + user.email);
reject({
@@ -326,24 +357,23 @@ function handleSendConfirmEmail(user) {
tomorrow.setDate(tomorrow.getDate() + 1);
user.confirmEmailToken = token;
user.confirmEmailTTL = tomorrow;
user.save().then(function(user) {
return mailjet.post("send", {
version: "v3.1"
}).request({
Messages: [{
From: {
Email: admin_email,
Name: site_name + " Admin"
},
To: [{
Email: user.email,
Name: user.firstName + " " + user.lastName
}],
Subject: "Confirm " + site_name + " Account",
TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token,
HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>"
}]
});
await user.save();
mailjet.post("send", {
version: "v3.1"
}).request({
Messages: [{
From: {
Email: admin_email,
Name: site_name + " Admin"
},
To: [{
Email: user.email,
Name: user.firstName + " " + user.lastName
}],
Subject: "Confirm " + site_name + " Account",
TextPart: "Thanks for signing up for " + site_name + ". We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address: " + account_server + "/confirm-email/" + user._id + "/" + token,
HTMLPart: "<h3>Thanks for signing up for " + site_name + "</h3><br />We just wanted to check and make sure your email address was real. Copy and paste this addres in your browser to confirm your email address:<p> " + account_server + "/confirm-email/" + user._id + "/" + token + "</p>"
}]
}).then(result => {
console.log("Confirm email sent to: " + user.email);
resolve({
@@ -353,103 +383,110 @@ function handleSendConfirmEmail(user) {
}).catch(err => {
console.error("Admin Email: " + admin_email + " User Email: " + user.email);
console.error("Error - Send Confirm Email - caught: " + err);
res.status(500);
reject({
success: false,
message: err
});
});
});
};
}
// -------------------------------------------
exports.sendConfirmEmail = function(req, res, next) {
exports.sendConfirmEmail = async function (req, res, next) {
const userId = req.params["userId"];
User.findById(userId, (err, user) => {
if (err) {
console.error("Error - Send Confirm Email: " + err);
res.json({
success: false,
message: err
});
return;
}
if (!user) {
console.error("Error - Send Confirm Email: User not found " + userId);
res.json({
success: false,
message: "user not found"
});
return;
}
handleSendConfirmEmail(user).then(function(result) {
res.json(result);
return;
}).catch(function(error) {
res.json(error);
return;
let user = await User.findById(userId).catch(err => {
console.error("Error - Send Confirm Email: " + err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
if (!user) {
console.error("Error - Send Confirm Email: User not found " + userId);
res.status(404);
res.json({
success: false,
message: "user not found"
});
return;
}
handleSendConfirmEmail(user).then(function (result) {
res.json(result);
return;
}).catch(function (error) {
res.json(error);
return;
});
}
// -------------------------------------------
exports.logout = function (req, res, next) {
// the logout method is added to the request object automatically by Passport
req.logout(function (err) {
if (err) { return next(err); }
res.clearCookie('sessionId', { domain: cookie_domain, path: '/' });
return res.json({
success: true
});
});
};
// -------------------------------------------
exports.terms = async function (req, res, next) {
const userId = req.params["userId"];
let user = await User.findById(userId).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
exports.logout = function(req, res, next) {
// the logout method is added to the request object automatically by Passport
req.logout();
res.clearCookie('sessionId', {domain: cookie_domain, path:'/'});
return res.json({
if (req.user.id != userId) {
console.log(
"Logged in user's ID: " +
req.user.id +
" does not match Param: " +
userId
);
res.status(500);
res.json({
success: false,
message: "UserID incorrect"
});
return;
}
user.terms = 1.1; //User.termsVer;
await user.save().catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
res.json({
success: true
});
};
exports.terms = function(req, res, next) {
const userId = req.params["userId"];
User.findById(userId, (err, user) => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
if (req.user.id != userId) {
console.log(
"Logged in user's ID: " +
req.user.id +
" does not match Param: " +
userId
);
res.json({
success: false,
message: "UserID incorrect"
});
return;
}
user.terms = 1.1; //User.termsVer;
user.save(err => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
res.json({
success: true
});
return;
});
});
};
return;
}
// -------------------------------------------
exports.validateProfile = function(req, res, next) {
console.log("Validating user profile: "+ req.body.email);
exports.validateProfile = function (req, res, next) {
console.log("Validating user profile: " + req.body.email);
if (!req.body.firstName || (req.body.firstName.length < 2)) {
console.error("ERROR: Validate System - req.body.firstName");
res.json({
@@ -486,68 +523,97 @@ exports.validateProfile = function(req, res, next) {
next();
}
exports.updateProfile = function(req, res, next) {
exports.updateProfile = async function (req, res, next) {
const userId = req.params["userId"];
User.findById(userId, (err, user) => {
// is email address already in use?
if (req.user.id != userId) {
console.log(
"ERROR: Logged in user's ID: " +
req.user.id +
" does not match Param: " +
userId
);
res.json({
success: false,
message: "UserID incorrect"
});
return;
}
// go ahead and create the new user
user.firstName = res.locals.firstName
user.lastName = res.locals.lastName
user.screenName = res.locals.screenName
user.location = res.locals.location
user.save(err => {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
var clientUser = (({
firstName,
lastName,
screenName,
location
}) => ({
firstName,
lastName,
screenName,
location
}))(
user
);
console.log("Updated Profile: " + userId)
res.json({
success: true,
user: clientUser
});
return;
user = await User.findById(userId).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
if (req.user.id != userId) {
console.log(
"ERROR: Logged in user's ID: " +
req.user.id +
" does not match Param: " +
userId
);
res.json({
success: false,
message: "UserID incorrect"
});
return;
}
// Lets make sure someone else isn't using this screenName
screenNameUser = await User.findOne({ screenName: req.body.screenName }).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
// Did we find a user with the screeName, is it not us?
if (screenNameUser && (screenNameUser.userId != user.userId)) {
res.status(500);
res.json({
success: false,
message: "Screen Name already in use"
});
return;
}
// go ahead and create the new user
user.firstName = res.locals.firstName
user.lastName = res.locals.lastName
user.screenName = res.locals.screenName
user.location = res.locals.location
await user.save().catch(err => {
console.error(err);
res.json({
success: false,
message: err
});
return;
});
var clientUser = (({
firstName,
lastName,
screenName,
location
}) => ({
firstName,
lastName,
screenName,
location
}))(
user
);
console.log("Updated Profile: " + userId)
res.json({
success: true,
user: clientUser
});
return;
};
// -------------------------------------------
exports.register = function(req, res, next) {
exports.register = async function (req, res, next) {
console.log("Registration request for: " + req.body.email);
User.findOne({
let user = await User.findOne({
$or: [{
email: req.body.email
}, {
@@ -555,49 +621,79 @@ exports.register = function(req, res, next) {
email: req.body.email
}
}]
}, (err, user) => {
// is email address already in use?
if (user) {
res.json({
success: false,
message: "Email already in use"
});
return;
}
// go ahead and create the new user
var user = (({
firstName,
lastName,
screenName,
location,
email,
password
}) => ({
firstName,
lastName,
screenName,
location,
}))(
res.locals
);
user.password = req.body.password;
user.email = req.body.email;
User.create(user).then(function(dbUser) {
console.log("Successfully registered: " + dbUser.email + ", now sending confirmation email.");
// Since regiestration worked, send a confirmation email.
return handleSendConfirmEmail(dbUser);
}).then(function(result) {
res.json(result);
return;
}).catch(function(err) {
console.error("Error creating user: " + user.email + " Error: " +err);
res.json({
success: false,
message: err
});
return;
}).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
};
// is email address already in use?
if (user) {
res.status(500);
res.json({
success: false,
message: "Email already in use"
});
return;
}
user = await User.findOne({ screenName: req.body.screenName }).catch(err => {
console.error(err);
res.status(500);
res.json({
success: false,
message: err
});
return;
});
// is email address already in use?
if (user) {
res.status(500);
res.json({
success: false,
message: "Screen Name already in use"
});
return;
}
// go ahead and create the new user
user = (({
firstName,
lastName,
screenName,
location,
email,
password
}) => ({
firstName,
lastName,
screenName,
location,
}))(
res.locals
);
user.password = req.body.password;
user.email = req.body.email;
let savedUser = await User.create(user)
console.log("Successfully registered: " + user.email + ", now sending confirmation email.");
// Since registration worked, send a confirmation email.
handleSendConfirmEmail(savedUser).then(function (result) {
console.log("Confirmation email sent to: " + user.email);
res.json({
success: true,
message: result
});
return;
}).catch(function (err) {
console.error("Error creating user: " + user.email + " Error: " + err);
res.json({
success: false,
message: err
});
return;
});
}

View File

@@ -15,39 +15,39 @@ const app = express()
// -------------------------------------------
async function connect() {
const connect = async () => {
// Demonstrate the readyState and on event emitters
console.log(mongoose.connection.readyState); //logs 0
mongoose.connection.on('connecting', () => {
console.log('Mongoose is connecting')
console.log(mongoose.connection.readyState); //logs 2
mongoose.connection.on('connecting', () => {
console.log('Mongoose is connecting')
console.log(mongoose.connection.readyState); //logs 2
});
mongoose.connection.on('connected', () => {
console.log('Mongoose is connected');
console.log(mongoose.connection.readyState); //logs 1
console.log('Mongoose is connected');
console.log(mongoose.connection.readyState); //logs 1
});
mongoose.connection.on('disconnecting', () => {
console.log('Mongoose is disconnecting');
console.log(mongoose.connection.readyState); // logs 3
console.log('Mongoose is disconnecting');
console.log(mongoose.connection.readyState); // logs 3
});
// Connect to a MongoDB server running on 'localhost:27017' and use the
// 'test' database.
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => {
if (err) {
console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Mongoose Successfully connected to ${secrets.db}.`)
}
})
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true }).catch(err => {
console.error(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
});
console.log("All Done");
}
connect();
mongoose.connection.on('error', err => {
console.error("Mongoose Error!")
console.error(err);
});
});
mongoose.connection.on('disconnected', () => {
console.log('Mongoose disconnected');
console.log(mongoose.connection.readyState); //logs 0
@@ -72,16 +72,16 @@ configurePassport(app, passport)
configureExpress(app, passport)
app.all('*', function(req, res, next) {
var allowedOrigins = [account_server, admin_server, frontend_server];
var origin = req.headers.origin;
if(allowedOrigins.indexOf(origin) > -1){
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
app.all('*', function (req, res, next) {
var allowedOrigins = [account_server, admin_server, frontend_server];
var origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin);
}
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
});
// -------------------------------------------
@@ -94,8 +94,8 @@ app.post("/users/:userId/reset-password/:token", users.resetPassword)
app.post("/api/send-reset-password", users.sendResetPassword)
app.post("/users/:userId", users.isLoggedIn, users.validateProfile, users.updateProfile)
app.post("/users/:userId/terms", users.isLoggedIn, users.terms)
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
app.post("/users/:userId/confirm/:token", users.confirmEmail)
app.post("/users/:userId/send-confirm", users.sendConfirmEmail)
app.post("/users/:userId/confirm/:token", users.confirmEmail)
app.get("*", (req, res, next) => {
res.sendFile(__dirname + '/public/index.html');

View File

@@ -1,15 +1,38 @@
import React, { Component } from "react"
import NavigationContainer from "./NavigationContainer"
import React from "react"
import Navigation from "./Navigation"
import { Routes, Route } from 'react-router-dom';
class App extends Component {
render() {
return(
<div>
<NavigationContainer />
{this.props.children}
</div>
)
}
}
import Restricted from "./Restricted"
import Login from "../User/Login"
import Register from "../User/Register"
import Profile from "../User/Profile"
import ConfirmEmail from "../User/ConfirmEmail"
import WaitConfirmEmail from "../User/WaitConfirmEmail"
import SentConfirmEmail from "../User/SentConfirmEmail"
import SendResetPassword from "../User/SendResetPassword"
import ResetPassword from "../User/ResetPassword"
import Terms from "../User/Terms"
const App = (params) => {
return (
<div>
<Navigation />
<Routes>
<Route exact path="/" element={<Restricted><Profile /></Restricted>} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/wait-confirm-email" element={<WaitConfirmEmail />} />
<Route path="/sent-confirm-email" element={<SentConfirmEmail />} />
<Route path="/send-reset-password" element={<SendResetPassword />} />
<Route path="/reset-password/:userId/:token" element={<ResetPassword />} />
<Route path="/confirm-email/:userId/:token" element={<ConfirmEmail />} />
<Route path="/terms" element={<Restricted><Terms /></Restricted>} />
<Route path="/profile" element={<Restricted><Profile /></Restricted>} />
<Route render={() => (<div>Miss</div>)} />
</Routes>
</div>
)
};
export default App

View File

@@ -1,53 +1,39 @@
import React, { Component } from "react";
import { Menu, Dropdown } from "semantic-ui-react";
import { useSelector, useDispatch } from 'react-redux'
import { logoutUser } from "../features/user/userSlice";
const navStyle = {
marginBottom: "30px"
};
class Navigation extends Component {
constructor(props) {
super(props);
this._logout = this._logout.bind(this);
}
const Navigation = (props) => {
const dispatch = useDispatch();
const { email } = useSelector((state) => state.user);
_logout = event => {
const logout = event => {
event.preventDefault();
this.props.manualLogout();
dispatch(logoutUser({}));
};
render() {
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
{this.props.user.authenticated ? (
<Menu style={navStyle}>
<Menu.Item name="systems" header>
{process.env.REACT_APP_SITE_NAME}
</Menu.Item>
<Menu.Menu position="right">
{/* <Link to="/update-plans">
<Menu.Item name="systems">Update Plans</Menu.Item>
</Link> */}
<Dropdown item text={this.props.user.email}>
<Dropdown.Menu>
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
{/* <Dropdown.Item href={billingLink}>Billing</Dropdown.Item> */}
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Menu.Menu>
</Menu>
) : (
<Menu style={navStyle}>
<Menu.Item name="systems" header>
OpenMHz
</Menu.Item>
</Menu>
)}
</div>
);
}
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
<Menu style={navStyle}>
<Menu.Item name="systems" header>
{process.env.REACT_APP_SITE_NAME}
</Menu.Item>
<Menu.Menu position="right">
<Dropdown item text={ email }>
<Dropdown.Menu>
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Menu.Menu>
</Menu>
</div>
);
}
export default Navigation;

View File

@@ -1,21 +0,0 @@
import { connect } from "react-redux"
import * as userActions from "../User/user-actions"
import Navigation from "./Navigation"
// Function passed in to `connect` to subscribe to Redux store updates.
// Any time it updates, mapStateToProps is called.
const mapStateToProps = (state) => {
return {
user: state.user
}
}
// Connects React component to the redux store
// It does not modify the component class passed to it
// Instead, it returns a new, connected component class, for you to use.
export default connect(
mapStateToProps,
userActions
)(Navigation)

View File

@@ -1,55 +1,31 @@
// in src/restricted.js
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import * as types from "../User/user-constants";
import axios from "axios";
import React, { useEffect } from "react";
import { useSelector, useDispatch } from 'react-redux'
import { authenticateUser } from "../features/user/userSlice";
import { useNavigate } from 'react-router-dom';
/**
* Higher-order component (HOC) to wrap restricted pages
*/
const restricted = (BaseComponent, store) => {
class Restricted extends Component {
componentWillMount() {
this.checkAuthentication(this.props);
}
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.checkAuthentication(nextProps);
}
}
checkAuthentication(params) {
const { history } = params;
const authenticated = store.getState().user.authenticated;
if (!authenticated) {
axios
.get("/authenticated")
.then(response => {
if (response.data.success) {
const user = response.data.user
store.dispatch({
type: types.LOGIN_SUCCESS_USER,
data: user
});
} else {
history.replace({ pathname: '/login',
state: { nextPathname: params.location.pathname } });
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened during logout that triggered an Error
history.replace({ pathname: '/login',
state: { nextPathname: params.location.pathname } });
console.log("Error", response.message);
}
});
const Restricted = ({ children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const dispatch = useDispatch();
const navigate = useNavigate();
}
}
render() {
return <BaseComponent {...this.props} />;
}
useEffect(() => {
dispatch(authenticateUser({}));
}, []);
if (hasAuthenticated) {
if (authenticated) {
return children;
}
return withRouter(Restricted);
}
export default restricted
else {
navigate("/login")
}
}
return <div />
};
export default Restricted

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react";
import React, { useState, useEffect } from "react";
import {
Container,
Dimmer,
@@ -7,72 +7,65 @@ import {
Message,
Icon
} from "semantic-ui-react";
import { Link } from "react-router-dom";
import { Link, useParams } from "react-router-dom";
import { confirmEmail } from "../features/user/userSlice";
import { useDispatch } from 'react-redux'
class ConfirmEmail extends Component {
const ConfirmEmail = (props) => {
const dispatch = useDispatch();
const [loading, setLoading] = useState(true);
const [confirmMessage, setConfirmMessage] = useState({ success: false, message: "" });
const { userId, token } = useParams();
state = {
loading: true,
confirmMessage: { success: false, message:""}
};
componentDidMount() {
const { match: { params } } = this.props;
this.props
.confirmEmail(params.userId, params.token)
.then(confirmMessage => {
if (confirmMessage) {
// report to the user is there was a problem during login
this.setState({
confirmMessage: confirmMessage, loading: false
});
}
});
const checkConfirmation = async(userId, token) => {
const result = await dispatch(confirmEmail({ userId, token })).unwrap();
if (result) {
setConfirmMessage(result);
setLoading(false);
}
}
render() {
var dimmerProps = { };
if (this.state.loading) {
dimmerProps["active"] = true;
}
return (
useEffect(() => {
checkConfirmation(userId, token);
}, [userId, token]);
<Container text>
const dimmerProps = { active: loading };
return (
<Container text>
<Dimmer {...dimmerProps}>
<Loader indeterminate>Confirming Email Address</Loader>
</Dimmer>
{this.state.confirmMessage.success ? (
<Message icon>
<Icon name='check'/>
<Message.Content>
<Message.Header>Success</Message.Header>
<p>You have successfully confirmed you email address.</p>
<Link to="/">
<Button
size="large"
content="Continue to Login"
fluid
/></Link>
<Loader indeterminate>Confirming Email Address</Loader>
</Dimmer>
</Message.Content>
</Message>
) : (
<Message icon>
<Icon name='exclamation'/>
{confirmMessage.success ? (
<Message icon>
<Icon name='check' />
<Message.Content>
<Message.Header>Success</Message.Header>
<p>You have successfully confirmed you email address.</p>
<Link to="/login">
<Button
size="large"
content="Continue to Login"
fluid
/></Link>
</Message.Content>
</Message>
) : (
<Message icon>
<Icon name='exclamation' />
<Message.Content>
<Message.Header>Error</Message.Header>
<p>{this.state.confirmMessage.message}</p>
<p>{confirmMessage.message}</p>
</Message.Content>
</Message>
)}
</Container>
);
}
</Message>
)}
</Container>
);
}
export default ConfirmEmail;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import ConfirmEmail from "./ConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(ConfirmEmail)

View File

@@ -1,6 +1,8 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { useState } from "react";
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { loginUser } from "../features/user/userSlice";
import { useDispatch } from 'react-redux'
import {
Container,
Header,
@@ -26,74 +28,54 @@ const forgotStyle = {
// ----------------------------------------------------
class Login extends Component {
constructor(props) {
super(props);
this._onLoginSubmit = this._onLoginSubmit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleRegisterClick = this.handleRegisterClick.bind(this);
}
const Login = (props) => {
const [loginMessage, setLoginMessage] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
let [searchParams] = useSearchParams();
const nextLocation = searchParams.get("nextLocation");
const dispatch = useDispatch();
state = {
loginMessage: "",
email: "",
password: ""
};
handleRegisterClick = () => {
this.props.changeUrl("/register");
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
_onLoginSubmit(event) {
const loginSubmit = async (event) => {
event.preventDefault();
const { email, password } = this.state;
var success = { type: "path", nextPathname: "/" };
if (this.props.nextPathname) {
success.nextPathname = this.props.nextPathname;
}
if (this.props.nextLocation) {
success.type = "location";
switch (this.props.nextLocation) {
case "frontend":
success.nextLocation = process.env.REACT_APP_FRONTEND_SERVER;
break;
default:
case "admin":
success.nextLocation = process.env.REACT_APP_ADMIN_SERVER;
break;
const result = await dispatch(loginUser({email,password})).unwrap();
if (result.success) {
if (nextLocation) {
switch (nextLocation) {
case "frontend":
window.location = process.env.REACT_APP_FRONTEND_SERVER;
break;
default:
case "admin":
window.location = process.env.REACT_APP_ADMIN_SERVER;
break;
}
} else {
navigate("/");
}
console.log(result)
} else {
console.error(result);
setLoginMessage(result.message);
if (result.message === "unconfirmed email") {
/*data.userId = response.data.userId;
dispatch(loginEmailError(data));*/
navigate("/wait-confirm-email");
}
}
// Passed in via react-redux. Returns a promise.
this.props
.manualLogin(
{
// this function is passed in via react-redux
email,
password
},
success
) // holds the path to redirect to after login (if any)
.then(loginMessage => {
if (loginMessage) {
// report to the user is there was a problem during login
this.setState({
loginMessage
});
}
});
}
render() {
var loginMessage = "";
let loginMessageDisplay =(<></>)
if (this.state.loginMessage) {
loginMessage = (
if (loginMessage) {
loginMessageDisplay = (
<Message icon>
<Icon name="lemon" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
{this.state.loginMessage}
{loginMessage}
</Message.Content>
</Message>
);
@@ -147,12 +129,12 @@ class Login extends Component {
<Button
size="large"
content="Register"
onClick={this.handleRegisterClick}
onClick={()=>navigate("/register")}
fluid
/>
<Divider style={dividerStyle} horizontal>Or</Divider>
<Header as="h3">Login</Header>
<Form onSubmit={this._onLoginSubmit}>
<Form onSubmit={loginSubmit}>
<Segment padded>
<Form.Field>
<Form.Input
@@ -160,7 +142,8 @@ class Login extends Component {
iconPosition="left"
type="text"
name="email"
onChange={this.handleInputChange}
onChange={e => setEmail(e.target.value)}
value={email}
placeholder="E-mail address"
/>
</Form.Field>
@@ -170,7 +153,8 @@ class Login extends Component {
iconPosition="left"
type="password"
name="password"
onChange={this.handleInputChange}
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Password"
/>
</Form.Field>
@@ -178,7 +162,7 @@ class Login extends Component {
<Button type="submit" size="large" value="Login" fluid>
Login
</Button>
{loginMessage}
{loginMessageDisplay}
</Segment>
</Form>
<div style={forgotStyle} >
@@ -190,6 +174,6 @@ class Login extends Component {
</Container>
);
}
}
export default Login;

View File

@@ -1,35 +0,0 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Login from "./Login"
import queryString from '../query-string';
// Function passed in to `connect` to subscribe to Redux store updates.
// Any time it updates, mapStateToProps is called.
// The second argument "ownProps" contains props passed to the component
const mapStateToProps = (state, props) => {
let nextPathname = false;
try {nextPathname = props.location.state.nextPathname}
catch(err) {}
const uri = queryString.parse(props.location.search);
let nextLocation = uri['nextLocation'];
return {
user: state.user,
nextPathname, // this prop passed in by React Router
nextLocation
}
}
// Connects React component to the redux store
// It does not modify the component class passed to it
// Instead, it returns a new, connected component class, for you to use.
export default connect(
mapStateToProps,
userActions
)(Login)

View File

@@ -1,45 +1,36 @@
import React, { Component } from "react"
import { useState, } from "react";
import { useSelector, useDispatch } from 'react-redux'
import UserForm from "./UserForm";
import {
Container,
Header
} from "semantic-ui-react";
import { updateProfile } from "../features/user/userSlice";
class Profile extends Component {
constructor(props) {
super(props);
this.handleProfileSubmit = this.handleProfileSubmit.bind(this);
}
const Profile = (props) => {
const [message, setMessage] = useState("");
const user = useSelector((state) => state.user);
const dispatch = useDispatch();
const handleProfileSubmit = async (updatedUser) => {
updatedUser.userId = user.userId
state = {
requestMessage: ""
const result = await dispatch(updateProfile(updatedUser)).unwrap();
if (result.success) {
console.log(result)
} else {
setMessage(result.message);
}
}
handleProfileSubmit(user) {
user.userId = this.props.user.userId
this.props
.updateProfile(user)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
render() {
// The Key on UserForm get updated when the user is loaded. This forces the Component to be completely redrawn, otherwise the changes in the Prop are not copied to State.
return(
<Container text>
<Header as="h1">Update Profile</Header>
<UserForm onSubmit={this.handleProfileSubmit} user={this.props.user} isEditing={true} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting} key={this.props.user.userId}/>
<UserForm onSubmit={handleProfileSubmit} user={user} isEditing={true} requestMessage={message} isWaiting={props.isWaiting} key={user.userId}/>
</Container>
)
}
}
export default Profile

View File

@@ -1,15 +0,0 @@
import { connect } from "react-redux"
import Profile from "./Profile"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user,
isWaiting: state.user.isWaiting
}
}
export default connect(
mapStateToProps,
userActions
)(Profile)

View File

@@ -1,4 +1,7 @@
import React, { Component } from "react";
import { useState } from "react";
import { registerUser } from "../features/user/userSlice";
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux'
import {
Container,
Header,
@@ -8,53 +11,39 @@ import {
import UserForm from "./UserForm";
// ----------------------------------------------------
class Register extends Component {
constructor(props) {
super(props);
this._onRegisterSubmit = this._onRegisterSubmit.bind(this);
}
function Register(props) {
const [requestMessage, setRequestMessage] = useState("");
const dispatch = useDispatch();
const navigate = useNavigate();
const onRegisterSubmit = async (user) => {
const result = await dispatch(registerUser(user)).unwrap();
if (result.success) {
navigate("/sent-confirm-email");
} else {
console.error(result);
setRequestMessage(result.message);
}
};
state = {
requestMessage: ""
}
_onRegisterSubmit(user) {
this.props
.manualRegister(user)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
render() {
return (
<div>
<Container text>
return (
<div>
<Container text>
<Message icon>
<Icon name='trophy'/>
<Message.Content>
<Message.Header>Welcome!</Message.Header>
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
<p> - Luke</p>
</Message.Content>
</Message>
</Container>
<Container text>
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
<UserForm onSubmit={this._onRegisterSubmit} requestMessage={this.state.requestMessage} isWaiting={this.props.isWaiting}/>
</Container>
</div>
);
}
<Icon name='trophy' />
<Message.Content>
<Message.Header>Welcome!</Message.Header>
<p>Creating an account on OpenMHz is the first step in sharing radio system recordings using Trunk Recorder.</p>
<p>After creating an account, head over <a href="https://github.com/robotastic/trunk-recorder">here</a> to learn more about Trunk Recorder</p>
<p> - Luke</p>
</Message.Content>
</Message>
</Container>
<Container text>
<Header style={{ paddingTop: '20px' }} as="h1">Register</Header>
<UserForm onSubmit={onRegisterSubmit} requestMessage={requestMessage} isWaiting={props.isWaiting} />
</Container>
</div>
);
}
export default Register;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Register from "./Register"
const mapStateToProps = (state) => {
return {
isWaiting: state.user.isWaiting
}
}
export default connect(
mapStateToProps,
userActions
)(Register)

View File

@@ -1,4 +1,7 @@
import React, { Component } from "react";
import React, { useState } from "react";
import { resetPassword } from "../features/user/userSlice";
import { useParams, useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux'
import {
Container,
Form,
@@ -6,101 +9,100 @@ import {
Button,
} from "semantic-ui-react";
class ResetPassword extends Component {
constructor(props) {
super(props);
this.handleResetPassword = this.handleResetPassword.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
state = {
checkInputMessages: [],
password: "",
confirmPassword: "",
passwordError: false,
confirmPasswordError: false,
sentReset: false
};
const ResetPassword = (props) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [passwordError, setPasswordError] = useState(false);
const [confirmPasswordError, setConfirmPasswordError] = useState(false);
const [checkInputMessages, setCheckInputMessages] = useState([]);
const { userId, token } = useParams();
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleResetPassword(event) {
const handleResetPassword = async () => {
let error = false;
var checkInputMessages = [];
var inputMessages = [];
if (this.state.password === "") {
this.setState({ passwordError: true });
checkInputMessages.push("Password is required");
if (password === "") {
setPasswordError(true);
inputMessages.push("Password is required");
error = true;
} if (this.state.password.length < 7) {
this.setState({ passwordError: true });
checkInputMessages.push("Password must be 7 charecters or more");
error = true;
}else {
this.setState({ passwordError: false });
}
if (this.state.password !== this.state.confirmPassword) {
this.setState({ confirmPasswordError: true });
checkInputMessages.push("The passwords did not match");
} else if (password.length < 7) {
setPasswordError(true);
inputMessages.push("Password must be 7 characters or more");
error = true;
} else {
this.setState({ confirmPasswordError: false });
setPasswordError(false);
}
const { match: { params } } = this.props;
this.setState({ checkInputMessages: checkInputMessages });
if (password !== confirmPassword) {
setConfirmPasswordError(true);
inputMessages.push("The passwords did not match");
error = true;
} else {
setConfirmPasswordError(false);
}
setCheckInputMessages(inputMessages);
if (!error) {
this.props.resetPassword(params.userId, params.token, this.state.password).then(resetSent => {
this.setState({resetSent: true});
});
const result = await dispatch(resetPassword({ userId, token, password })).unwrap();
if (result.success) {
navigate("/login");
} else {
console.error(result);
}
}
}
};
render() {
return (
<div>
<Container text>
return (
<div>
<Container text>
<Segment>
<Form
className="raised padding segment"
onSubmit={this.handleSubmit}
>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={this.handleInputChange}
error={this.state.passwordError}
label="Password"
placeholder="Password..."
<Form className="raised padding segment">
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={e => setPassword(e.target.value)}
error={passwordError}
label="Password"
placeholder="Password..."
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={e => setConfirmPassword(e.target.value)}
error={confirmPasswordError}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
<Button
size="large"
content="Reset Password"
onClick={handleResetPassword}
fluid
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={this.handleInputChange}
error={this.state.confirmPasswordError}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
<Button
size="large"
content="Reset Password"
onClick={this.handleResetPassword}
fluid
/>
</Form>
</Segment>
{checkInputMessages.length > 0 && (
<div style={{ marginTop: "10px" }}>
{checkInputMessages.map((msg) => (
<p key={msg} style={{ color: "red" }}>
{msg}
</p>
))}
</div>
)}
</Form>
</Segment>
</Container>
</div>
);
}
}
</div>
);
};
export default ResetPassword;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import ResetPassword from "./ResetPassword"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(ResetPassword)

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react";
import { useState } from "react";
import {
Form,
Grid,
@@ -7,106 +7,97 @@ import {
Message,
Icon
} from "semantic-ui-react";
import { sendResetPassword } from "../features/user/userSlice";
import { Link } from "react-router-dom";
import { useDispatch } from 'react-redux'
const returnStyle = {
marginTop: "30px"
};
class SendResetPassword extends Component {
constructor(props) {
super(props);
this.handleSendResetPassword = this.handleSendResetPassword.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
state = {
checkInputMessages: "",
email: "",
emailError: false,
sentReset: false
const SendResetPassword = () => {
const dispatch = useDispatch();
const [checkInputMessages, setCheckInputMessages] = useState("");
const [email, setEmail] = useState("");
const [emailError, setEmailError] = useState(false);
const [resetSent, setResetSent] = useState(false);
const returnStyle = {
marginTop: "30px"
};
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleSendResetPassword(event) {
if (this.state.email === "") {
this.setState({ emailError: true, checkInputMessages: "Email is required"});
const handleSendResetPassword = async () => {
if (email === "") {
setEmailError(true);
setCheckInputMessages("Email is required");
} else {
this.setState({ emailError: false });
this.props.sendResetPassword({email: this.state.email}).then(resetMessage => {
if (resetMessage) {
// report to the user is there was a problem during login
this.setState({resetSent: false, checkInputMessages: resetMessage, emailError: true});
} else {
this.setState({resetSent: true});
const result = await dispatch(sendResetPassword({ email })).unwrap();
if (result.success) {
setResetSent(true);
setEmailError(true);
} else {
setCheckInputMessages(result.message);
console.error(result);
}
});
}
}
render() {
var resetMessage = "";
if (this.state.checkInputMessages.length) {
resetMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
};
{this.state.checkInputMessages}
</Message.Content>
</Message>
);
}
return (
<Grid verticalAlign='middle' centered>
<Grid.Row>
<Grid.Column width="6">
{ !this.state.resetSent ? (
<div>
<h2>Forgot your password?</h2>
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
Enter the email address for your account.
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={this.handleInputChange}
error={this.state.emailError}
label="Email"
placeholder="Email..."
/>
</Form.Field>
<Button
size="large"
content="Reset Password"
onClick={this.handleSendResetPassword}
fluid
/>
</Form>
{resetMessage}
</div>
)
:( <Segment raised padded='very'>
<h2>Reset Link Sent </h2>
<div>An email with a link to reset your password was sent to: {this.state.email}</div>
<Link to="/" style={returnStyle}><Icon name="left arrow"/>Back to OpenMHz</Link>
</Segment>
)}
</Grid.Column>
</Grid.Row>
</Grid>
var resetMessage = "";
if (checkInputMessages.length) {
resetMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
{checkInputMessages}
</Message.Content>
</Message>
);
}
return (
<Grid verticalAlign="middle" centered>
<Grid.Row>
<Grid.Column width={6}>
{!resetSent ? (
<div>
<h2>Forgot your password?</h2>
<Form className="raised padding segment">
Enter the email address for your account.
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={e => setEmail(e.target.value)}
value={email}
error={emailError}
label="Email"
placeholder="Email..."
/>
</Form.Field>
<Button
size="large"
content="Reset Password"
onClick={handleSendResetPassword}
fluid
/>
</Form>
{resetMessage}
</div>
) : (
<Segment raised padded="very">
<h2>Reset Link Sent </h2>
<div>
An email with a link to reset your password was sent to: {email}
</div>
<Link to="/" style={returnStyle}>
<Icon name="left arrow" />
Back to OpenMHz
</Link>
</Segment>
)}
</Grid.Column>
</Grid.Row>
</Grid>
);
}
export default SendResetPassword;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import SendResetPassword from "./SendResetPassword"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(SendResetPassword)

View File

@@ -1,28 +1,25 @@
import React, { Component } from "react"
import {
Container,
Message,
Icon
} from "semantic-ui-react";
import React from "react";
import { Container, Message, Icon } from "semantic-ui-react";
import { useSelector } from 'react-redux'
class SentConfirmEmail extends Component {
const SentConfirmEmail = ({ user }) => {
const { email } = useSelector((state) => state.user);
return (
<div>
<Container text>
<Message icon>
<Icon name='envelope outline' />
<Message.Content>
<Message.Header>Confirmation email sent!</Message.Header>
<p>
We sent an email to the address you gave us: {email}<br />
Please click on the link in the email to verify the email address.
</p>
</Message.Content>
</Message>
</Container>
</div>
);
};
render() {
return (
<div>
<Container text>
<Message icon>
<Icon name='envelope outline' />
<Message.Content>
<Message.Header>Confirmation email sent!</Message.Header>
<p>We sent an email to the address you gave us: {this.props.user.email}<br />
Please click on the link in the email to verify the email address.</p>
</Message.Content>
</Message>
</Container>
</div>
)
}
}
export default SentConfirmEmail
export default SentConfirmEmail;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import SentConfirmEmail from "./SentConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(SentConfirmEmail)

View File

@@ -1,84 +1,63 @@
import React, { Component } from "react";
import {
Segment,
Container,
Button
} from "semantic-ui-react";
import React, { useState } from "react";
import { Segment, Container, Button } from "semantic-ui-react";
class Terms extends Component {
constructor(props) {
super(props);
this.handleAccept = this.handleAccept.bind(this);
this.handleDecline = this.handleDecline.bind(this);
}
const Terms = ({ user, acceptTerms }) => {
const [requestMessage, setRequestMessage] = useState(null);
const handleAccept = () => {
acceptTerms(user.userId).then((requestMessage) => {
if (requestMessage) {
setRequestMessage(requestMessage);
}
});
};
const handleDecline = () => {};
handleAccept() {
this.props
.acceptTerms(this.props.user.userId)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
}
});
}
return (
<div>
<Container text>
<Segment attached>
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
handleDecline() {
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
And more
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
}
What is a Terms of Use
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
render() {
return (
<div>
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
<Container text>
<Segment attached>
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
The definition of a Terms of Use agreement is simple:
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
And more
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
What is a Terms of Use
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
Wikipedia
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
The definition of a Terms of Use agreement is simple:
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
Wikipedia
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
Terms of Use (ToU)
Terms of Service (ToS)
Terms and Conditions (T&C)
User Agreement
Conditions of Use
And so on
The “Conditions of Use” of Amazon could be named “Terms of Use”:
Terms of Use (ToU)
Terms of Service (ToS)
Terms and Conditions (T&amp;C)
User Agreement
Conditions of Use
And so on
The “Conditions of Use” of Amazon could be named “Terms of Use”:
</Segment>
<Button.Group attached="bottom">
<Button
onClick={this.handleDecline}
>Decline</Button>
<Button.Or />
<Button positive
onClick={this.handleAccept}
>Accept</Button>
</Button.Group>
</Container>
</div>
);
}
}
<Button.Group attached="bottom">
<Button onClick={handleDecline}>Decline</Button>
<Button.Or />
<Button positive onClick={handleAccept}>
Accept
</Button>
</Button.Group>
</Container>
</div>
);
};
export default Terms;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import * as userActions from "./user-actions"
import Terms from "./Terms"
function mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(Terms)

View File

@@ -1,4 +1,5 @@
import React, { Component } from "react";
import React, { useEffect, useState, } from "react";
import {
Form,
Dimmer,
@@ -10,270 +11,266 @@ import {
// ----------------------------------------------------
class UserForm extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.checkInputs = this.checkInputs.bind(this);
this.render = this.render.bind(this)
this.state = {
checkInputMessages: [],
registerMessages: "",
firstName: "",
lastName: "",
screenName: "",
email: "",
location: "",
password: "",
confirmPassword: "",
firstNameError: false,
lastNameError: false,
screenNameError: false,
emailError: false,
locationError: false,
passwordError: false,
confirmPasswordError: false,
isEditing: this.props.isEditing
};
if (this.props.isEditing) {
this.state.firstName = this.props.user.firstName;
this.state.lastName = this.props.user.lastName;
this.state.screenName = this.props.user.screenName;
this.state.email = this.props.user.email;
this.state.location = this.props.user.location;
const UserForm = (props) => {
const [checkInputMessages, setCheckInputMessages] = useState([]);
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [screenName, setScreenName] = useState("");
const [email, setEmail] = useState("");
const [location, setLocation] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [firstNameError, setFirstNameError] = useState(false);
const [lastNameError, setLastNameError] = useState(false);
const [screenNameError, setScreenNameError] = useState(false);
const [emailError, setEmailError] = useState(false);
const [locationError, setLocationError] = useState(false);
const [passwordError, setPasswordError] = useState(false);
const [confirmPasswordError, setConfirmPasswordError] = useState(false);
const [isEditing] = useState(props.isEditing);
const [changed, setChanged] = useState(false);
const requestMessage = props.requestMessage;
useEffect(() => {
if (props.isEditing) {
setFirstName(props.user.firstName);
setLastName(props.user.lastName);
setScreenName(props.user.screenName);
setEmail(props.user.email);
setLocation(props.user.location);
setChanged(false);
}
}
}, [props.isEditing, props.user]);
checkInputs() {
const checkInputs = () => {
let error = false;
var checkInputMessages = [];
let inputMessages = [];
if (this.state.firstName === "") {
this.setState({ firstNameError: true });
checkInputMessages.push("First Name is required");
if (firstName === "") {
setFirstNameError(true);
inputMessages.push("First Name is required");
error = true;
} else {
this.setState({ firstNameError: false });
setFirstNameError(false);
}
if (this.state.lastName === "") {
this.setState({ lastNameError: true });
checkInputMessages.push("Last Name is required");
if (lastName === "") {
setLastNameError(true);
inputMessages.push("Last Name is required");
error = true;
} else {
this.setState({ lastNameError: false });
setLastNameError(false);
}
if (this.state.email === "") {
this.setState({ emailError: true });
checkInputMessages.push("Email is required");
if (email === "") {
setEmailError(true);
inputMessages.push("Email is required");
error = true;
} else {
this.setState({ emailError: false });
setEmailError(false);
}
if (this.state.location === "") {
this.setState({ locationError: true });
checkInputMessages.push("Location is required");
if (location === "") {
setLocationError(true);
inputMessages.push("Location is required");
error = true;
} else {
this.setState({ locationError: false });
setLocationError(false);
}
if (this.state.screenName === "") {
this.setState({ screenNameError: true });
checkInputMessages.push("Screen Name is required");
if (screenName === "") {
setScreenNameError(true);
inputMessages.push("Screen Name is required");
error = true;
} else {
this.setState({ screenNameError: false });
setScreenNameError(false);
}
if (this.props.isEditing) {
this.setState({ checkInputMessages: checkInputMessages });
if (props.isEditing) {
setCheckInputMessages(checkInputMessages);
return error;
}
if (this.state.password === "") {
this.setState({ passwordError: true });
checkInputMessages.push("Password is required");
if (password === "") {
setPasswordError(true);
inputMessages.push("Password is required");
error = true;
} if (this.state.password.length < 7) {
this.setState({ passwordError: true });
checkInputMessages.push("Password must be 7 charecters or more");
} if (password.length < 7) {
setPasswordError(true);
inputMessages.push("Password must be 7 charecters or more");
error = true;
} else {
this.setState({ passwordError: false });
setPasswordError(false);
}
if (this.state.password !== this.state.confirmPassword) {
this.setState({ confirmPasswordError: true });
checkInputMessages.push("The passwords did not match");
if (password !== confirmPassword) {
setConfirmPasswordError(true);
inputMessages.push("The passwords did not match");
error = true;
} else {
this.setState({ confirmPasswordError: false });
setConfirmPasswordError(false);
}
this.setState({ checkInputMessages: checkInputMessages });
setCheckInputMessages(inputMessages);
return error;
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleSubmit(event) {
const handleSubmit = (event) => {
event.preventDefault();
let inputError = this.checkInputs();
let inputError = checkInputs();
if (!inputError) {
const user = (({ firstName, lastName, screenName, location, email, password }) => ({ firstName, lastName, screenName, location, email, password }))(this.state);
this.props.onSubmit(user);
const user ={ firstName, lastName, screenName, location, email, password };
setChanged(false);
props.onSubmit(user);
}
}
render() {
var registerMessage = "";
var floatStyle = {
overflow: 'auto'
}
var dimmerProps = {};
if (this.props.isWaiting) {
dimmerProps["active"] = true;
}
const { isEditing } = this.props;
if (this.state.checkInputMessages.length || this.props.requestMessage.length) {
var counter = 0;
registerMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{this.state.checkInputMessages.map(function (listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{this.props.requestMessage.length > 0 &&
<li key="req-mess">{this.props.requestMessage}</li>
}
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form
className="raised padding segment"
onSubmit={this.handleSubmit}
>
<Dimmer {...dimmerProps}>
<Loader indeterminate>Updating Profile</Loader>
</Dimmer>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="firstName"
onChange={this.handleInputChange}
error={this.state.firstNameError}
defaultValue={this.state.firstName}
label="First name"
placeholder="First Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="lastName"
onChange={this.handleInputChange}
error={this.state.lastNameError}
defaultValue={this.state.lastName}
label="Last name"
placeholder="Last Name..."
/>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={this.handleInputChange}
error={this.state.emailError}
defaultValue={this.state.email}
disabled={isEditing}
label="Email"
placeholder="Email..."
/>
var registerMessage = "";
var floatStyle = {
overflow: 'auto'
}
var dimmerProps = {};
if (props.isWaiting) {
dimmerProps["active"] = true;
}
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="screenName"
onChange={this.handleInputChange}
error={this.state.screenNameError}
defaultValue={this.state.screenName}
label="Screen Name"
placeholder="Screen Name..."
/>
<p style={{ fontStyle: "italic" }}>
Shown on OpenMHz for each of your systems
</p>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="location"
onChange={this.handleInputChange}
error={this.state.locationError}
defaultValue={this.state.location}
label="General Location"
placeholder="City, State..."
/>
</Form.Field>
</Form.Group>
{isEditing !== true && (
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={this.handleInputChange}
error={this.state.passwordError}
disabled={isEditing}
label="Password"
placeholder="Password..."
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={this.handleInputChange}
error={this.state.confirmPasswordError}
disabled={isEditing}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
)}
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue'>
{isEditing !== true ? 'Register' : 'Update'}
</Button>
</div>
</Form>
{registerMessage}
</div>
if (checkInputMessages.length || requestMessage.length) {
var counter = 0;
registerMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{checkInputMessages.map(function (listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{requestMessage.length > 0 &&
<li key="req-mess">{requestMessage}</li>
}
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form
className="raised padding segment"
onSubmit={handleSubmit}
>
<Dimmer {...dimmerProps}>
<Loader indeterminate>Updating Profile</Loader>
</Dimmer>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="firstName"
onChange={e => {setFirstName(e.target.value); setChanged(true)}}
error={firstNameError}
value={firstName}
label="First name"
placeholder="First Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="lastName"
onChange={e => {setLastName(e.target.value); setChanged(true)}}
error={lastNameError}
value={lastName}
label="Last name"
placeholder="Last Name..."
/>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="email"
onChange={e => {setEmail(e.target.value); setChanged(true)}}
error={emailError}
value={email}
disabled={isEditing}
label="Email"
placeholder="Email..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="screenName"
onChange={e => {setScreenName(e.target.value); setChanged(true)}}
error={screenNameError}
value={screenName}
label="Screen Name"
placeholder="Screen Name..."
/>
<p style={{ fontStyle: "italic" }}>
Shown on OpenMHz for each of your systems
</p>
</Form.Field>
</Form.Group>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="location"
onChange={e => {setLocation(e.target.value); setChanged(true)}}
error={locationError}
value={location}
label="General Location"
placeholder="City, State..."
/>
</Form.Field>
</Form.Group>
{isEditing !== true && (
<Form.Group widths="equal">
<Form.Field>
<Form.Input
name="password"
type="password"
onChange={e => {setPassword(e.target.value); setChanged(true)}}
value={password}
error={passwordError}
disabled={isEditing}
label="Password"
placeholder="Password..."
/>
</Form.Field>
<Form.Field>
<Form.Input
name="confirmPassword"
type="password"
onChange={e => {setConfirmPassword(e.target.value); setChanged(true)}}
value={confirmPassword}
error={confirmPasswordError}
disabled={isEditing}
label="Confirm Password"
placeholder="Confirm Password..."
/>
</Form.Field>
</Form.Group>
)}
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login" color='blue' disabled={!changed}>
{isEditing !== true ? 'Register' : 'Update'}
</Button>
</div>
</Form>
{registerMessage}
</div>
);
}
export default UserForm;

View File

@@ -1,52 +1,42 @@
import React, { Component } from "react"
import {
Container,
Header,
Button,
Message,
Icon
} from "semantic-ui-react";
class WaitConfirmEmail extends Component {
constructor(props) {
super(props);
this.handleSendConfirmEmail = this.handleSendConfirmEmail.bind(this);
import { sendConfirmEmail } from "../features/user/userSlice";
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux'
import { Container, Header, Button, Message, Icon } from "semantic-ui-react";
}
handleSendConfirmEmail(event) {
this.props.sendConfirmEmail(this.props.user.userId).then(loginMessage => {
if (loginMessage) {
// report to the user is there was a problem during login
this.setState({
loginMessage
});
}
});
const WaitConfirmEmail = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { email, userId } = useSelector((state) => state.user);
const handleSendConfirmEmail = async () => {
await dispatch(sendConfirmEmail(userId)).unwrap();
navigate("/sent-confirm-email")
};
render() {
return(
<Container text>
<Message icon>
<Icon name='envelope outline'/>
<Message.Content>
<Message.Header>Confirm your email address</Message.Header>
<p>We sent an email to the address you gave us: {this.props.user.email}<br/>
Please click on the link in the email to verify the email address.</p>
<Header as='h3'>Didn't get an email?</Header>
<p>So, you have waited a bit and haven't gotten anything?</p>
return (
<Container text>
<Message icon>
<Icon name="envelope outline" />
<Message.Content>
<Message.Header>Confirm your email address</Message.Header>
<p>
We sent an email to the address you gave us: {email}
<br />
Please click on the link in the email to verify the email address.
</p>
<Header as="h3">Didn't get an email?</Header>
<p>So, you have waited a bit and haven't gotten anything?</p>
<p>...and you checked your spam folder?</p>
<p>Well, click below.</p>
<Button
size="large"
content="Resend Email Confirmation"
onClick={this.handleSendConfirmEmail}
/>
</Message.Content>
</Message>
<Button
size="large"
content="Resend Email Confirmation"
onClick={handleSendConfirmEmail}
/>
</Message.Content>
</Message>
</Container>
);
};
</Container>
)
}
}
export default WaitConfirmEmail
export default WaitConfirmEmail;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import WaitConfirmEmail from "./WaitConfirmEmail"
import * as userActions from "./user-actions"
const mapStateToProps = (state) => {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
userActions
)(WaitConfirmEmail)

View File

@@ -0,0 +1,185 @@
// Import the RTK Query methods from the React-specific entry point
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
// 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" }),
providesTags: (result, error, arg) =>
result
? [...result.map(({ _id }) => ({ type: 'Group', _id })), 'Group']
: ['Group'],
}),
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
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;
dispatch(
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
groups.push(newGroup);
})
)
} catch { }
},
}),
deleteGroup: builder.mutation({
query: (group) => ({
url: `/groups/${group.shortName}/${group._id}`,
method: 'DELETE',
credentials: "include"
}),
async onQueryStarted(group, { dispatch, queryFulfilled }) {
try {
await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => {
const groupIndex = groups.findIndex((obj => obj.groupId === group.groupId));
groups.splice(groupIndex,1);
})
)
} catch { }
},
}),
saveGroupOrder: builder.mutation({
query: ({shortName, order}) => ({
url: `/groups/${shortName}/reorder`,
method: 'POST',
credentials: "include",
body: order,
}),
//invalidatesTags: (result, error, group) => ['Group'],
}),
importTalkgroups: builder.mutation({
query: ({shortName,file}) => ({
url: `${process.env.REACT_APP_ADMIN_SERVER}/talkgroups/${shortName}/import`,
method: 'POST',
credentials: "include",
body: file,
}),
async onQueryStarted({shortName,file}, { dispatch, queryFulfilled }) {
try {
const { data: newTalkgroups } = await queryFulfilled;
dispatch(
apiSlice.util.updateQueryData('getTalkgroups', shortName, (talkgroups) => {
talkgroups.length = 0; // Clear your array
talkgroups.push(...newTalkgroups);
})
)
} catch { }
},
//invalidatesTags: (result, error, group) => ['Group'],
}),
getSystems: builder.query({
// The URL for the request is '/fakeApi/posts'
query: () => ({ url: `/systems`, credentials: "include" })
}),
createSystem: builder.mutation({
query: (system) => ({
url: `/systems`,
method: 'POST',
credentials: "include",
body: system,
}),
async onQueryStarted(system, { dispatch, queryFulfilled }) {
try {
const { data: newSystem } = await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
systems.push(newSystem);
})
)
} catch {}
},
}),
updateSystem: builder.mutation({
query: (system) => ({
url: `/systems/${system.shortName}`,
method: 'POST',
credentials: "include",
body: system,
}),
async onQueryStarted(system, { dispatch, queryFulfilled }) {
try {
const { data: updatedSystem } = await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
const systemIndex = systems.findIndex((obj => obj.shortName === updatedSystem.shortName));
systems[systemIndex] = updatedSystem;
})
)
} catch {}
},
}),
deleteSystem: builder.mutation({
query: (shortName) => ({
url: `/systems/${shortName}`,
method: 'DELETE',
credentials: "include"
}),
async onQueryStarted(shortName, { dispatch, queryFulfilled }) {
try {
await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
const systemIndex = systems.findIndex((obj => obj.shortName === shortName));
systems.splice(systemIndex, 1);
})
)
} catch {}
},
}),
})
})
// Export the auto-generated hook for the `getPosts` query endpoint
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice

View File

@@ -0,0 +1,256 @@
import {
createSlice,
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 loginUser = createAsyncThunk(
'user/login',
async (data) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/login"
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data)
}).then(
(data) => data.json()
)
return res;
}
)
export const registerUser = createAsyncThunk(
'user/register',
async (data) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/register"
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data)
}).then(
(data) => data.json()
)
return res;
}
)
export const updateProfile = createAsyncThunk(
'user/updateProfile',
async (user) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + user.userId;
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user)
}).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 confirmEmail = createAsyncThunk(
'user/confirmEmail',
async ({userId, token}) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/confirm/" + token;
const res = await fetch(url, {
method: 'POST'
}).then(
(data) => data.json()
)
return res;
}
)
export const sendConfirmEmail = createAsyncThunk(
'user/sendConfirmEmail',
async (userId) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/send-confirm";
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
}
}).then(
(data) => data.json()
)
return res;
}
)
export const resetPassword = createAsyncThunk(
'user/resetPassword',
async (data) => {
const {userId, token, password} = data;
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + userId + "/reset-password/" + token
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({password})
}).then(
(data) => data.json()
)
return res;
}
)
export const sendResetPassword = createAsyncThunk(
'user/sendResetPassword',
async (data) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/api/send-reset-password"
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data)
}).then(
(data) => data.json()
)
return res;
}
)
export const userSlice = createSlice({
name: 'user',
initialState: {
isWaiting: false,
authenticated: false,
hasAuthenticated: false,
userId: "",
admin: false,
email: "",
firstName: "",
lastName: "",
location: "",
screenName: ""
},
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: {
[loginUser.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
state.authenticated = true;
state.hasAuthenticated = true;
state.userId = payload.user.userId;
state.admin = payload.user.admin;
state.email = payload.user.email;
state.firstName = payload.user.firstName;
state.lastName = payload.user.lastName;
state.location = payload.user.location;
state.screenName = payload.user.screenName;
} else {
state.authenticated = false;
state.hasAuthenticated = true;
}
},
[updateProfile.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
state.firstName = payload.user.firstName;
state.lastName = payload.user.lastName;
state.location = payload.user.location;
state.screenName = payload.user.screenName;
}
},
[loginUser.rejected]: (state, { payload }) => {
state.authenticated = false;
state.hasAuthenticated = true;
},
[authenticateUser.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
state.authenticated = true;
state.hasAuthenticated = true;
state.userId = payload.user.userId;
state.admin = payload.user.admin;
state.email = payload.user.email;
state.firstName = payload.user.firstName;
state.lastName = payload.user.lastName;
state.location = payload.user.location;
state.screenName = payload.user.screenName;
} 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;
}
},
[loginUser.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,48 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { Route, Switch } from 'react-router' // react-router v4/v5
import { ConnectedRouter } from 'connected-react-router'
import configureStore, { history } from './redux-router/configureStore'
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from "react-router-dom";
import setupStore from './redux-router/configureStore'
import 'semantic-ui-css/semantic.min.css'
import App from "./App/App"
import Restricted from "./App/Restricted"
import LoginContainer from "./User/LoginContainer"
import RegisterContainer from "./User/RegisterContainer"
import ProfileContainer from "./User/ProfileContainer"
import ConfirmEmailContainer from "./User/ConfirmEmailContainer"
import WaitConfirmEmailContainer from "./User/WaitConfirmEmailContainer"
import SentConfirmEmailContainer from "./User/SentConfirmEmailContainer"
import SendResetPasswordContainer from "./User/SendResetPasswordContainer"
import ResetPasswordContainer from "./User/ResetPasswordContainer"
import TermsContainer from "./User/TermsContainer"
const store = configureStore(/* provide initial state if any */)
const store = setupStore(/* provide initial state if any */)
console.log(process.env);
//This thssi
ReactDOM.render(
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<>
<App>
<Switch>
<Route exact path="/" component={Restricted(ProfileContainer, store)} />
<Route path="/login" component={LoginContainer} />
<Route path="/register" component={RegisterContainer} />
<Route path="/wait-confirm-email" component={WaitConfirmEmailContainer} />
<Route path="/sent-confirm-email" component={SentConfirmEmailContainer} />
<Route path="/send-reset-password" component={SendResetPasswordContainer} />
<Route path="/reset-password/:userId/:token" component={ResetPasswordContainer} />
<Route path="/confirm-email/:userId/:token" component={ConfirmEmailContainer} />
<Route path="/terms" component={Restricted(TermsContainer, store)} />
<Route path="/profile" component={Restricted(ProfileContainer, store)} />
</Switch>
</App>
</>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
)

View File

@@ -1,26 +1,20 @@
// https://github.com/supasate/connected-react-router
// configureStore.js
import { configureStore } from '@reduxjs/toolkit'
import { userReducer } from '../features/user/userSlice'
import { apiSlice } from "../features/api/apiSlice"
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from './reducers'
import thunk from 'redux-thunk';
export const history = createBrowserHistory()
export default function configureStore(preloadedState) {
const store = createStore(
createRootReducer(history), // root reducer with router state
const setupStore = (preloadedState) => {
const store = configureStore({
reducer: {
user: userReducer,
[apiSlice.reducerPath]: apiSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
// adding the api middleware enables caching, invalidation, polling and other features of `rtk-query`
getDefaultMiddleware().concat(apiSlice.middleware),
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
thunk,
// ... other middlewares ...
),
),
)
})
return store
}
}
export default setupStore;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -2,16 +2,15 @@ const path = require("path");
const express = require("express");
const session = require("express-session");
const bodyParser = require("body-parser");
const connectMongo = require("connect-mongo");
const MongoStore = require("connect-mongo");
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 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 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 dev_server = frontend_server + ":3000"
var dev_server = "http://admin.openmhz.test:3000"
module.exports = function(app, passport) {
app.set("port", 3008)
@@ -36,9 +35,12 @@ module.exports = function(app, passport) {
secure: false,
domain: cookie_domain
},
store: new MongoStore({
url: secrets.db,
autoReconnect: true
store: MongoStore.create({
mongoUrl: secrets.db,
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)
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user)
})
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id).exec();
if (user) {
done(null,user);
} else {
done("User not found", null);
}
})

View File

@@ -4,286 +4,246 @@ const mongoose = require("mongoose");
//router.get('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
exports.getGroups = function(req, res, next) {
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;
}
exports.getGroups = async function (req, res, next) {
process.nextTick(async function () {
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
if (!system) {
res.status(404);
res.json({ success: false });
return;
}
if (req.params.groupId) {
var groupId = req.params.groupId.toLowerCase();
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;
}
Group.findOne(
{
_id: new mongoose.Types.ObjectId(groupId),
shortName: req.params.shortName.toLowerCase()
},
{ sort: { position: -1 } },
function(err, group) {
if (err) {
res.json({
success: false,
message: err
});
return;
} else {
res.json({
success: true,
group: group
});
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;
}
}
);
}
if (req.params.groupId) {
var groupId = req.params.groupId.toLowerCase();
const group = await Group.findOne(
{
_id: new mongoose.Types.ObjectId(groupId),
shortName: req.params.shortName.toLowerCase()
},
{ sort: { position: -1 } });
if (!group) {
res.status(404);
res.json({ success: false });
return;
} else {
res.json(group);
return;
}
);
});
} else {
const group = await Group.find(
{
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) {
//router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) {
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;
}
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.deleteGroup = async function (req, res, next) {
const system = await System.findOne({ 'shortName': req.params.shortName.toLowerCase() }).catch(err => {
console.error(err);
res.status(500);
res.json({ success: false, message: err });
return;
});
if (!system) {
console.error(err);
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;
}
await Group.findOneAndRemove({
shortName: req.params.shortName.toLowerCase(),
'_id': new mongoose.Types.ObjectId(req.params.groupId)
}).catch(err => {
res.status(500);
res.json({
success: false,
message: err
});
return;
});
exports.reorderGroups = function(req, res, next) {
res.json({
success: true
});
return;
}
exports.reorderGroups = function (req, res, next) {
console.log(req.body)
//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;
}
process.nextTick(async function () {
const system = await System.findOne({ shortName: req.params.shortName.toLowerCase() });
var groupOrder = JSON.parse(req.body.groupOrder);
for (var i = 0; i < groupOrder.length; i++) {
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;
}
console.log(req.body)
if (!req.body.groupOrder) {
res.status(500);
res.json({
success: false,
message: "Order data is wrong."
});
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({
success: true
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) {
exports.upsertGroup = function (req, res, next) {
console.log(req.body)
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
process.nextTick(function() {
System.findOne(
process.nextTick(async function () {
console.log(req.body)
let 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 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 = await Group.findOneAndUpdate(
{
_id: new mongoose.Types.ObjectId(groupId),
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 message = "";
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
var talkgroups = JSON.parse(req.body.talkgroups);
{
groupName: groupName,
talkgroups: talkgroups
},
{
new: true,
upsert: true
}).catch(err => {
res.status(500);
res.json({
success: false,
message: err
});
return;
});
res.json(group);
return;
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;
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

@@ -1,241 +0,0 @@
const mongoose = require("mongoose");
const Permission = require("../models/permission");
const System = require("../models/system");
const User = require("../models/user");
exports.fetchPermissions = function(req, res, next) {
Permission.find(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, permissions) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
var userIds = permissions.map(permission => {return permission.userId});
User.find(
{
_id: {
$in: userIds
}
},
function(err, users) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
var returnUsers = users.map(user =>{
var permission = permissions.find(obj => {
if (obj.userId.toString() == user._id.toString()) {
return true;
}
return false;
});
if (!permission) {
console.error("Error - Permission not found for user: " + user._id);
res.json({ success: false, message: "Permission not found" });
return;
}
return {_id: permission._id, userId: user._id, firstName: user.firstName, lastName: user.lastName, email: user.email, role: permission.role };
});
res.json({ success: true, permissions: returnUsers });
});
return;
}
);
};
exports.addPermission = async (req, res, next) => {
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
let role = req.body.role;
var system = false;
var user = false;
var existing = false;
var saved = false;
if (!role || (role >= 20) || (role <1)) {
res.json({
success: false,
message: "Invalid Role"
});
return;
}
console.log("role looks good");
try {
system = await System.findOne(
{
shortName: req.params.shortName.toLowerCase()
});
} catch(err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
res.json({
success: false,
message: "That System does not exist."
});
return;
}
console.log("system looks good")
try {
user = await User.findOne(
{
email: req.body.email
});
} catch (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!user) {
res.json({ success: false, message: "User not found" });
return;
}
console.log("user looks good")
try {
existing = await Permission.findOne({userId: user._id, systemId: system._id});
} catch (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (existing) {
res.json({ success: false, message: "User already has acccess" });
return;
}
console.log("no existing")
var permission = new Permission({
userId: user._id,
systemId: system._id,
shortName: system.shortName,
role: role
});
try {
saved = await permission.save()
} catch(err) {
res.json({
success: false,
message: err
});
return;
}
console.log("saved it")
var newPermission = saved.toObject();
newPermission.firstName = user.firstName;
newPermission.lastName = user.lastName;
newPermission.email = user.email;
console.log("Add Permission to: " + system.shortName + " for: " + newPermission.email + " by: " + req.user.email);
res.json({
success: true,
permission: newPermission
});
};
exports.updatePermission = function(req, res, next) {
process.nextTick(function() {
Permission.findOne(
{
_id: new mongoose.Types.ObjectId(req.params.permissionId),
shortName: req.params.shortName.toLowerCase(req.params.shortName)
},
function(err, permission) {
if (err) {
res.json({
success: false,
message: err
});
return;
}
if (permission.role >= 20) {
res.json({
success: false,
message: "Cannot change permissions of system owner"
});
return;
}
var newRole = req.body.role;
if (!newRole || (newRole >= 20) || (newRole <1)) {
res.json({
success: false,
message: "Invalid Role"
});
return;
}
permission.role = newRole;
permission.save(function(err, saved) {
if (err) {
res.json({
success: false,
message: err
});
return;
}
res.json({
success: true,
permission: saved.toJSON()
});
return;
});
}
);
});
};
exports.deletePermission = function(req, res, next) {
//router.get('/system/:shortName/remove_group/:groupId', isLoggedIn, function(req, res) {
Permission.findOneAndRemove({
shortName: req.params.shortName.toLowerCase(),
'_id': new mongoose.Types.ObjectId(req.params.permissionId),
role: { $lt : 20}
}, function(err, permission) {
if (err) {
res.json({
success: false,
message: err
});
return;
}
if (!permission) {
res.json({
success: false,
message: "Permission not found"
});
return;
} else {
res.json({
success: true
});
return;
}
// removed!
});
};

View File

@@ -12,173 +12,132 @@ 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);
System.find({
userId: mongoose.Types.ObjectId(req.user._id)
},
function (err, systems) {
if (err) {
console.error("Error - ListSystems: " + err);
res.json({
success: false,
message: err
});
return;
}
const userId = new mongoose.Types.ObjectId(req.user._id);
const systems = await System.find({ userId: userId }).exec();
if (systems == null) {
res.status(400);
res.json({ success: false, message: err });
return;
}
var shortNames = new Array();
for (var i = 0; i < systems.length; i++) {
shortNames.push(systems[i].shortName);
}
let shortNames = new Array();
for (var i = 0; i < systems.length; i++) {
shortNames.push(systems[i].shortName);
}
SystemStat.find({
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;
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;
}
);
let 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;
});
const stats = await SystemStat.find({ shortName: { $in: shortNames } }).exec();
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].uploadErrors = stats[i].uploadErrors;
sys_stats[stats[i].shortName].talkgroupStats= stats[i].talkgroupStats;
sys_stats[stats[i].shortName].decodeErrorsFreq = stats[i].decodeErrorsFreq;
}
res.json({
success: true,
systems: returnSys,
stats: sys_stats
});
return;
};
function remove_system(shortName) {
/*
var sysDir = config.mediaDirectory + "/" + shortName + "/"
console.log("Removing DIR: " + sysDir);
fs.remove(sysDir, function(err) {
if (err) return console.error(err)
});
const remove_system = async (shortName) => {
Transmission.remove({
shortName: shortName
}, function(err) {
if (err) return console.error(err);
// removed!
});
*/
System.remove({
shortName: shortName
},
function (err) {
if (err) return console.error(err);
// removed!
}
);
await System.deleteOne({
shortName: shortName
}).catch(err => console.error(err));
Talkgroup.remove({
shortName: shortName
},
function (err) {
if (err) return console.error(err);
// removed!
}
);
await Talkgroup.deleteMany({
shortName: shortName
}).catch(err => console.error(err));
Group.remove({
shortName: shortName
},
function (err) {
if (err) return console.error(err);
// removed!
}
);
await Group.deleteMany({
shortName: shortName
}).catch(err => console.error(err));
}
exports.deleteSystem = function (req, res, next) {
exports.deleteSystem = async function (req, res, next) {
//router.get('/delete_system/:shortName', isLoggedIn, function(req, res) {
System.findOne({
shortName: req.params.shortName.toLowerCase()
},
function (err, system) {
if (err) {
res.json({
success: false,
message: err
});
return;
}
if (!system) {
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id));
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
console.log("Removing: " + system.name);
remove_system(system.shortName);
res.json({
success: true
});
return;
}
);
};
var system = await System.findOne({
shortName: req.params.shortName.toLowerCase()
}).catch(err => {
res.status(500);
res.json({
success: false,
message: err
});
return;
});
if (!system) {
res.status(500);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id));
res.status(401);
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
console.log("Removing: " + system.name);
remove_system(system.shortName);
res.json({
success: true
});
return;
}
// -------------------------------------------
exports.ownSystem = async function (req, res, next) {
@@ -189,6 +148,7 @@ exports.ownSystem = async function (req, res, next) {
if (!system) {
console.error("ERROR: OwnSystem - system does not exist: " + req.body.shortName.toLowerCase());
res.status(500)
res.json({
success: false,
message: "That System does not exist."
@@ -197,7 +157,7 @@ exports.ownSystem = async function (req, res, next) {
}
if (!system.userId.equals(req.user._id)) {
console.error("ERROR: OwnSystem - not users system " + req.body.shortName.toLowerCase());
res.status(500)
res.json({
success: false,
message: "You are not the user associated with this system."
@@ -210,6 +170,7 @@ exports.ownSystem = async function (req, res, next) {
} catch (err) {
console.error("ERROR: ownSystem - caught: " + err);
res.status(500)
res.json({
success: false,
message: err
@@ -224,12 +185,13 @@ exports.uniqueShortName = async function (req, res, next) {
var shortName = res.locals.shortName;
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")
shortName = req.body.shortName;
}
if (typeof shortName == 'undefined') {
console.error("ShortName is not in req.body instead")
res.status(500)
res.json({
success: false,
message: "Short Name not provided in form"
@@ -243,6 +205,7 @@ exports.uniqueShortName = async function (req, res, next) {
if (system) {
console.error("ERROR: Unique Shortname already in use: " + req.body.shortName.toLowerCase());
res.status(500)
res.json({
success: false,
message: "Short Name already in use"
@@ -255,10 +218,8 @@ exports.uniqueShortName = async function (req, res, next) {
} catch (err) {
console.error("ERROR: Unique ShortName - caught: " + err);
res.json({
success: false,
message: err
});
res.status(500);
res.json({ message: "ERROR: Unique ShortName - caught: " + err });
return;
}
@@ -279,59 +240,58 @@ exports.updateSystem = async function (req, res, next) {
res.locals.system.showScreenName = res.locals.showScreenName;
res.locals.system.ignoreUnknownTalkgroup = res.locals.ignoreUnknownTalkgroup;
res.locals.system.save(function (err) {
if (err) {
res.json({
success: false,
message: err
});
return;
} else {
var returnSys = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}))(res.locals.system);
returnSys.id = res.locals.system._id;
res.json({
success: true,
system: returnSys
});
return;
}
await res.locals.system.save().catch(err => {
res.status(500);
res.json({
success: false,
message: err
});
return;
});
var returnSys = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName,
ignoreUnknownTalkgroup
}))(res.locals.system);
returnSys.id = res.locals.system._id;
res.json(returnSys);
return;
}
// -------------------------------------------
exports.validateSystem = async function (req, res, next) {
try {
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)) {
console.error("ERROR: Validate System - req.body.name");
res.status(500)
res.json({
success: false,
message: "System Name is Required"
@@ -342,6 +302,7 @@ exports.validateSystem = async function (req, res, next) {
if (!req.body.description || (req.body.description.length < 2)) {
console.error("ERROR: Validate System - req.body.description");
res.status(500)
res.json({
success: false,
message: "System Description is Required"
@@ -352,6 +313,7 @@ exports.validateSystem = async function (req, res, next) {
if (!req.body.shortName || (req.body.shortName.length < 2)) {
console.error("ERROR: Validate System - req.body.shortName");
res.status(500)
res.json({
success: false,
message: "System ShortName is Required"
@@ -367,6 +329,7 @@ exports.validateSystem = async function (req, res, next) {
// make sure it is one of the expected types
if ((req.body.systemType != "state") && (req.body.systemType != "city") && (req.body.systemType != "county") && (req.body.systemType != "international")) {
console.error("ERROR: Validate System - req.body.systemType: " + req.body.systemType);
res.status(500)
res.json({
success: false,
message: "Select system location"
@@ -383,6 +346,7 @@ exports.validateSystem = async function (req, res, next) {
req.body.systemType === "county") {
if (!req.body.state || (req.body.state === "") || (req.body.state.length < 2)) {
console.error("ERROR: Validate System - req.body.state: " + req.body.state);
res.status(500)
res.json({
success: false,
message: "Select the State for the System"
@@ -395,6 +359,7 @@ exports.validateSystem = async function (req, res, next) {
if (req.body.systemType === "city") {
if (!req.body.city || (req.body.city === "") || (req.body.city.length < 2)) {
console.error("ERROR: Validate System - req.body.systemType: " + req.body.systemType);
res.status(500)
res.json({
success: false,
message: "Enter the city for the System"
@@ -407,6 +372,7 @@ exports.validateSystem = async function (req, res, next) {
if (req.body.systemType === "county") {
if (!req.body.county || (req.body.county === "") || (req.body.county.length < 2)) {
console.error("ERROR: Validate System - req.body.county: " + req.body.county);
res.status(500)
res.json({
success: false,
message: "Enter the county for the System"
@@ -419,6 +385,7 @@ exports.validateSystem = async function (req, res, next) {
if (req.body.systemType === "international") {
if (!req.body.country || (req.body.country === "") || (req.body.country.length < 2)) {
console.error("ERROR: Validate System - req.body.country: " + req.body.country);
res.status(500)
res.json({
success: false,
message: "Enter the country for the System"
@@ -431,6 +398,7 @@ exports.validateSystem = async function (req, res, next) {
} catch (err) {
console.error("ERROR: Validate System - caught: " + err);
res.status(500)
res.json({
success: false,
message: err
@@ -457,7 +425,7 @@ exports.createSystem = async function (req, res, next) {
county,
country,
showScreenName,
ignoreUnknownTalkgroup
ignoreUnknownTalkgroup
}) => ({
name,
shortName,
@@ -468,51 +436,50 @@ exports.createSystem = async function (req, res, next) {
county,
country,
showScreenName,
ignoreUnknownTalkgroup
ignoreUnknownTalkgroup
}))(res.locals);
system.key = key;
system.userId = mongoose.Types.ObjectId(req.user._id);
System.create(system, function (err, newSys) {
if (err) {
console.error(err);
res.json({
success: false,
message: err
});
return;
}
var returnSys = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
showScreenName,
ignoreUnknownTalkgroup ,
key
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
showScreenName,
ignoreUnknownTalkgroup ,
key
}))(newSys);
returnSys.id = newSys._id;
system.userId = new mongoose.Types.ObjectId(req.user._id);
const newSys = await System.create(system).catch(err => {
console.error(err);
res.status(500)
res.json({
success: true,
system: returnSys
success: false,
message: err
});
return;
});
var returnSys = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
showScreenName,
ignoreUnknownTalkgroup,
key
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
showScreenName,
ignoreUnknownTalkgroup,
key
}))(newSys);
returnSys.id = newSys._id;
res.json(returnSys);
return;
}

View File

@@ -1,255 +1,220 @@
const Talkgroup = require("../models/talkgroup");
const System = require("../models/system");
const fs = require('fs-extra');
const {parse} = require('csv-parse');
const crypto = require("crypto");
var parse = require('csv-parse');
exports.isLoggedIn = function(req, res, next) {
exports.isLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect(account_server + "/login");
};
//router.get("/tg_export/:shortName", isLoggedIn, function(req, res) {
exports.exportTalkgroups = function(req, res, next) {
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;
}
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!
exports.exportTalkgroups = function (req, res, next) {
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;
}
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) {
exports.importTalkgroups = function(req, res, next) {
process.nextTick(function() {
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;
}
async function csv_import(shortName, filename, callback) {
var talkgroups = [];
const count = await Talkgroup.deleteMany({ shortName: shortName });
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.json({ success: false, message: err });
res.end();
return;
} else {
res.json({ success: true, talkgroups: data });
return;
}
});
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().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 = await 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) {
console.error("Error - File is bad. Size");
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 {
console.error("Error - importTalkgroups: no file");
res.end(
"Well, there is no magic for those who don’t believe in it!"
);
res.json( data );
return;
}
});
} 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) {
Talkgroup.find(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, talkgroups) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
res.json({ success: true, talkgroups: talkgroups });
return;
}
);
};
exports.fetchTalkgroups = async function (req, res, next) {
const talkgroups = await Talkgroup.find({ shortName: req.params.shortName.toLowerCase() }).catch(err => {
console.error(err);
res.status(500);
res.json({ success: false, message: err });
return;
});
res.json(talkgroups );
return;
}

View File

@@ -8,7 +8,7 @@ const configureExpress = require("./config/express");
const systems = require("./controllers/systems");
const groups = require("./controllers/groups");
const talkgroups = require("./controllers/talkgroups");
const Permission = require("./models/permission");
require("./models/user");
const multer = require('multer');
@@ -42,13 +42,7 @@ async function connect() {
// Connect to a MongoDB server running on 'localhost:27017' and use the
// 'test' database.
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true }, (err, res) => {
if (err) {
console.log(`Mongoose - Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Mongoose Successfully connected to ${secrets.db}.`)
}
})
await mongoose.connect(secrets.db, { useNewUrlParser: true, useUnifiedTopology: true });
console.log("All Done");
}
connect();
@@ -66,26 +60,6 @@ mongoose.connection.on('disconnected', () => {
const isDev = process.env.NODE_ENV === "development"
/*
async function getRole(req) {
let promise = new Promise((resolve, reject) => {
if (req.params.shortName && req.user) {
var short_name = req.params.shortName.toLowerCase();
var userId = req.user._id;
Permission.findOne({'userId': userId, 'shortName': short_name}, function(err, permission) {
if (err) {
console.error('Error - getRole userId: ' + userId + ' shortName: ' + short_name + ' error: ' + err);
reject(-1);
}
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
resolve(permission.role);
});
} else {
reject(-1);
}});
return promise;
}*/
@@ -141,6 +115,22 @@ function isLoggedIn(req, res, next) {
});
};
function uploadFile(req, res, next) {
const upload = multer({dest: 'uploads/'}).single('file');
upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
console.error("Multer Error: " + err)
} else if (err) {
console.error(err);
// An unknown error occurred when uploading.
}
console.log("Things look good for Multer")
// Everything went fine.
next()
})
}
// -------------------------------------------
configurePassport(app, passport)
@@ -150,13 +140,8 @@ configureExpress(app, passport)
app.use(express.static(path.join(__dirname, "public")));
app.get("")
//app.get("/permissions/:shortname", isOwner, permissions.fetchPermissions)
/*app.get("/permissions/:shortName", permissions.fetchPermissions);
app.delete("/permissions/:shortName/:permissionId", isAdmin, permissions.deletePermission);
app.post("/permissions/:shortName/:permissionId", isAdmin, permissions.updatePermission);
app.post("/permissions/:shortName", isAdmin, permissions.addPermission);*/
app.get("/talkgroups/:shortName", isLoggedIn, talkgroups.fetchTalkgroups)
app.post("/talkgroups/:shortName/import", isLoggedIn, upload.single('file'), talkgroups.importTalkgroups)
app.post("/talkgroups/:shortName/import", isLoggedIn, uploadFile, talkgroups.importTalkgroups)
app.get("/talkgroups/:shortName/export", isLoggedIn, talkgroups.exportTalkgroups)
app.post("/groups/:shortName/reorder", isLoggedIn, groups.reorderGroups);
app.post("/groups/:shortName/:groupId?", isLoggedIn, groups.upsertGroup);

View File

@@ -1,24 +0,0 @@
// Defining a User Model in mongoose
// Code modified from https://github.com/sahat/hackathon-starter
var mongoose = require("mongoose");
const PermissionSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
systemId: mongoose.Schema.Types.ObjectId,
shortName: String,
role: {
type: Number,
default: 0
},
ver: {
type: Number,
default: 1.2
}
})
module.exports=mongoose.model("Permission", PermissionSchema)

View File

@@ -84,4 +84,4 @@ UserSchema.pre("save", function(next) {
*/
UserSchema.statics = {}
module.exports = mongoose.model("User", UserSchema)
module.exports = mongoose.model("User", UserSchema)

View File

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

View File

@@ -1,55 +1,46 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Menu, Dropdown } from "semantic-ui-react";
import { useSelector, useDispatch } from 'react-redux'
import { logoutUser } from "../features/user/userSlice";
const navStyle = {
marginBottom: "30px"
};
const blankStyle = {
marginBottom: "50px"
};
class Navigation extends Component {
constructor(props) {
super(props);
this._logout = this._logout.bind(this);
}
const Navigation = (props) => {
const dispatch = useDispatch();
const { email } = useSelector((state) => state.user);
_logout = event => {
const logout = event => {
event.preventDefault();
this.props.manualLogout();
dispatch(logoutUser({}));
};
render() {
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
{this.props.user.authenticated ? (
<Menu style={navStyle}>
<Menu.Item name="systems" header>
{process.env.REACT_APP_SITE_NAME}
</Menu.Item>
<Menu.Menu position="right">
<Link to="/list-systems">
<Menu.Item name="systems"> Systems</Menu.Item>
</Link>
<Link to="/about">
<Menu.Item name="about">About</Menu.Item>
</Link>
<Dropdown item text={this.props.user.email}>
<Dropdown.Menu>
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
<Dropdown.Item onClick={this._logout}>Logout</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Menu.Menu>
</Menu>
) : (
<div style={blankStyle} />
)}
</div>
);
}
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
<Menu style={navStyle}>
<Menu.Item name="systems" header>
{process.env.REACT_APP_SITE_NAME}
</Menu.Item>
<Menu.Menu position="right">
<Link to="/list-systems">
<Menu.Item name="systems"> Systems</Menu.Item>
</Link>
<Link to="/about">
<Menu.Item name="about">About</Menu.Item>
</Link>
<Dropdown item text={ email }>
<Dropdown.Menu>
<Dropdown.Item href={profileLink}>Profile</Dropdown.Item>
<Dropdown.Item onClick={logout}>Logout</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</Menu.Menu>
</Menu>
</div>
);
}
export default Navigation;

View File

@@ -1,21 +0,0 @@
import { connect } from "react-redux"
import * as userActions from "../User/user-actions"
import Navigation from "./Navigation"
// Function passed in to `connect` to subscribe to Redux store updates.
// Any time it updates, mapStateToProps is called.
const mapStateToProps = (state) => {
return {
user: state.user
}
}
// Connects React component to the redux store
// It does not modify the component class passed to it
// Instead, it returns a new, connected component class, for you to use.
export default connect(
mapStateToProps,
userActions
)(Navigation)

View File

@@ -1,54 +1,29 @@
// in src/restricted.js
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import * as types from "../User/user-constants";
import axios from "axios";
import React, { useEffect} from "react";
import { useSelector, useDispatch } from 'react-redux'
import { authenticateUser } from "../features/user/userSlice";
/**
* Higher-order component (HOC) to wrap restricted pages
*/
const restricted = (BaseComponent, store) => {
class Restricted extends Component {
componentDidMount() {
this.checkAuthentication(this.props);
}
componentDidUpdate(prevProps) {
if (prevProps.location !== this.props.location) {
this.checkAuthentication(this.props);
}
}
const Restricted = ({children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const dispatch = useDispatch();
useEffect(() => {
dispatch(authenticateUser({}));
},[]);
checkAuthentication(params) {
const authenticated = store.getState().user.authenticated;
if (!authenticated) {
axios
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated", { withCredentials: true })
.then(response => {
if (response.data.success) {
const user = response.data.user
store.dispatch({
type: types.LOGIN_SUCCESS_USER,
data: user
});
} else {
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
/*history.replace({ pathname: '/login',
state: { nextPathname: params.location.pathname } });*/
}
})
.catch(response => {
if (response instanceof Error) {
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
// Something happened during logout that triggered an Error
console.log("Error", response.message);
}
});
}
}
render() {
return <BaseComponent {...this.props} />;
}
}
return withRouter(Restricted);
if (hasAuthenticated) {
if (authenticated) {
return children;;
}
else {
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
}
}
export default restricted
return <div/>
};
export default Restricted

View File

@@ -1,10 +1,9 @@
import React, { Component } from "react";
import { Container, Header } from "semantic-ui-react";
class About extends Component {
const About = () => {
render() {
return (
<Container text>
<Header size='large'>Share the Air</Header>
@@ -18,7 +17,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>
</Container>
);
}
}
export default About;

View File

@@ -1,31 +1,21 @@
import React, { Component } from "react";
import React from "react";
import { Modal, Icon, Button, Header } from "semantic-ui-react";
class Message extends Component {
constructor(props) {
super(props);
}
const Message = (props) => {
_logout = event => {
event.preventDefault();
this.props.manualLogout();
};
render() {
return (
<Modal open={this.props.open} onClose={this.props.onClose} basic size="small">
<Header icon="info" content={this.props.title} />
<Modal.Content>
<p>{this.props.message}</p>
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={this.props.onClose} inverted>
return (
<Modal open={props.open} onClose={props.onClose} basic size="small">
<Header icon="info" content={props.title} />
<Modal.Content>
<p>{props.message}</p>
</Modal.Content>
<Modal.Actions>
<Button color='green' onClick={props.onClose} inverted>
<Icon name='checkmark' /> Got it
</Button>
</Modal.Actions>
</Modal>
);
}
}
</Modal.Actions>
</Modal>
);
};
export default Message;

View File

@@ -1,4 +1,6 @@
import React, { Component } from "react";
import React, { useEffect, useState, } from "react";
import { useGetTalkgroupsQuery, useGetGroupsQuery, useUpdateGroupMutation, useCreateGroupMutation} from '../features/api/apiSlice'
import {
Grid,
Modal,
@@ -13,84 +15,75 @@ import {
// ----------------------------------------------------
class GroupModal extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClose = this.handleClose.bind(this);
const GroupModal = (props) => {
// Set initial state
this.state = {
editGroup: false,
groupName: "",
group: {
name: "",
items: []
},
talkgroups: this.props.talkgroups ? this.props.talkgroups : []
};
const emptyGroup = {
name: "",
items: []
}
componentDidMount() {
//this.props.groupActions.fetchGroups(this.props.shortName);
const [groupName, setGroupName] = useState("");
const [group, setGroup] = useState(emptyGroup)
const [talkgroups, setTalkgroups] = useState(props.talkgroups ? props.talkgroups : [])
this.props.talkgroupActions.fetchTalkgroups(this.props.shortName);
}
const { data: talkgroupsData} = useGetTalkgroupsQuery(props.shortName);
const { data: groupsData} = useGetGroupsQuery(props.shortName);
const [ updateGroup] = useUpdateGroupMutation();
const [ createGroup] = useCreateGroupMutation();
componentDidUpdate(prevProps) {
useEffect(() => {
if (talkgroupsData && groupsData && props.editGroupId) {
let editGroup = groupsData.find(
group => group._id === props.editGroupId
);
let items = [];
// This determines if you are trying to edit a group.
// In this case, the value of editGroupID will chane to the group being edited
if (this.props.talkgroups && this.props.groups && this.props.editGroupId && !prevProps.editGroupId) {
var editGroup = this.props.groups.find(
group => group._id === this.props.editGroupId
);
var items = [];
var talkgroups = this.props.talkgroups;
for (var i = 0; i < editGroup.talkgroups.length; i++) {
const talkgroupId = editGroup.talkgroups[i];
const index = talkgroups.findIndex(tg => tg.num === talkgroupId);
if (index >= 0) {
const item = {
num: talkgroups[index].num,
description: talkgroups[index].description
};
items.push(item);
talkgroups = talkgroups
.slice(0, index)
.concat(talkgroups.slice(index + 1));
}
let talkgroups = talkgroupsData.slice(0); //copy
for (var i = 0; i < editGroup.talkgroups.length; i++) {
const talkgroupNum = editGroup.talkgroups[i];
const index = talkgroups.findIndex(tg => tg.num === talkgroupNum);
if (index >= 0) {
const item = {
num: talkgroups[index].num,
description: talkgroups[index].description
};
items.push(item);
talkgroups = talkgroups
.slice(0, index)
.concat(talkgroups.slice(index + 1));
}
// we want to remove that are part of the Group from the list on the Left
const group = { name: editGroup.groupName, items: items }
this.setState({ group: group, talkgroups: talkgroups, groupName: editGroup.groupName });
}
if (this.props.talkgroups && !prevProps.talkgroups) {
// If we are not editing, make sure there is the full list on the left.
this.setState({ talkgroups: [...this.props.talkgroups] })
// we want to remove that are part of the Group from the list on the Left
const group = { name: editGroup.groupName, items: items }
setGroup(group);
setTalkgroups(talkgroups);
setGroupName(editGroup.groupName);
}
},[groupsData,talkgroupsData, props.editGroupId]);
// If we are done editing, clear out the modal
if (!this.props.editGroupId && (this.props.editGroupId !== prevProps.editGroupId)) {
this.setState({
groupName: "", group: {
name: "",
items: []
},
talkgroups: [...this.props.talkgroups]
});
useEffect(() => {
if (!props.editGroupId && talkgroupsData) {
setTalkgroups([...talkgroupsData]);
}
}
},[talkgroupsData, props.editGroupId]);
removeTalkgroup(event, index) {
useEffect(() => {
if (!props.editGroupId && props.talkgroups) {
setGroupName("");
setGroup(emptyGroup);
setTalkgroups( [...props.talkgroups]);
}
},[props.editGroupId, props.talkgroups]);
const removeTalkgroup = (index) => {
const item = {
num: this.state.group.items[index].num,
description: this.state.group.items[index].description
num: group.items[index].num,
description: group.items[index].description
};
const talkgroups = this.state.talkgroups.slice(0); // copy
let talkgroups = talkgroupsData.slice(0); // copy
for (var i = 0, len = talkgroups.length; i < len; i++) {
if (item.num < talkgroups[i].num) {
@@ -98,110 +91,75 @@ class GroupModal extends Component {
break;
}
}
this.setState({
group: {
items: this.state.group.items
.slice(0, index)
.concat(this.state.group.items.slice(index + 1))
},
talkgroups: talkgroups
setGroup( {
items: this.state.group.items
.slice(0, index)
.concat(this.state.group.items.slice(index + 1))
});
setTalkgroups(talkgroups);
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
addTalkgroup(event, index) {
const addTalkgroup = (event, index) => {
const item = {
num: this.state.talkgroups[index].num,
description: this.state.talkgroups[index].description
num: talkgroupsData[index].num,
description: talkgroupsData[index].description
};
this.setState({
group: { items: this.state.group.items.concat(item) },
talkgroups: this.state.talkgroups
.slice(0, index)
.concat(this.state.talkgroups.slice(index + 1))
});
setGroup({ items: group.items.concat(item) })
setTalkgroups(talkgroups.slice(0, index).concat(talkgroups.slice(index + 1)));
}
handleClose = () => {
this.setState({
groupName: "", group: {
name: "",
items: []
},
talkgroups: [...this.props.talkgroups]
});
this.props.onClose(false);
}
handleSubmit() {
var talkgroupNums = this.state.group.items.map(a => a.num);
if (this.props.editGroupId) {
const handleClose = () => {
setGroupName("");
setGroup(emptyGroup);
setTalkgroups([...talkgroupsData]);
props.onClose();
}
const handleSubmit = async () => {
const talkgroupNums = group.items.map(a => a.num);
if (props.editGroupId) {
const data = {
_id: this.props.editGroupId,
shortName: this.props.shortName,
groupName: this.state.groupName,
_id: props.editGroupId,
shortName: props.shortName,
groupName: groupName,
talkgroups: JSON.stringify(talkgroupNums)
};
this.setState({
groupName: "", group: {
name: "",
items: []
},
talkgroups: [...this.props.talkgroups]
});
this.props.groupActions.updateGroup(data).then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
this.props.onClose(false);
} else {
this.props.onClose(true);
}
});
setGroupName("");
setGroup(emptyGroup);
setTalkgroups([...talkgroupsData]);
await updateGroup(data);
props.onClose(true);
} else {
const data = {
shortName: this.props.shortName,
groupName: this.state.groupName,
shortName: props.shortName,
groupName: groupName,
talkgroups: JSON.stringify(talkgroupNums)
};
this.setState({
groupName: "", group: {
name: "",
items: []
},
talkgroups: [...this.props.talkgroups]
});
setGroupName("");
setGroup(emptyGroup);
setTalkgroups([...talkgroupsData]);
await createGroup(data);
props.onClose();
this.props.groupActions.createGroup(data).then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
this.props.onClose(false);
} else {
this.props.onClose(true);
}
});
}
}
render() {
const talkgroups = this.state.talkgroups;
const group = this.state.group.items;
const railStyle = {
top: "15px",
minHeight: "200px"
};
return (
<Modal open={this.props.open} onClose={this.handleClose} centered={false}>
<Modal open={props.open} onClose={handleClose} centered={false}>
<Modal.Header>
{this.props.editGroupId ? "Edit Group" : "Create Group"}
{props.editGroupId ? "Edit Group" : "Create Group"}
</Modal.Header>
<Modal.Content scrolling>
<Modal.Description>
@@ -227,7 +185,7 @@ class GroupModal extends Component {
<Icon
link
name="plus"
onClick={e => this.addTalkgroup(e, i)}
onClick={e => addTalkgroup(e, i)}
/>
</Table.Cell>
</Table.Row>
@@ -238,26 +196,26 @@ class GroupModal extends Component {
<Segment style={railStyle}>
<Input
name="groupName"
onChange={this.handleInputChange}
onChange={e => setGroupName(e.target.value)}
placeholder="Group Name"
value={this.state.groupName}
value={groupName}
/>
<List divided relaxed>
{group.map((talkgroup, i) => (
{group.items.map((talkgroup, i) => (
<List.Item key={"Group-" + i}>
<List.Icon
name="remove"
verticalAlign="middle"
link={true}
onClick={e => this.removeTalkgroup(e, i)}
onClick={e => removeTalkgroup(e, i)}
/>
<List.Content>{talkgroup.description}</List.Content>
</List.Item>
))}
</List>
<Button onClick={this.handleSubmit}>
<Button onClick={handleSubmit}>
<Icon name="checkmark" />{" "}
{this.props.editGroupId ? "Update" : "Save"}
{props.editGroupId ? "Update" : "Save"}
</Button>
</Segment>
</Rail>
@@ -266,14 +224,13 @@ class GroupModal extends Component {
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.handleSubmit}>
<Button onClick={handleSubmit}>
<Icon name="checkmark" />{" "}
{this.props.editGroupId ? "Update" : "Save"}
{props.editGroupId ? "Update" : "Save"}
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default GroupModal;

View File

@@ -1,26 +0,0 @@
import { connect } from "react-redux"
import * as groupActionCreators from "./group-actions"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import { bindActionCreators } from 'redux'
import GroupModal from "./GroupModal"
//groups: state.group.items[props.match.params.shortName],
function mapStateToProps(state,props) {
return {
talkgroups: state.talkgroup.items[props.shortName],
groups: state.group.items[props.shortName],
shortName: props.shortName
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators,dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(GroupModal)

View File

@@ -1,15 +1,4 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Link } from 'react-router-dom'
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
@@ -19,18 +8,22 @@ import {
// ----------------------------------------------------
class ListGroups extends Component {
constructor(props) {
super(props);
}
const ListGroups = (props) => {
//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
render() {
const groups = this.props.groups;
const groups = props.groups;
let groupsDisplay = [];
if (groups) {
// if a group gets deleted, it will still be listed in the Order array for a little.
for (const id of props.order) {
const group = props.groups.find( group => group._id === id );
if (group) {
groupsDisplay.push(group)
}
}
return (
<Table >
@@ -42,7 +35,8 @@ class ListGroups extends Component {
</Table.Row>
</Table.Header>
<Table.Body>
{groups.map((group, i) => (
{groupsDisplay.map((group, i) => (
<Table.Row key={ "Group-" + i}>
<Table.Cell>{group.groupName}</Table.Cell>
<Table.Cell>{group.talkgroups.length}</Table.Cell>
@@ -50,23 +44,27 @@ class ListGroups extends Component {
<Icon
name="pencil alternate"
link={true}
onClick={e => this.props.editGroup(group._id)}
onClick={e => props.editGroup(group._id)}
/><Icon
name="up arrow"
link={true}
onClick={e => this.props.reorderGroup(i, i-1)}
link={i===0?false:true}
disabled={i===0?true:false}
onClick={e => props.reorderGroup(i, i-1)}
/><Icon
name="down arrow"
link={true}
onClick={e => this.props.reorderGroup(i, i+1)}
link={i===groupsDisplay.length-1?false:true}
disabled={i===groupsDisplay.length-1?true:false}
onClick={e => props.reorderGroup(i, i+1)}
/><Icon
name="remove"
link={true}
onClick={e => this.props.deleteGroup(group._id)}
onClick={e => props.deleteGroup(group._id)}
/>
</Table.Cell>
</Table.Row>
))}
)
)}
</Table.Body>
</Table>
@@ -77,6 +75,5 @@ class ListGroups extends Component {
);
}
}
}
export default ListGroups;

View File

@@ -1,245 +0,0 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./group-constants";
import { push } from 'connected-react-router';
// "Log In" action creators
function beginCreateGroup() {
return { type: types.CREATE_GROUP };
}
function createGroupSuccess(data) {
return {
type: types.CREATE_SUCCESS_GROUP,
data
};
}
function createGroupError(data) {
return {
type: types.CREATE_ERROR_GROUP,
data
};
}
function beginUpdateGroup() {
return { type: types.UPDATE_GROUP };
}
function updateGroupSuccess(data) {
return { type: types.UPDATE_SUCCESS_GROUP, data };
}
function updateGroupError() {
return { type: types.UPDATE_ERROR_GROUP };
}
function beginDeleteGroup() {
return { type: types.DELETE_GROUP };
}
function deleteGroupSuccess(data) {
return { type: types.DELETE_SUCCESS_GROUP, data };
}
function deleteGroupError() {
return { type: types.DELETE_ERROR_GROUP };
}
function beginFetchGroup() {
return { type: types.FETCH_GROUP };
}
function fetchGroupSuccess(data) {
return {
type: types.FETCH_SUCCESS_GROUP,
data
};
}
function fetchGroupError(data) {
return {
type: types.FETCH_ERROR_GROUP,
data
};
}
function saveGroupOrderSuccess() {
return { type: types.SAVE_ORDER_SUCCESS_GROUP };
}
function saveGroupOrderError() {
return { type: types.SAVE_ORDER_ERROR_GROUP };
}
function makeUserRequest(method, data, api = "/login") {
// returns a Promise
return axios({
method: method,
url: api,
data: data,
withCredentials: true
});
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function updateGroup(data) {
return dispatch => {
dispatch(beginUpdateGroup());
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + data.shortName + "/" + data._id)
.then(response => {
if (response.data.success) {
dispatch(updateGroupSuccess(response.data.group));
} else {
dispatch(updateGroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
function shouldFetchGroups(state,shortName) {
if (!state.group.items || !state.group.items[shortName]) {
return true;
} else if (state.group.isWaiting) {
return false;
} else {
return false;
}
}
export function reorderGroup(shortName, oldIndex, newIndex) {
return (dispatch, getState) => {
const data = {
shortName: shortName,
oldIndex: oldIndex,
newIndex: newIndex
};
dispatch({
type: types.REORDER_GROUP,
data
});
};
}
export function fetchGroups(shortName) {
return (dispatch, getState) => {
if (shouldFetchGroups(getState(), shortName)) {
dispatch(beginFetchGroup());
return axios
.get(process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName, {withCredentials: true})
.then(response => {
if (response.data.success) {
const data = { shortName: shortName, groups: response.data.groups };
dispatch(fetchGroupSuccess(data));
} else {
dispatch(fetchGroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
}
};
}
export function saveGroupOrder(shortName, data) {
return dispatch => {
dispatch({
type: types.SAVE_ORDER_GROUP,
data
});
return makeUserRequest(
"post",
data,
process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName + "/reorder"
)
.then(response => {
if (response.data.success) {
dispatch(saveGroupOrderSuccess());
} else {
dispatch(saveGroupOrderError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function deleteGroup(shortName, groupId) {
return dispatch => {
dispatch(beginDeleteGroup());
return makeUserRequest("delete", null, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + shortName + "/" + groupId)
.then(response => {
if (response.data.success) {
const data = {shortName: shortName, groupId: groupId}
dispatch(deleteGroupSuccess(data));
} else {
dispatch(deleteGroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function createGroup(data) {
return dispatch => {
dispatch(beginCreateGroup());
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/groups/" + data.shortName)
.then(response => {
if (response.data.success) {
dispatch(createGroupSuccess(response.data.group));
} else {
dispatch(createGroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -1,16 +0,0 @@
export const CREATE_GROUP = "CREATE_GROUP"
export const CREATE_SUCCESS_GROUP = "CREATE_SUCCESS_GROUP"
export const CREATE_ERROR_GROUP = "CREATE_ERROR_GROUP"
export const UPDATE_GROUP = "UPDATE_GROUP"
export const UPDATE_SUCCESS_GROUP = "UPDATE_SUCCESS_GROUP"
export const UPDATE_ERROR_GROUP = "UPDATE_ERROR_GROUP"
export const DELETE_GROUP = "DELETE_GROUP"
export const DELETE_SUCCESS_GROUP = "DELETE_SUCCESS_GROUP"
export const DELETE_ERROR_GROUP = "DELETE_ERROR_GROUP"
export const FETCH_GROUP = "FETCH_GROUP"
export const FETCH_SUCCESS_GROUP = "FETCH_SUCCESS_GROUP"
export const FETCH_ERROR_GROUP = "FETCH_ERROR_GROUP"
export const REORDER_GROUP = "REORDER_GROUP"
export const SAVE_ORDER_GROUP = "SAVE_ORDER_GROUP"
export const SAVE_ORDER_SUCCESS_GROUP = "SAVE_ORDER_SUCCESS_GROUP"
export const SAVE_ORDER_ERROR_GROUP = "SAVE_ORDER_ERROR_GROUP"

View File

@@ -1,123 +0,0 @@
import {
CREATE_GROUP,
CREATE_SUCCESS_GROUP,
CREATE_ERROR_GROUP,
DELETE_GROUP,
DELETE_SUCCESS_GROUP,
DELETE_ERROR_GROUP,
UPDATE_GROUP,
UPDATE_SUCCESS_GROUP,
UPDATE_ERROR_GROUP,
FETCH_GROUP,
FETCH_SUCCESS_GROUP,
FETCH_ERROR_GROUP,
REORDER_GROUP,
SAVE_ORDER_GROUP,
SAVE_ORDER_ERROR_GROUP,
SAVE_ORDER_SUCCESS_GROUP
} from "./group-constants";
function updateItemInArray(array, updatedItem) {
const updatedItems = array.map(item => {
if(item._id !== updatedItem._id) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
return updatedItem;
});
return updatedItems;
}
const group = (
state = {
orderChange: false,
isWaiting: false,
items: {}},
action
) => {
switch (action.type) {
case SAVE_ORDER_GROUP:
case CREATE_GROUP:
case FETCH_GROUP:
case DELETE_GROUP:
case UPDATE_GROUP:
return Object.assign({}, state, { isWaiting: true});
case UPDATE_SUCCESS_GROUP:
const updatedGroups = updateItemInArray(state.items[action.data.shortName], action.data);
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: updatedGroups
}
});
case CREATE_SUCCESS_GROUP:
const newGroups = state.items[action.data.shortName].concat(action.data);
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: newGroups
}
});
case DELETE_SUCCESS_GROUP:
const delGroups = state.items[action.data.shortName].filter(x => x._id !== action.data.groupId);
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: delGroups
}
});
case FETCH_SUCCESS_GROUP:
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: action.data.groups
}
});
case SAVE_ORDER_SUCCESS_GROUP:
return Object.assign({}, state, {
orderChange: false,
isWaiting: false
});
case SAVE_ORDER_ERROR_GROUP:
case FETCH_ERROR_GROUP:
case CREATE_ERROR_GROUP:
case DELETE_ERROR_GROUP:
case UPDATE_ERROR_GROUP:
return Object.assign({}, state, {
isWaiting: false
});
case REORDER_GROUP:
if ((action.data.newIndex<0)||(action.data.newIndex>(state.items[action.data.shortName].length-1))) {
return state
}
const moveGroups = state.items[action.data.shortName].slice();
moveGroups.splice(action.data.newIndex, 0, moveGroups.splice(action.data.oldIndex, 1)[0]);
return Object.assign({}, state, {
orderChange: true,
items: {
...state.items,
[action.data.shortName]: moveGroups
}
});
default:
return state;
}
};
export default group;

View File

@@ -1,148 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Checkbox,
Header,
Form,
Grid,
Modal,
Rail,
Segment,
List,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class AddPermissionModal extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleChange = this.handleChange.bind(this);
// Set initial state
this.state = {
role: 5,
email: "",
error: ""
};
}
componentDidMount() {
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleChange = (e, { newRole }) => this.setState({ newRole })
handleClose = () => {
this.setState({
error: ""
});
this.props.onClose(false);
}
handleSubmit() {
//if (this.props.updatePermissionId) {
const data = {email: this.state.email, shortName: this.props.shortName, role: this.state.role }
this.props.permissionActions.addPermission(data).then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
error: requestMessage
});
//this.props.onClose(false);
} else {
this.setState({
error: ""
});
this.props.onClose(true);
}
});
//}
}
render() {
const railStyle = {
top: "15px",
minHeight: "200px"
};
var errorMessage = "";
if (this.state.error) {
errorMessage = (
<Message negative>
<Message.Header>That didn't work...</Message.Header>
<p>{this.state.error}</p>
</Message>
)
}
return (
<Modal size='tiny' open={this.props.open} onClose={this.handleClose} centered={false}>
<Modal.Header>Select Role</Modal.Header>
<Modal.Content >
<Modal.Description>
<Form>
<Form.Field>
Add User Access to System: {this.props.shortName}
</Form.Field>
<Form.Field>
<Input label="User's email" placeholder='user@domain.com' name='email' onChange={this.handleInputChange} />
</Form.Field>
<Form.Field>
<Checkbox
radio
label='User'
name='role'
value={5}
checked={(this.state.role >= 0) && (this.state.role < 10)}
onChange={this.handleInputChange}
/>
</Form.Field>
<Form.Field>
<Checkbox
radio
label='Admin'
name='role'
value={15}
checked={(this.state.role >= 10) && (this.state.role < 20)}
onChange={this.handleInputChange}
/>
</Form.Field>
</Form>
{errorMessage}
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.handleClose} >
<Icon name='remove' /> Cancel
</Button>
<Button onClick={this.handleSubmit} >
<Icon name='plus' /> Add
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default AddPermissionModal;

View File

@@ -1,23 +0,0 @@
import { connect } from "react-redux"
import * as permissionActionCreators from "./permission-actions"
import { bindActionCreators } from 'redux'
import AddPermissionModal from "./AddPermissionModal"
//groups: state.group.items[props.match.params.shortName],
function mapStateToProps(state,props) {
return {
permissions: state.permission.items,
shortName: props.shortName
}
}
function mapDispatchToProps(dispatch) {
return {
permissionActions: bindActionCreators(permissionActionCreators,dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AddPermissionModal)

View File

@@ -1,76 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Link } from 'react-router-dom'
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
// ----------------------------------------------------
// ----------------------------------------------------
class ListPermissions extends Component {
constructor(props) {
super(props);
}
//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
render() {
const permissions = this.props.permissions;
if (permissions) {
return (
<Table >
<Table.Header>
<Table.Row>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
<Table.HeaderCell>Role</Table.HeaderCell>
<Table.HeaderCell></Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{permissions.map((permission, i) => (
<Table.Row key={ "Group-" + i}>
<Table.Cell>{permission.firstName} {permission.lastName}</Table.Cell>
<Table.Cell>{permission.email}</Table.Cell>
<Table.Cell>{permission.role}</Table.Cell>
<Table.Cell><Icon
name="pencil alternate"
link={true}
onClick={e => this.props.updatePermission(permission._id)}
/>
<Icon
name="remove"
link={true}
onClick={e => this.props.deletePermission(permission)}
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
);
} else {
return (
<div/>
);
}
}
}
export default ListPermissions;

View File

@@ -1,134 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Checkbox,
Header,
Form,
Grid,
Modal,
Rail,
Segment,
List,
Input,
Button,
Message,
Icon,
Table
} from "semantic-ui-react";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class UpdatePermissionModal extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleChange = this.handleChange.bind(this);
// Set initial state
this.state = {
newRole: false,
permission: false
};
}
componentDidMount() {
}
componentWillReceiveProps(nextProps) {
if (nextProps.permissions && nextProps.updatePermissionId) {
var updatePermission = nextProps.permissions.find(permission=> permission._id === nextProps.updatePermissionId);
if (updatePermission) {
this.setState({permission: updatePermission, newRole: updatePermission.role});
}
}
}
handleChange = (e, { name, value }) => this.setState({ newRole: value })
handleClose = () => this.props.onClose(false);
handleSubmit() {
if (this.props.updatePermissionId) {
const data = {permissionId: this.props.updatePermissionId, shortName: this.props.shortName, role: this.state.newRole }
this.props.permissionActions.update(data).then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
this.props.onClose(false);
} else {
this.props.onClose(true);
}
});
}
}
render() {
const railStyle = {
top: "15px",
minHeight: "200px"
};
var user = ""
if (this.state.permission) {
user = this.state.permission.email
}
return (
<Modal size='tiny' open={this.props.open} onClose={this.handleClose} centered={false}>
<Modal.Header>Select Role</Modal.Header>
<Modal.Content >
<Modal.Description>
<Form>
<Form.Field>
Set role for <b>{user}</b>:
</Form.Field>
<Form.Field>
<Checkbox
radio
label='User'
name='checkboxRadioGroup'
value={5}
checked={(this.state.newRole >= 0) && (this.state.newRole < 10)}
onChange={this.handleChange}
/>
</Form.Field>
<Form.Field>
<Checkbox
radio
label='Admin'
name='checkboxRadioGroup'
value={15}
checked={(this.state.newRole >= 10) && (this.state.newRole < 20)}
onChange={this.handleChange}
/>
</Form.Field>
</Form>
</Modal.Description>
</Modal.Content>
<Modal.Actions>
<Button onClick={this.handleSubmit} >
<Icon name='checkmark' /> Update
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default UpdatePermissionModal;

View File

@@ -1,23 +0,0 @@
import { connect } from "react-redux"
import * as permissionActionCreators from "./permission-actions"
import { bindActionCreators } from 'redux'
import UpdatePermissionModal from "./UpdatePermissionModal"
//groups: state.group.items[props.match.params.shortName],
function mapStateToProps(state,props) {
return {
permissions: state.permission.items,
shortName: props.shortName
}
}
function mapDispatchToProps(dispatch) {
return {
permissionActions: bindActionCreators(permissionActionCreators,dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(UpdatePermissionModal)

View File

@@ -1,166 +0,0 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./permission-constants";
function beginAddPermission() {
return { type: types.ADD_PERMISSION };
}
function addPermissionSuccess(data) {
return { type: types.ADD_PERMISSION_SUCCESS, data };
}
function addPermissionError() {
return { type: types.ADD_PERMISSION_ERROR };
}
function beginDeletePermission() {
return { type: types.DELETE_PERMISSION };
}
function deletePermissionSuccess(data) {
return { type: types.DELETE_PERMISSION_SUCCESS, data };
}
function deletePermissionError() {
return { type: types.DELETE_PERMISSION_ERROR };
}
function beginUpdateRole() {
return { type: types.UPDATE_ROLE };
}
function updateRoleSuccess(data) {
return { type: types.UPDATE_ROLE_SUCCESS, data };
}
function updateRoleError() {
return { type: types.UPDATE_ROLE_ERROR };
}
// "Log In" action creators
function beginFetchPermission() {
return { type: types.FETCH_PERMISSION };
}
function fetchPermissionError(data) {
return { type: types.FETCH_ERROR_PERMISSION,
data };
}
function fetchPermissionSuccess(data) {
return { type: types.FETCH_SUCCESS_PERMISSION,
data };
}
function shouldFetch(state) {
if ((!state.permission.items) || (state.permission.items.length <1)) {
return true
} else if (state.permission.isWaiting) {
return false
} else {
return false
}
}
export function fetch(shortName) {
return (dispatch, getState) => {
if (shouldFetch(getState())) {
dispatch(beginFetchPermission());
return axios
.get(process.env.REACT_APP_ADMIN_SERVER + "/permissions/"+shortName, {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(fetchPermissionSuccess(response.data.permissions));
} else {
dispatch(fetchPermissionError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
};
}
export function update(data) {
return (dispatch, getState) => {
dispatch(beginUpdateRole());
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName + "/" + data.permissionId;
return axios.post( url, data, {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(updateRoleSuccess(response.data.permission));
} else {
dispatch(updateRoleError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function addPermission(data) {
return (dispatch, getState) => {
dispatch(beginAddPermission());
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName;
return axios.post( url, data, {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(addPermissionSuccess(response.data.permission));
} else {
dispatch(addPermissionError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function deletePermission(data) {
return (dispatch, getState) => {
dispatch(beginDeletePermission());
var url = process.env.REACT_APP_ADMIN_SERVER + "/permissions/" + data.shortName + "/" + data.permissionId;
return axios.delete( url, {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(deletePermissionSuccess(data.permissionId));
} else {
dispatch(deletePermissionError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -1,12 +0,0 @@
export const ADD_PERMISSION = "ADD_PERMISSION"
export const ADD_PERMISSION_ERROR = "ADD_PERMISSION_ERROR"
export const ADD_PERMISSION_SUCCESS = "ADD_PERMISSION_SUCCESS"
export const FETCH_PERMISSION = "FETCH_PERMISSION"
export const FETCH_ERROR_PERMISSION = "FETCH_ERROR_PERMISSION"
export const FETCH_SUCCESS_PERMISSION = "FETCH_SUCCESS_PERMISSION"
export const UPDATE_ROLE = "UPDATE_ROLE"
export const UPDATE_ROLE_ERROR = "UPDATE_ROLE_ERROR"
export const UPDATE_ROLE_SUCCESS = "UPDATE_ROLE SUCCESS"
export const DELETE_PERMISSION = "DELETE_PERMISSION"
export const DELETE_PERMISSION_ERROR = "DELETE_PERMISSION_ERROR"
export const DELETE_PERMISSION_SUCCESS = "DELETE_PERMISSION_SUCCESS"

View File

@@ -1,87 +0,0 @@
import {
ADD_PERMISSION,
ADD_PERMISSION_SUCCESS,
ADD_PERMISSION_ERROR,
DELETE_PERMISSION,
DELETE_PERMISSION_SUCCESS,
DELETE_PERMISSION_ERROR,
FETCH_PERMISSION,
FETCH_SUCCESS_PERMISSION,
FETCH_ERROR_PERMISSION,
UPDATE_ROLE,
UPDATE_ROLE_ERROR,
UPDATE_ROLE_SUCCESS
} from "./permission-constants";
function updateRoleInArray(array, updatedItem) {
const updatedItems = array.map(item => {
if(item._id !== updatedItem._id) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
item.role = updatedItem.role;
return item;
});
return updatedItems;
}
function deleteItemInArray(array, _id) {
const updatedItems = array.filter(function (item) {
return item._id !== _id;
});
return updatedItems;
}
const permission = (
state = {
isWaiting: false,
items: []
},
action
) => {
switch (action.type) {
case ADD_PERMISSION_SUCCESS:
return Object.assign({}, state, {
isWaiting: false,
items: [...state.items, action.data]
});
case DELETE_PERMISSION_SUCCESS:
return Object.assign({}, state, {
isWaiting: false,
items: deleteItemInArray(state.items, action.data)
});
case UPDATE_ROLE_SUCCESS:
return Object.assign({}, state, {
isWaiting: false,
items: updateRoleInArray(state.items, action.data)
});
case FETCH_SUCCESS_PERMISSION:
return Object.assign({}, state, {
isWaiting: false,
items: action.data
});
case DELETE_PERMISSION_ERROR:
case ADD_PERMISSION_ERROR:
case UPDATE_ROLE_ERROR:
case FETCH_ERROR_PERMISSION:
return Object.assign({}, state, {
isWaiting: false
});
case DELETE_PERMISSION:
case ADD_PERMISSION:
case UPDATE_ROLE:
case FETCH_PERMISSION:
return Object.assign({}, state, { isWaiting: true});
default:
return state;
}
};
export default permission;

View File

@@ -1,103 +1,77 @@
import React, { Component } from "react";
import React from "react";
import { Group } from '@visx/group';
import { curveStep} from '@visx/curve';
import { LinePath } from '@visx/shape';
import { scaleLinear, scaleBand, scaleTime } from '@visx/scale';
import { scaleLinear, scaleTime } from '@visx/scale';
import { timeParse, timeFormat } from 'd3-time-format';
import { AxisBottom, AxisLeft } from '@visx/axis';
import { Dimmer, Loader } from "semantic-ui-react";
import { Loader } from "semantic-ui-react";
const CallChart = (props) => {
class CallChart extends Component {
constructor(props) {
super(props);
}
var callData = props.data;
if (!callData) {
return (
<Loader size='large'>Loading</Loader>
)
}
render() {
var callData = this.props.data;
if (!callData) {
return (
var callTotals = callData.callTotals;
var errorTotals = callData.errorTotals;
<Loader size='large'>Loading</Loader>
// Define the graph dimensions and margins
const width = props.width;
const height = 500;
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
const parseDate = timeParse("%Y%m%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
)
}
var callTotals = [];
var errorTotals = [];
// Then we'll create some bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// Define the graph dimensions and margins
const width = this.props.width;
const height = 500;
const margin = { top: 20, bottom: 40, left: 40, right: 20 };
const parseDate = timeParse("%Y%m%d");
const format = timeFormat("%b %d");
const formatDate = (date) => format(parseDate(date));
// accessors
const x = d => d.x;
const y = d => d.y;
// Then we'll create some bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;
// accessors
const x = d => d.x;
const y = d => d.y;
// Compose together the scale and accessor functions to get point functions
const dotColor = n => {
var colores_g = ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"];
return colores_g[n % colores_g.length];
}
const compose = (scale, accessor) => (data) => scale(accessor(data));
var xScale, yScale, xPoint, yPoint;
if (callData) {
// We'll use some mock data from `@vx/mock-data` for this.
callTotals = callData.callTotals;
errorTotals = callData.errorTotals;
// scales
xScale = scaleTime({
range: [0, xMax],
domain: [callData.minDate, callData.maxDate],
tickFormat: () => (val) => formatDate(val)
});
yScale = scaleLinear({
range: [yMax, 0],
domain: [0, callData.maxValue],
});
xPoint = compose(xScale, x);
yPoint = compose(yScale, y);
}
var xScale, yScale;
if (callData) {
// We'll use some mock data from `@vx/mock-data` for this.
// scales
xScale = scaleTime({
range: [0, xMax],
domain: [callData.minDate, callData.maxDate],
tickFormat: () => (val) => formatDate(val)
});
yScale = scaleLinear({
range: [yMax, 0],
domain: [0, callData.maxValue],
});
}
return (
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top} >
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
<AxisLeft scale={yScale} />
<LinePath
data={callTotals}
xScale={xScale}
yScale={yScale}
x={x}
y={y}
strokeWidth={2}
curve={curveStep}
/>
<LinePath
data={errorTotals}
xScale={xScale}
yScale={yScale}
x={x}
y={y}
strokeWidth={2}
stroke="red"
curve={curveStep}
/>
</Group>
</svg>
);
}
<svg width={width} height={height}>
<Group left={margin.left} top={margin.top}>
<AxisBottom top={yMax} scale={xScale} numTicks={width > 520 ? 10 : 5} />
<AxisLeft scale={yScale} />
<LinePath
data={callTotals}
x={x}
y={y}
strokeWidth={2}
curve={curveStep}
/>
<LinePath
data={errorTotals}
x={x}
y={y}
strokeWidth={2}
stroke="red"
curve={curveStep}
/>
</Group>
</svg>
);
}
export default CallChart;

View File

@@ -1,55 +1,34 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import React, { useState } from "react";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Divider,
Message,
Icon
} from "semantic-ui-react";
import { useCreateSystemMutation} from '../features/api/apiSlice'
import { useSelector } from 'react-redux'
import { useNavigate } from 'react-router-dom';
import SystemForm from "./SystemForm";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
const CreateSystem = () => {
const navigate = useNavigate();
const [createSystem ] = useCreateSystemMutation();
const { screenName } = useSelector((state) => state.user);
// ----------------------------------------------------
class CreateSystem extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
const [requestMessage, setRequestMessage] = useState("");
const handleSubmit = async (system) => {
try {
await createSystem(system).unwrap();
navigate("/list-systems")
} catch (error) {
const message = error.data.message;
console.log(message);
setRequestMessage(message);
}
}
state = {
requestMessage: ""
}
handleSubmit(system) {
this.props
.createSystem(system)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
} else {
this.props.changeUrl("/list-systems")
}
});
}
render() {
return (
<div>
<Container text>
@@ -72,11 +51,11 @@ class CreateSystem extends Component {
<Container text>
<Header as="h1">Create System</Header>
<SystemForm onSubmit={this.handleSubmit} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
<SystemForm onSubmit={handleSubmit} requestMessage={requestMessage} screenName={screenName}/>
</Container>
</div>
);
}
}
export default CreateSystem;

View File

@@ -1,14 +0,0 @@
import { connect } from "react-redux"
import * as systemActions from "./system-actions"
import CreateSystem from "./CreateSystem"
function mapStateToProps(state) {
return {
user: state.user
}
}
export default connect(
mapStateToProps,
systemActions
)(CreateSystem)

View File

@@ -7,9 +7,7 @@ import { AxisBottom, AxisLeft } from '@visx/axis';
import { Loader } from "semantic-ui-react";
import { LegendOrdinal} from '@visx/legend';
class ErrorChart extends Component {
constructor(props) {
super(props);
}
render() {
var errorData = this.props.data;
@@ -44,12 +42,11 @@ class ErrorChart extends Component {
return colores_g[n % colores_g.length];
}
const compose = (scale, accessor) => (data) => scale(accessor(data));
var zScale = scaleOrdinal({
domain: errorData.legend,
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
});
var xScale, yScale, xPoint, yPoint;
var xScale, yScale;
if (errorData) {
// We'll use some mock data from `@vx/mock-data` for this.
data = errorData.data;
@@ -67,8 +64,6 @@ class ErrorChart extends Component {
domain: errorData.legend,
range: ["#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00", "#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707", "#651067", "#329262", "#5574a6", "#3b3eac"]
});
xPoint = compose(xScale, x);
yPoint = compose(yScale, y);
}

View File

@@ -1,4 +1,3 @@
import React, { Component } from "react";
import SystemCard from "./SystemCard";
import {
Container,
@@ -8,37 +7,38 @@ import {
Icon,
Button,
} from "semantic-ui-react";
import { useGetSystemsQuery, } from '../features/api/apiSlice'
import { useNavigate } from 'react-router-dom';
// ----------------------------------------------------
class ListSystems extends Component {
state = {
const ListSystems = () => {
const { data, isSuccess } = useGetSystemsQuery();
const navigate = useNavigate();
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/42597602/react-onclick-pass-event-with-parameter
render() {
const systems = this.props.system.items;
return (
<div>
<Container>
<Header as="h1">Radio Systems</Header>
<Button onClick={(e) => this.props.changeUrl("/create-system")}><Icon name='plus' />Add System</Button>
<Button onClick={(e) => navigate("/create-system")}><Icon name='plus' />Add System</Button>
<Divider />
<Card.Group itemsPerRow={4}>
{systems.map((system) =>
<SystemCard system={system} key={system.shortName} onClick={(e) => this.props.changeUrl("/system/" + system.shortName)}/>
{systems &&
systems.map((system) =>
<SystemCard system={system} key={system.shortName} onClick={(e) => navigate("/system/" + system.shortName)}/>
)}
</Card.Group>
</Container>
</div>
);
}
}
export default ListSystems;

View File

@@ -1,16 +0,0 @@
import { connect } from "react-redux"
import * as systemActions from "./system-actions"
import ListSystems from "./ListSystems"
function mapStateToProps(state) {
return {
system: state.system
}
}
export default connect(
mapStateToProps,
systemActions
)(ListSystems)

View File

@@ -1,72 +0,0 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import {
Container,
Header,
Form,
Grid,
Segment,
Input,
Button,
Message,
Step,
Icon
} from "semantic-ui-react";
import SystemForm from "./SystemForm";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
// ----------------------------------------------------
class PreCreateSystem extends Component {
constructor(props) {
super(props);
}
state = {
requestMessage: ""
}
render() {
return (
<div>
<Step.Group attached='top' widths={3}>
<Step>
<Icon name='wifi' />
<Step.Content>
<Step.Title>Trunk Recorder</Step.Title>
<Step.Description>Choose your shipping options</Step.Description>
</Step.Content>
</Step>
<Step active>
<Icon name='payment' />
<Step.Content>
<Step.Title>Billing</Step.Title>
<Step.Description>Enter billing information</Step.Description>
</Step.Content>
</Step>
<Step disabled>
<Icon name='info' />
<Step.Content>
<Step.Title>Confirm Order</Step.Title>
</Step.Content>
</Step>
</Step.Group>
<Segment attached>
<Image src='https://react.semantic-ui.com/images/wireframe/paragraph.png' />
</Segment>
</div>
);
}
}
export default PreCreateSystem;

View File

@@ -1,27 +1,22 @@
import React, { Component } from "react"
import CallChart from "./CallChart"
import { ParentSize } from "@visx/responsive";
class ResponsiveCallChart extends Component {
constructor(props) {
super(props);
}
const ResponsiveCallChart = (props) => {
render() {
return(
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<CallChart
width={w}
height={h}
data={this.props.data}
/>
);
}}
</ParentSize>
)
}
return (
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<CallChart
width={w}
height={h}
data={props.data}
/>
);
}}
</ParentSize>
)
}
export default ResponsiveCallChart

View File

@@ -1,27 +1,21 @@
import React, { Component } from "react"
import ErrorChart from "./ErrorChart"
import { ParentSize } from "@visx/responsive";
class ResponsiveErrorChart extends Component {
constructor(props) {
super(props);
}
const ResponsiveErrorChart = (props) => {
render() {
return(
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<ErrorChart
width={w}
height={h}
data={this.props.data}
/>
);
}}
</ParentSize>
)
}
return (
<ParentSize className="graph-container">
{({ width: w, height: h }) => {
return (
<ErrorChart
width={w}
height={h}
data={props.data}
/>
);
}}
</ParentSize>
)
}
export default ResponsiveErrorChart

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
import React, { Component } from "react";
import {
Card,
Icon,
@@ -9,15 +8,13 @@ import {
class SystemCard extends Component {
render() {
const SystemCard = (props) => {
var location = "";
const system = this.props.system;
const { system } = props;
if (system) {
switch (system.systemType) {
case "state":
@@ -37,26 +34,26 @@ render() {
}
}
return (
<Card link onClick={this.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 link onClick={props.onClick}>
<Card.Description>{system.description}</Card.Description>
</Card.Content>
<Card.Content extra>
<Icon name='marker' />
{location}
</Card.Content>
</Card>
<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 extra>
<Icon name='marker' />
{location}
</Card.Content>
</Card>
);
}
}
export default SystemCard;

View File

@@ -1,40 +0,0 @@
import { connect } from "react-redux"
import * as systemActionCreators from "./system-actions"
import * as talkgroupActionCreators from "../Talkgroups/talkgroup-actions"
import * as groupActionCreators from "../Group/group-actions"
import * as permissionActionCreators from "../Permission/permission-actions"
import * as errorActionCreators from "./error-actions"
import { bindActionCreators } from 'redux'
import System from "./System"
function mapStateToProps(state,props) {
return {
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
permissions: state.permission.items,
statistic: state.statistic[props.match.params.shortName],
systemIsWaiting: state.system.isWaiting,
talkgroupsIsWaiting: state.talkgroup.isWaiting,
groupsIsWaiting: state.group.isWaiting,
talkgroups: state.talkgroup.items[props.match.params.shortName],
orderChange: state.group.orderChange,
groups: state.group.items[props.match.params.shortName],
errors: state.error.items[props.match.params.shortName],
shortName: props.match.params.shortName,
activeTab: (props.match.params.tab ? props.match.params.tab : 0)
}
}
function mapDispatchToProps(dispatch) {
return {
groupActions: bindActionCreators(groupActionCreators, dispatch),
systemActions: bindActionCreators(systemActionCreators, dispatch),
talkgroupActions: bindActionCreators(talkgroupActionCreators, dispatch),
errorActions: bindActionCreators(errorActionCreators, dispatch),
permissionActions: bindActionCreators(permissionActionCreators, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(System)

View File

@@ -1,4 +1,4 @@
import React, { Component } from "react";
import React, { useEffect, useState } from "react";
import {
Divider,
Form,
@@ -9,421 +9,385 @@ import {
} from "semantic-ui-react";
// ----------------------------------------------------
class SystemForm extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleScreenNameCheckboxChange = this.handleScreenNameCheckboxChange.bind(this);
this.handleTalkgroupCheckboxChange = this.handleTalkgroupCheckboxChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.checkInputs = this.checkInputs.bind(this);
this.render = this.render.bind(this);
const SystemForm = (props) => {
this.state = {
checkInputMessages: [],
name: "",
shortName: "",
description: "",
systemType: "state",
state: "",
city: "",
county: "",
country: "",
showScreenName: false,
ignoreUnknownTalkgroup: false,
nameError: false,
shortNameError: false,
descriptionError: false,
emailError: false,
systemTypeError: false,
stateError: false,
cityErorr: false,
countyError: false,
countryError: false,
isEditing: this.props.isEditing
};
if (this.props.isEditing && this.props.system) {
this.state.name = this.props.system.name;
this.state.shortName = this.props.system.shortName;
this.state.description = this.props.system.description;
this.state.systemType = this.props.system.systemType;
this.state.city = this.props.system.city;
this.state.state = this.props.system.state;
this.state.county = this.props.system.county;
this.state.country = this.props.system.country;
this.state.showScreenName = this.props.system.showScreenName;
this.state.ignoreUnknownTalkgroup = this.props.system.ignoreUnknownTalkgroup;
const [checkInputMessages, setCheckInputMessage] = useState([]);
const [name, setName] = useState("");
const [shortName, setShortName] = useState("");
const [description, setDescription] = useState("");
const [systemType, setSystemType] = useState("state");
const [systemState, setSystemState] = useState("");
const [city, setCity] = useState("");
const [county, setCounty] = useState("");
const [country, setCountry] = useState("");
const [showScreenName, setShowScreenName] = useState(false);
const [ignoreUnknownTalkgroup, setIgnoreUnknownTalkgroup] = useState(false);
const [nameError, setNameError] = useState(false);
const [shortNameError, setShortNameError] = useState(false);
const [descriptionError, setDescriptionError] = useState(false);
const [systemStateError, setSystemStateError] = useState(false);
const [cityError, setCityError] = useState(false);
const [countyError, setCountyError] = useState(false);
const [countryError, setCountryError] = useState(false);
const [isEditing] = useState(props.isEditing);
useEffect(() => {
if (props.isEditing && props.system) {
setName(props.system.name);
setShortName(props.system.shortName);
setDescription(props.system.description);
setSystemType(props.system.systemType);
setCity(props.system.city);
setSystemState(props.system.state)
setCounty(props.system.county);
setCountry(props.system.country);
setShowScreenName(props.system.showScreenName);
setIgnoreUnknownTalkgroup(props.system.ignoreUnknownTalkgroup);
}
}
// If we directly to an updating url, the system fetch will come back after the constructer, so we need to catch this and update the values
componentDidUpdate(prevProps) {
if (this.props.isEditing && this.props.system && !prevProps.system) {
this.setState({
name: this.props.system.name,
shortName: this.props.system.shortName,
description: this.props.system.description,
systemType: this.props.system.systemType,
city: this.props.system.city,
state: this.props.system.state,
county: this.props.system.county,
country: this.props.system.country,
showScreenName: this.props.system.showScreenName,
ignoreUnknownTalkgroup: this.props.system.ignoreUnknownTalkgroup,
});
}
}
checkInputs() {
}, [props.system, props.isEditing]);
const checkInputs = () => {
let error = false;
var checkInputMessages = [];
var messages = [];
if (this.state.name === "") {
this.setState({ nameError: true });
checkInputMessages.push("System Name is required");
if (name === "") {
setNameError(true);
messages.push("System Name is required");
error = true;
} else {
this.setState({ nameError: false });
setNameError(false);
}
if (this.state.shortName.length === 0 || this.state.shortName.length > 10) {
this.setState({ shortNameError: true });
checkInputMessages.push(
if (shortName.length < 4 || shortName.length > 10) {
setShortNameError(true)
messages.push(
"Short Name for the system has to be 4-10 charecters"
);
error = true;
} else {
this.setState({ shortNameError: false });
setShortNameError(false);
}
if (this.state.description === "") {
this.setState({ descriptionError: true });
checkInputMessages.push("System description is required");
if (description === "") {
setDescriptionError(true);
messages.push("System description is required");
error = true;
} else {
this.setState({ descriptionError: false });
setDescriptionError(false);
}
if (
this.state.systemType === "state" ||
this.state.systemType === "city" ||
this.state.systemType === "county"
systemType === "state" ||
systemType === "city" ||
systemType === "county"
) {
if (this.state.state === "") {
this.setState({ stateError: true });
checkInputMessages.push("Select a state");
if (systemState === "") {
setSystemStateError(true);
messages.push("Select a state");
error = true;
} else {
this.setState({ stateError: false });
setSystemStateError(false);
}
}
if (this.state.systemType === "city") {
if (this.state.city === "") {
this.setState({ cityError: true });
checkInputMessages.push("Enter a city");
if (systemType === "city") {
if (city === "") {
setCityError(true);
messages.push("Enter a city");
error = true;
} else {
this.setState({ cityError: false });
setCityError(false);
}
}
if (this.state.systemType === "county") {
if (this.state.county === "") {
this.setState({ countyError: true });
checkInputMessages.push("Enter a city");
if (systemType === "county") {
if (county === "") {
setCountyError(true)
messages.push("Enter a county");
error = true;
} else {
this.setState({ countyError: false });
setCountyError(false);
}
}
if (this.state.systemType === "international") {
if (this.state.country === "") {
this.setState({ countryError: true });
checkInputMessages.push("Enter a city");
if (systemType === "international") {
if (country === "") {
setCountryError(true);
messages.push("Enter a country");
error = true;
} else {
this.setState({ countryError: false });
setCountryError(false);
}
}
this.setState({ checkInputMessages: checkInputMessages });
setCheckInputMessage(messages);
return error;
}
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
handleScreenNameCheckboxChange() {
this.setState({ showScreenName: !this.state.showScreenName });
const handleScreenNameCheckboxChange = () => {
setShowScreenName((current) => !current);
}
handleTalkgroupCheckboxChange() {
this.setState({ ignoreUnknownTalkgroup: !this.state.ignoreUnknownTalkgroup });
const handleTalkgroupCheckboxChange = () => {
setIgnoreUnknownTalkgroup((current) => !current);
}
handleSubmit(event) {
const handleSubmit = (event) => {
event.preventDefault();
let inputError = this.checkInputs();
let inputError = checkInputs();
if (!inputError) {
const system = (({
const system = {
name,
shortName,
description,
systemType,
city,
state,
state: systemState,
county,
country,
showScreenName,
ignoreUnknownTalkgroup
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
showScreenName,
ignoreUnknownTalkgroup
}))(this.state);
this.props.onSubmit(system);
};
props.onSubmit(system);
}
}
render() {
var requestMessage = "";
var floatStyle = {
overflow: "auto"
};
const { isEditing } = this.props;
const stateOptions = [
{ value: "AL", text: "Alabama" },
{ value: "AK", text: "Alaska" },
{ value: "AZ", text: "Arizona" },
{ value: "AR", text: "Arkansas" },
{ value: "CA", text: "California" },
{ value: "CO", text: "Colorado" },
{ value: "CT", text: "Connecticut" },
{ value: "DE", text: "Delaware" },
{ value: "DC", text: "District Of Columbia" },
{ value: "FL", text: "Florida" },
{ value: "GA", text: "Georgia" },
{ value: "HI", text: "Hawaii" },
{ value: "ID", text: "Idaho" },
{ value: "IL", text: "Illinois" },
{ value: "IN", text: "Indiana" },
{ value: "IA", text: "Iowa" },
{ value: "KS", text: "Kansas" },
{ value: "KY", text: "Kentucky" },
{ value: "LA", text: "Louisiana" },
{ value: "ME", text: "Maine" },
{ value: "MD", text: "Maryland" },
{ value: "MA", text: "Massachusetts" },
{ value: "MI", text: "Michigan" },
{ value: "MN", text: "Minnesota" },
{ value: "MS", text: "Mississippi" },
{ value: "MO", text: "Missouri" },
{ value: "MT", text: "Montana" },
{ value: "NE", text: "Nebraska" },
{ value: "NV", text: "Nevada" },
{ value: "NH", text: "New Hampshire" },
{ value: "NJ", text: "New Jersey" },
{ value: "NM", text: "New Mexico" },
{ value: "NY", text: "New York" },
{ value: "NC", text: "North Carolina" },
{ value: "ND", text: "North Dakota" },
{ value: "OH", text: "Ohio" },
{ value: "OK", text: "Oklahoma" },
{ value: "OR", text: "Oregon" },
{ value: "PA", text: "Pennsylvania" },
{ value: "RI", text: "Rhode Island" },
{ value: "SC", text: "South Carolina" },
{ value: "SD", text: "South Dakota" },
{ value: "TN", text: "Tennessee" },
{ value: "TX", text: "Texas" },
{ value: "UT", text: "Utah" },
{ value: "VT", text: "Vermont" },
{ value: "VA", text: "Virginia" },
{ value: "WA", text: "Washington" },
{ value: "WV", text: "West Virginia" },
{ value: "WI", text: "Wisconsin" },
{ value: "WY", text: "Wyoming" }
];
if (
this.state.checkInputMessages.length ||
this.props.requestMessage.length
) {
var counter = 0;
requestMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{this.state.checkInputMessages.map(function (listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{this.props.requestMessage.length > 0 && (
<li key="req-mess">{this.props.requestMessage}</li>
)}
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form className="raised padding segment" onSubmit={this.handleSubmit}>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="name"
onChange={this.handleInputChange}
error={this.state.nameError}
value={this.state.name}
label="System name"
placeholder="System Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="shortName"
onChange={this.handleInputChange}
error={this.state.shortNameError}
value={this.state.shortName}
disabled={isEditing}
label="System Short Name (4-10 characters)"
placeholder="Short Name..."
/>
</Form.Field>
</Form.Group>
<Divider horizontal />
<Form.Field>
<Checkbox
onChange={this.handleScreenNameCheckboxChange}
checked={this.state.showScreenName}
label="Display Screen Name in System Listing" />
</Form.Field>
<Form.Field>
<Checkbox
onChange={this.handleTalkgroupCheckboxChange}
checked={this.state.ignoreUnknownTalkgroup}
label="Ignore Unknown Talkgroups" />
</Form.Field>
<Divider horizontal />
<Form.TextArea
label="Description"
name="description"
onChange={this.handleInputChange}
error={this.state.descriptionError}
value={this.state.description}
rows={2}
placeholder="Description of system..."
/>
var requestMessage = "";
var floatStyle = {
overflow: "auto"
};
<Divider horizontal><Icon name="marker" />Location</Divider>
<Form.Group inline>
<label>System Type</label>
<Form.Radio
label="State"
name="systemType"
value="state"
checked={this.state.systemType === "state"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="County"
name="systemType"
value="county"
checked={this.state.systemType === "county"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="City"
name="systemType"
value="city"
checked={this.state.systemType === "city"}
onChange={this.handleInputChange}
/>
<Form.Radio
label="International"
name="systemType"
value="international"
checked={this.state.systemType === "international"}
onChange={this.handleInputChange}
/>
</Form.Group>
<Form.Group widths="equal">
{this.state.systemType === "city" && (
<Form.Field>
<Form.Input
type="text"
name="city"
onChange={this.handleInputChange}
error={this.state.cityError}
value={this.state.city}
label="City"
placeholder="City..."
/>
</Form.Field>
const stateOptions = [
{ value: "AL", text: "Alabama" },
{ value: "AK", text: "Alaska" },
{ value: "AZ", text: "Arizona" },
{ value: "AR", text: "Arkansas" },
{ value: "CA", text: "California" },
{ value: "CO", text: "Colorado" },
{ value: "CT", text: "Connecticut" },
{ value: "DE", text: "Delaware" },
{ value: "DC", text: "District Of Columbia" },
{ value: "FL", text: "Florida" },
{ value: "GA", text: "Georgia" },
{ value: "HI", text: "Hawaii" },
{ value: "ID", text: "Idaho" },
{ value: "IL", text: "Illinois" },
{ value: "IN", text: "Indiana" },
{ value: "IA", text: "Iowa" },
{ value: "KS", text: "Kansas" },
{ value: "KY", text: "Kentucky" },
{ value: "LA", text: "Louisiana" },
{ value: "ME", text: "Maine" },
{ value: "MD", text: "Maryland" },
{ value: "MA", text: "Massachusetts" },
{ value: "MI", text: "Michigan" },
{ value: "MN", text: "Minnesota" },
{ value: "MS", text: "Mississippi" },
{ value: "MO", text: "Missouri" },
{ value: "MT", text: "Montana" },
{ value: "NE", text: "Nebraska" },
{ value: "NV", text: "Nevada" },
{ value: "NH", text: "New Hampshire" },
{ value: "NJ", text: "New Jersey" },
{ value: "NM", text: "New Mexico" },
{ value: "NY", text: "New York" },
{ value: "NC", text: "North Carolina" },
{ value: "ND", text: "North Dakota" },
{ value: "OH", text: "Ohio" },
{ value: "OK", text: "Oklahoma" },
{ value: "OR", text: "Oregon" },
{ value: "PA", text: "Pennsylvania" },
{ value: "RI", text: "Rhode Island" },
{ value: "SC", text: "South Carolina" },
{ value: "SD", text: "South Dakota" },
{ value: "TN", text: "Tennessee" },
{ value: "TX", text: "Texas" },
{ value: "UT", text: "Utah" },
{ value: "VT", text: "Vermont" },
{ value: "VA", text: "Virginia" },
{ value: "WA", text: "Washington" },
{ value: "WV", text: "West Virginia" },
{ value: "WI", text: "Wisconsin" },
{ value: "WY", text: "Wyoming" }
];
if (
checkInputMessages.length ||
props.requestMessage.length
) {
var counter = 0;
requestMessage = (
<Message compact warning icon>
<Icon name="lightning" />
<Message.Content>
<Message.Header>Problems...</Message.Header>
<ul>
{checkInputMessages.map(function (listValue) {
return <li key={`ListItem_${counter++}`}>{listValue}</li>;
})}
{props.requestMessage.length > 0 && (
<li key="req-mess">{props.requestMessage}</li>
)}
{this.state.systemType === "county" && (
<Form.Field>
<Form.Input
type="text"
name="county"
onChange={this.handleInputChange}
error={this.state.countyError}
value={this.state.county}
label="County"
placeholder="County..."
/>
</Form.Field>
)}
{this.state.systemType === "international" ? (
<Form.Field>
<Form.Input
type="text"
name="country"
onChange={this.handleInputChange}
error={this.state.countryError}
value={this.state.country}
label="Country"
placeholder="Country..."
/>
</Form.Field>
) : (
<Form.Field>
<Form.Dropdown
name="state"
onChange={this.handleInputChange}
error={this.state.stateError}
value={this.state.state}
options={stateOptions}
scrolling
label="State"
placeholder="Select State..."
/>
</Form.Field>
)}
</Form.Group>
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login">
{isEditing !== true ? "Create" : "Update"}
</Button>
</div>
</Form>
{requestMessage}
</div>
</ul>
</Message.Content>
</Message>
);
}
return (
<div>
<Form className="raised padding segment" onSubmit={handleSubmit}>
<Form.Group widths="equal">
<Form.Field>
<Form.Input
type="text"
name="name"
onChange={e => setName(e.target.value)}
error={nameError}
value={name}
label="System name"
placeholder="System Name..."
/>
</Form.Field>
<Form.Field>
<Form.Input
type="text"
name="shortName"
onChange={e => setShortName(e.target.value)}
error={shortNameError}
value={shortName}
disabled={isEditing}
label="System Short Name (4-10 characters)"
placeholder="Short Name..."
/>
</Form.Field>
</Form.Group>
<Divider horizontal />
<Form.Field>
<Checkbox
onChange={handleScreenNameCheckboxChange}
checked={showScreenName}
label="Display Screen Name in System Listing" />
</Form.Field>
<Form.Field>
<Checkbox
onChange={handleTalkgroupCheckboxChange}
checked={ignoreUnknownTalkgroup}
label="Ignore Unknown Talkgroups" />
</Form.Field>
<Divider horizontal />
<Form.TextArea
label="Description"
name="description"
onChange={e => setDescription(e.target.value)}
error={descriptionError}
value={description}
rows={2}
placeholder="Description of system..."
/>
<Divider horizontal><Icon name="marker" />Location</Divider>
<Form.Group inline>
<label>System Type</label>
<Form.Radio
label="State"
name="systemType"
value="state"
checked={systemType === "state"}
onChange={e => setSystemType("state")}
/>
<Form.Radio
label="County"
name="systemType"
value="county"
checked={systemType === "county"}
onChange={e => setSystemType("county")}
/>
<Form.Radio
label="City"
name="systemType"
value="city"
checked={systemType === "city"}
onChange={e => setSystemType("city")}
/>
<Form.Radio
label="International"
name="systemType"
value="international"
checked={systemType === "international"}
onChange={e => setSystemType("international")}
/>
</Form.Group>
<Form.Group widths="equal">
{systemType === "city" && (
<Form.Field>
<Form.Input
type="text"
name="city"
onChange={e => setCity(e.target.value)}
error={cityError}
value={city}
label="City"
placeholder="City..."
/>
</Form.Field>
)}
{systemType === "county" && (
<Form.Field>
<Form.Input
type="text"
name="county"
onChange={e => setCounty(e.target.value)}
error={countyError}
value={county}
label="County"
placeholder="County..."
/>
</Form.Field>
)}
{systemType === "international" ? (
<Form.Field>
<Form.Input
type="text"
name="country"
onChange={e => setCountry(e.target.value)}
error={countryError}
value={country}
label="Country"
placeholder="Country..."
/>
</Form.Field>
) : (
<Form.Field>
<Form.Dropdown
name="state"
onChange={(e, data ) => {setSystemState(data.value); console.log(data.value)}}
error={systemStateError}
value={systemState}
options={stateOptions}
scrolling
label="State"
placeholder="Select State..."
/>
</Form.Field>
)}
</Form.Group>
<div style={floatStyle}>
<Button type="submit" size="large" floated="right" value="Login">
{isEditing !== true ? "Create" : "Update"}
</Button>
</div>
</Form>
{requestMessage}
</div>
);
}
export default SystemForm;

View File

@@ -1,47 +1,47 @@
import React, { Component } from "react"
import React, { useState } from "react";
import SystemForm from "./SystemForm";
import {
Container,
Header
} from "semantic-ui-react";
import { useUpdateSystemMutation, useGetSystemsQuery, } from '../features/api/apiSlice'
import { useSelector } from 'react-redux'
import { useNavigate, useParams } from 'react-router-dom';
class UpdateSystem extends Component {
constructor(props) {
super(props);
this.handleSystemSubmit = this.handleSystemSubmit.bind(this);
const UpdateSystem = (props) => {
const { screenName } = useSelector((state) => state.user);
const { shortName } = useParams();
const { data: systemsData, isSuccess: isSystemsSuccess } = useGetSystemsQuery();
const [updateSystem] = useUpdateSystemMutation();
const [requestMessage, setRequestMessage] = useState("");
const navigate = useNavigate();
let system = false;
if (isSystemsSuccess) {
system = systemsData.systems.find(sys => sys.shortName === shortName);
}
state = {
requestMessage: ""
const handleSystemSubmit = async (system) => {
try {
await updateSystem(system).unwrap();
navigate("/system/" + shortName)
} catch (error) {
const message = error.data.message;
console.log(message);
setRequestMessage(message);
}
}
componentDidMount() {
this.props.fetchSystems()
}
handleSystemSubmit(system) {
this.props
.updateSystem(system)
.then(requestMessage => {
if (requestMessage) {
// report to the user is there was a problem during registration
this.setState({
requestMessage
});
} else {
this.props.changeUrl("/system/"+system.shortName)
}
});
}
return (
<Container text>
<Header as="h1">Update System</Header>
<SystemForm onSubmit={handleSystemSubmit} system={system} isEditing={true} requestMessage={requestMessage} screenName={screenName} />
</Container>
)
render() {
return(
<Container text>
<Header as="h1">Update System</Header>
<SystemForm onSubmit={this.handleSystemSubmit} system={this.props.system} isEditing={true} requestMessage={this.state.requestMessage} screenName={this.props.user.screenName}/>
</Container>
)
}
}
export default UpdateSystem

View File

@@ -1,18 +0,0 @@
import { connect } from "react-redux"
import UpdateSystem from "./UpdateSystem"
import * as systemActions from "./system-actions"
const mapStateToProps = (state, props) => {
return {
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
user: state.user
}
}
export default connect(
mapStateToProps,
systemActions
)(UpdateSystem)

View File

@@ -1,54 +0,0 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./error-constants";
import { push } from 'connected-react-router';
function beginFetch() {
return { type: types.FETCH };
}
function fetchSuccess(data) {
return { type: types.FETCH_SUCCESS,
data };
}
function fetchError(data) {
return { type: types.FETCH_ERROR,
data };
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
export function fetch(shortName) {
return (dispatch, getState) => {
dispatch(beginFetch());
return axios
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/errors", {withCredentials: true})
.then(response => {
if (response.data) {
var data ={
shortName: shortName,
errors: response.data
}
dispatch(fetchSuccess(data));
} else {
dispatch(fetchError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -1,3 +0,0 @@
export const FETCH = "FETCH"
export const FETCH_SUCCESS = "FETCH_SUCCESS"
export const FETCH_ERROR = "FETCH_ERROR"

View File

@@ -1,42 +0,0 @@
import {
FETCH,
FETCH_SUCCESS,
FETCH_ERROR
} from "./error-constants";
const error = (
state = {
isWaiting: false,
items: {}
},
action
) => {
switch (action.type) {
case FETCH:
return Object.assign({}, state, { isWaiting: true});
case FETCH_SUCCESS:
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: action.data.errors
}
});
case FETCH_ERROR:
return Object.assign({}, state, {
isWaiting: false
});
default:
return state;
}
};
export default error;

View File

@@ -1,20 +0,0 @@
const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
const statistic = (
state = {},
action
) => {
switch (action.type) {
case FETCH_SUCCESS_STATISTIC:
return Object.assign({}, state, {...action.data});
default:
return state;
}
};
export default statistic;

View File

@@ -1,232 +0,0 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./system-constants";
import { push } from 'connected-react-router';
// "Log In" action creators
function beginCreateSystem() {
return { type: types.CREATE_SYSTEM };
}
function createSystemSuccess(data) {
return {
type: types.CREATE_SUCCESS_SYSTEM,
data
};
}
function createSystemError(data) {
return {
type: types.CREATE_ERROR_SYSTEM,
data
};
}
function beginUpdateSystem() {
return { type: types.UPDATE_SYSTEM };
}
function updateSystemSuccess(data) {
return { type: types.UPDATE_SUCCESS_SYSTEM, data };
}
function updateSystemError(data) {
return { type: types.UPDATE_ERROR_SYSTEM, data };
}
function beginDeleteSystem() {
return { type: types.DELETE_SYSTEM };
}
function deleteSystemSuccess(data) {
return { type: types.DELETE_SUCCESS_SYSTEM,
data };
}
function deleteSystemError() {
return { type: types.DELETE_ERROR_SYSTEM };
}
function beginFetchSystem() {
return { type: types.FETCH_SYSTEM };
}
function fetchSystemSuccess(data) {
return { type: types.FETCH_SUCCESS_SYSTEM,
data };
}
function fetchStatisticSuccess(data) {
return { type: types.FETCH_SUCCESS_STATISTIC,
data };
}
function fetchSystemError(data) {
return { type: types.FETCH_ERROR_SYSTEM,
data };
}
function beginFetchError() {
return { type: types.FETCH_ERROR };
}
function fetchErrorsSuccess(data) {
return { type: types.FETCH_SUCCESS_ERROR,
data };
}
function fetchErrorsError(data) {
return { type: types.FETCH_ERROR_ERROR,
data };
}
function makeUserRequest(method, data, api = "/login") {
// returns a Promise
return axios({
method: method,
url: api,
data: data,
withCredentials: true
});
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function updateSystem(data) {
return dispatch => {
dispatch(beginUpdateSystem());
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/systems/" + data.shortName, {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(updateSystemSuccess(response.data.system));
} else {
dispatch(updateSystemError());
return response.data.message;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
function shouldFetchSystems(state, subreddit) {
if ((!state.system.items) || (state.system.items.length <1)) {
return true
} else if (state.system.isWaiting) {
return false
} else {
return false
}
}
export function fetchErrors(shortName) {
return (dispatch, getState) => {
dispatch(beginFetchError());
return axios
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/systems", {withCredentials: true})
.then(response => {
if (response.data) {
dispatch(fetchErrorsSuccess(shortName, response.data));
} else {
dispatch(fetchErrorsError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function fetchSystems() {
return (dispatch, getState) => {
if (shouldFetchSystems(getState())) {
dispatch(beginFetchSystem());
return axios
.get(process.env.REACT_APP_ADMIN_SERVER + "/systems", {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(fetchSystemSuccess(response.data.systems));
dispatch(fetchStatisticSuccess(response.data.stats));
} else {
dispatch(fetchSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
};
}
export function deleteSystem(shortName) {
return dispatch => {
dispatch(beginDeleteSystem());
return makeUserRequest("delete", null, process.env.REACT_APP_ADMIN_SERVER + "/systems/"+shortName)
.then(response => {
if (response.data.success) {
dispatch(deleteSystemSuccess({shortName: shortName}));
dispatch(push("/list-systems"));
} else {
dispatch(deleteSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}
export function createSystem(data) {
return dispatch => {
dispatch(beginCreateSystem());
return makeUserRequest("post", data, process.env.REACT_APP_ADMIN_SERVER + "/systems", {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(createSystemSuccess(response.data.system));
} else {
dispatch(createSystemError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -1,16 +0,0 @@
export const CREATE_SYSTEM = "CREATE_SYSTEM"
export const CREATE_SUCCESS_SYSTEM = "CREATE_SUCCESS_SYSTEM"
export const CREATE_ERROR_SYSTEM = "CREATE_ERROR_SYSTEM"
export const UPDATE_SYSTEM = "UPDATE_SYSTEM"
export const UPDATE_SUCCESS_SYSTEM = "UPDATE_SUCCESS_SYSTEM"
export const UPDATE_ERROR_SYSTEM = "UPDATE_ERROR_SYSTEM"
export const DELETE_SYSTEM = "DELETE_SYSTEM"
export const DELETE_SUCCESS_SYSTEM = "DELETE_SUCCESS_SYSTEM"
export const DELETE_ERROR_SYSTEM = "DELETE_ERROR_SYSTEM"
export const FETCH_SYSTEM = "FETCH_SYSTEM"
export const FETCH_SUCCESS_SYSTEM = "FETCH_SUCCESS_SYSTEM"
export const FETCH_ERROR_SYSTEM = "FETCH_ERROR_SYSTEM"
export const FETCH_SUCCESS_STATISTIC = "FETCH_SUCCESS_STATISTIC"
export const FETCH_ERROR = "FETCH_ERROR"
export const FETCH_SUCCESS_ERROR = "FETCH_SUCCESS_ERROR"
export const FETCH_ERROR_ERROR = "FETCH_ERROR_ERROR"

View File

@@ -1,93 +0,0 @@
import {
CREATE_SYSTEM,
CREATE_SUCCESS_SYSTEM,
CREATE_ERROR_SYSTEM,
UPDATE_SYSTEM,
UPDATE_SUCCESS_SYSTEM,
UPDATE_ERROR_SYSTEM,
DELETE_SYSTEM,
DELETE_SUCCESS_SYSTEM,
DELETE_ERROR_SYSTEM,
FETCH_SYSTEM,
FETCH_SUCCESS_SYSTEM,
FETCH_ERROR_SYSTEM
} from "./system-constants";
function updateObjectInArray(array, shortName, updatedItem) {
return array.map( item => {
if(item.shortName !== shortName) {
// This isn't the item we care about - keep it as-is
return item;
}
// Otherwise, this is the one we want - return an updated value
return {
...item,
...updatedItem
};
});
}
const system = (
state = {
isWaiting: false,
items: []},
action
) => {
switch (action.type) {
case DELETE_SYSTEM:
case UPDATE_SYSTEM:
case FETCH_SYSTEM:
case CREATE_SYSTEM:
return Object.assign({}, state, { isWaiting: true});
case DELETE_SUCCESS_SYSTEM:
return Object.assign({}, state, {
items: state.items.filter(item => item.shortName !== action.data.shortName)
});
case UPDATE_SUCCESS_SYSTEM:
return Object.assign({}, state, {
items: updateObjectInArray(state.items, action.data.shortName, action.data)
});
case FETCH_SUCCESS_SYSTEM:
return Object.assign({}, state, {
isWaiting: false,
items: action.data
});
case CREATE_SUCCESS_SYSTEM:
const newItems = state.items.concat({
name: action.data.name,
shortName: action.data.shortName,
description: action.data.description,
systemType: action.data.systemType,
city: action.data.city,
state: action.data.state,
county: action.data.county,
country: action.data.country,
key: action.data.key,
userId: action.data.userId,
id: action.data.id,
showScreenName: action.data.showScreenName,
ignoreUnknownTalkgroup : action.data.ignoreUnknownTalkgroup
});
return Object.assign({}, state, { isWaiting: false,
items: newItems});
case DELETE_ERROR_SYSTEM:
case UPDATE_ERROR_SYSTEM:
case FETCH_ERROR_SYSTEM:
case CREATE_ERROR_SYSTEM:
return Object.assign({}, state, {
isWaiting: false
});
default:
return state;
}
};
export default system;

View File

@@ -1,163 +0,0 @@
import axios, { post } from 'axios';
//import { browserHistory } from "react-router"
import * as types from "./talkgroup-constants";
import { push } from 'connected-react-router';
// "Log In" action creators
function beginImportTalkgroup() {
return { type: types.IMPORT_TALKGROUP };
}
function importTalkgroupSuccess(data) {
return {
type: types.IMPORT_SUCCESS_TALKGROUP,
data
};
}
function importTalkgroupError(data) {
return {
type: types.IMPORT_ERROR_TALKGROUP,
data
};
}
function beginExportTalkgroup() {
return { type: types.EXPORT_TALKGROUP };
}
function beginFetchTalkgroup() {
return { type: types.FETCH_TALKGROUP };
}
function fetchTalkgroupSuccess(data) {
return { type: types.FETCH_SUCCESS_TALKGROUP,
data };
}
function fetchTalkgroupError(data) {
return { type: types.FETCH_ERROR_TALKGROUP,
data };
}
export function changeUrl(url) {
return dispatch => {
dispatch(push(url));
};
}
// https://stackoverflow.com/questions/41878838/how-do-i-set-multipart-in-axios-with-react
function fileUpload(shortName, file){
const url = process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/" + shortName + "/import";
const formData = new FormData();
formData.append('file',file)
const config = {
headers: {
'content-type': 'multipart/form-data'
},
withCredentials: true
}
return post(url, formData,config)
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function importTalkgroups(
shortName, file,
successPath // path to redirect to upon successful log in
) {
return dispatch => {
dispatch(beginImportTalkgroup());
return fileUpload(shortName, file)
.then(response => {
if (response.data.success) {
var data = {};
data.shortName = shortName;
data.talkgroups = response.data.talkgroups
dispatch(importTalkgroupSuccess(data));
// use browserHistory singleton to control navigation. Will generate a
// state change for time-traveling as we are using the react-router-redux package
//browserHistory.push(successPath)
} else {
dispatch(importTalkgroupError(response.data.message));
return response.data.message;
}
})
.catch(function(response) {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
return response.message;
}
});
};
}
export function exportTalkgroups(
shortName
) {
return dispatch => {
dispatch(beginExportTalkgroup());
return axios
.get(process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/" + shortName + "/export", { withCredentials: true })
.then(response => {
if (response.data.success) {
var data = {shortName: shortName, talkgroups: response.data.talkgroups}
dispatch(fetchTalkgroupSuccess( data));
} else {
dispatch(fetchTalkgroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
};
function shouldFetchTalkgroups(state, shortName) {
if (!state.talkgroup.items.hasOwnProperty(shortName)) {
return true
} else if (state.talkgroup.isWaiting) {
return false
} else {
return false
}
}
export function fetchTalkgroups(shortName) {
return (dispatch, getState) => {
if (shouldFetchTalkgroups(getState(), shortName)) {
dispatch(beginFetchTalkgroup());
return axios
.get(process.env.REACT_APP_ADMIN_SERVER + "/talkgroups/"+shortName, { withCredentials: true })
.then(response => {
if (response.data.success) {
var data = {shortName: shortName, talkgroups: response.data.talkgroups}
dispatch(fetchTalkgroupSuccess( data));
} else {
dispatch(fetchTalkgroupError());
let registerMessage = response.data.message;
return registerMessage;
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened in setting up the request that triggered an Error
console.log("Error", response.message);
}
});
};
};
}

View File

@@ -1,9 +0,0 @@
export const IMPORT_TALKGROUP = "IMPORT_TALKGROUP"
export const IMPORT_SUCCESS_TALKGROUP = "IMPORT_SUCCESS_TALKGROUP"
export const IMPORT_ERROR_TALKGROUP = "IMPORT_ERROR_TALKGROUP"
export const EXPORT_TALKGROUP = "EXPORT_TALKGROUP"
export const EXPORT_SUCCESS_TALKGROUP = "EXPORT_SUCCESS_TALKGROUP"
export const EXPORT_ERROR_TALKGROUP = "EXPORT_ERROR_TALKGROUP"
export const FETCH_TALKGROUP = "FETCH_TALKGROUP"
export const FETCH_SUCCESS_TALKGROUP = "FETCH_SUCCESS_TALKGROUP"
export const FETCH_ERROR_TALKGROUP = "FETCH_ERROR_TALKGROUP"

View File

@@ -1,51 +0,0 @@
import {
IMPORT_TALKGROUP,
IMPORT_SUCCESS_TALKGROUP,
IMPORT_ERROR_TALKGROUP,
EXPORT_TALKGROUP,
EXPORT_SUCCESS_TALKGROUP,
EXPORT_ERROR_TALKGROUP,
FETCH_TALKGROUP,
FETCH_SUCCESS_TALKGROUP,
FETCH_ERROR_TALKGROUP
} from "./talkgroup-constants";
const talkgroup = (
state = {
isWaiting: false,
items: {}},
action
) => {
switch (action.type) {
case FETCH_TALKGROUP:
case IMPORT_TALKGROUP:
case EXPORT_TALKGROUP:
return Object.assign({}, state, { isWaiting: true});
case IMPORT_SUCCESS_TALKGROUP:
case FETCH_SUCCESS_TALKGROUP:
return Object.assign({}, state, {
isWaiting: false,
items: {
...state.items,
[action.data.shortName]: action.data.talkgroups
}
});
case FETCH_ERROR_TALKGROUP:
case IMPORT_ERROR_TALKGROUP:
case EXPORT_ERROR_TALKGROUP:
case EXPORT_SUCCESS_TALKGROUP:
return Object.assign({}, state, {
isWaiting: false
});
default:
return state;
}
};
export default talkgroup;

View File

@@ -1,46 +0,0 @@
import axios from "axios";
//import { browserHistory } from "react-router"
import * as types from "./user-constants";
import { push } from 'connected-react-router';
// "Log Out" action creators
function beginLogout() {
return { type: types.LOGOUT_USER };
}
function logoutSuccess() {
return { type: types.LOGOUT_SUCCESS_USER };
}
function logoutError() {
return { type: types.LOGOUT_ERROR_USER };
}
// Example of an Async Action Creator
// http://redux.js.org/docs/advanced/AsyncActions.html
export function manualLogout() {
return dispatch => {
dispatch(beginLogout());
return axios
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/logout", {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(logoutSuccess());
// use browserHistory singleton to control navigation. Will generate a
// state change for time-traveling as we are using the react-router-redux package
//browserHistory.push("/") // logout to home page
dispatch(push("/"));
} else {
dispatch(logoutError());
}
})
.catch(response => {
if (response instanceof Error) {
// Something happened during logout that triggered an Error
console.log("Error", response.message);
}
});
};
}

View File

@@ -1,6 +0,0 @@
export const LOGIN_SUCCESS_USER = "LOGIN_SUCCESS_USER"
export const LOGIN_EMAIL_ERROR_USER = "LOGIN_EMAIL_ERROR_USER"
export const LOGIN_ERROR_USER = "LOGIN_ERROR_USER"
export const LOGOUT_USER = "LOGOUT_USER"
export const LOGOUT_SUCCESS_USER = "LOGOUT_SUCCESS_USER"
export const LOGOUT_ERROR_USER = "LOGOUT_ERROR_USER"

View File

@@ -1,55 +0,0 @@
import {
LOGOUT_USER,
LOGOUT_SUCCESS_USER,
LOGOUT_ERROR_USER,
LOGIN_SUCCESS_USER,
LOGIN_ERROR_USER,
LOGIN_EMAIL_ERROR_USER
} from "./user-constants";
const user = (
state = {
isWaiting: false,
authenticated: false,
userId: ""
},
action
) => {
switch (action.type) {
case LOGIN_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: true,
...action.data
});
case LOGIN_EMAIL_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false,
userId: action.data.userId
});
case LOGIN_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false
});
case LOGOUT_USER:
return Object.assign({}, state, { isWaiting: true });
case LOGOUT_SUCCESS_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: false,
email: "",
userId: ""
});
case LOGOUT_ERROR_USER:
return Object.assign({}, state, {
isWaiting: false,
authenticated: true
});
default:
return state;
}
};
export default user;

View File

@@ -0,0 +1,201 @@
// Import the RTK Query methods from the React-specific entry point
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
/*
const axiosBaseQuery =
({ baseUrl } = { baseUrl: '' }) =>
async ({ url, method, data, params }) => {
try {
const result = await axios({ url: baseUrl + url, method, data, params })
return { data: result.data }
} catch (axiosError) {
let err = axiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message,
},
}
}
}*/
// 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" }),
providesTags: (result) =>
result
? [...result.map(({ _id }) => ({ type: 'Group', _id })), 'Group']
: ['Group'],
}),
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
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;
dispatch(
apiSlice.util.updateQueryData('getGroups', newGroup.shortName, (groups) => {
groups.push(newGroup);
})
)
} catch { }
},
}),
deleteGroup: builder.mutation({
query: (group) => ({
url: `/groups/${group.shortName}/${group._id}`,
method: 'DELETE',
credentials: "include"
}),
async onQueryStarted(group, { dispatch, queryFulfilled }) {
try {
await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => {
const groupIndex = groups.findIndex((obj => obj.groupId === group.groupId));
groups.splice(groupIndex,1);
})
)
} catch { }
},
}),
saveGroupOrder: builder.mutation({
query: ({shortName, order}) => ({
url: `/groups/${shortName}/reorder`,
method: 'POST',
credentials: "include",
body: order,
}),
//invalidatesTags: (result, error, group) => ['Group'],
}),
importTalkgroups: builder.mutation({
query: ({shortName,file}) => ({
url: `${process.env.REACT_APP_ADMIN_SERVER}/talkgroups/${shortName}/import`,
method: 'POST',
credentials: "include",
body: file,
}),
async onQueryStarted({shortName,file}, { dispatch, queryFulfilled }) {
try {
const { data: newTalkgroups } = await queryFulfilled;
dispatch(
apiSlice.util.updateQueryData('getTalkgroups', shortName, (talkgroups) => {
talkgroups.length = 0; // Clear your array
talkgroups.push(...newTalkgroups);
})
)
} catch { }
},
//invalidatesTags: (result, error, group) => ['Group'],
}),
getSystems: builder.query({
// The URL for the request is '/fakeApi/posts'
query: () => ({ url: `/systems`, credentials: "include" })
}),
createSystem: builder.mutation({
query: (system) => ({
url: `/systems`,
method: 'POST',
credentials: "include",
body: system,
}),
async onQueryStarted(system, { dispatch, queryFulfilled }) {
try {
const { data: newSystem } = await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
systems.push(newSystem);
})
)
} catch {}
},
}),
updateSystem: builder.mutation({
query: (system) => ({
url: `/systems/${system.shortName}`,
method: 'POST',
credentials: "include",
body: system,
}),
async onQueryStarted(system, { dispatch, queryFulfilled }) {
try {
const { data: updatedSystem } = await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
const systemIndex = systems.findIndex((obj => obj.shortName === updatedSystem.shortName));
systems[systemIndex] = updatedSystem;
})
)
} catch {}
},
}),
deleteSystem: builder.mutation({
query: (shortName) => ({
url: `/systems/${shortName}`,
method: 'DELETE',
credentials: "include"
}),
async onQueryStarted(shortName, { dispatch, queryFulfilled }) {
try {
await queryFulfilled
dispatch(
apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => {
const systems = systemsData.systems;
const systemIndex = systems.findIndex((obj => obj.shortName === shortName));
systems.splice(systemIndex, 1);
})
)
} catch {}
},
}),
})
})
// Export the auto-generated hook for the `getPosts` query endpoint
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = 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
}

Some files were not shown because too many files have changed in this diff Show More