Admin is mostly working

This commit is contained in:
Luke Berndt
2021-01-04 22:22:39 -05:00
parent 0deadc385f
commit c8b4f02d06
158 changed files with 17683 additions and 3661 deletions

View File

@@ -1,12 +0,0 @@
{
"presets":
["@babel/preset-env",
"@babel/react"],
"plugins": [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-transform-runtime", {
"regenerator": true
}],
["lodash", { "id": ["lodash", "semantic-ui-react"] }]
]
}

View File

@@ -1,3 +0,0 @@
node_modules
lib
public

12
admin/.env Normal file
View File

@@ -0,0 +1,12 @@
REACT_APP_BACKEND_SERVER=https://api.openmhz.com
REACT_APP_FRONTEND_SERVER=https://openmhz.com
REACT_APP_SOCKET_SERVER=wss://api.openmhz.com
REACT_APP_ACCOUNT_SERVER=https://account.openmhz.com
REACT_APP_ADMIN_SERVER=localhost:3000
REACT_APP_ADMIN_EMAIL=luke@robotastic.com
REACT_APP_SITE_NAME=OpenMHz
REACT_APP_ARCHIVE_DAYS=30
REACT_APP_PRO_PLAN=1
REACT_APP_FREE_PLAN=0
REACT_APP_PRO_PLAN_ARCHIVE=30
REACT_APP_FREE_PLAN_ARCHIVE=7

1
admin/.eslintcache Normal file

File diff suppressed because one or more lines are too long

59
admin/.gitignore vendored
View File

@@ -1,36 +1,23 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
lib
uploads/
# Dependency directory
node_modules
assets
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@@ -1,21 +0,0 @@
# docker build -t trunk-server-frontend .
# do a `git pull` in trunk-server-frontend to update
FROM node:10
ENV HOME=/home/app
RUN mkdir -p /home/app
COPY ./package.json /tmp
RUN cd /tmp && npm install
RUN cp -a /tmp/node_modules /home/app
# add `/usr/src/app/node_modules/.bin` to $PATH
ENV PATH /usr/app/node_modules/.bin:$PATH
WORKDIR $HOME
COPY . $HOME/
RUN npm run build
CMD ["npm", "run", "start"]

View File

@@ -0,0 +1,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app), using the [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/) template.
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `yarn build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

View File

@@ -1,10 +0,0 @@
/*
Called during development only. Allows us to use ES6 and JSX in our server code.
*/
require('@babel/register')({
presets: ["@babel/preset-env",
"@babel/react"]
});
require("./src/server/index")

12
admin/env-example Normal file
View File

@@ -0,0 +1,12 @@
REACT_APP_BACKEND_SERVER=https://api.openmhz.com
REACT_APP_FRONTEND_SERVER=https://openmhz.com
REACT_APP_SOCKET_SERVER=wss://api.openmhz.com
REACT_ACCOUNT_SERVER=https://account.openmhz.com
REACT_ADMIN_SERVER=https://admin.openmhz.com
REACT_ADMIN_EMAIL=luke@robotastic.com
REACT_APP_SITE_NAME=OpenMHz
REACT_APP_ARCHIVE_DAYS=30
REACT_APP_PRO_PLAN=1
REACT_APP_FREE_PLAN=0
REACT_APP_PRO_PLAN_ARCHIVE=30
REACT_APP_FREE_PLAN_ARCHIVE=7

10
admin/notes.md Normal file
View File

@@ -0,0 +1,10 @@
## Base
start using `npx create-react-app admin --template redux`
## Packages used:
- [connected-react-router](https://github.com/supasate/connected-react-router): Synchronize router state with redux store through
- [Redux Thunk](https://github.com/reduxjs/redux-thunk): lets you do AJAX calls
- [axios](https://github.com/axios/axios) - library for doing HTTP requests
- [react-router-dom](https://reactrouter.com/web/guides/quick-start) - Adds routes to React
- [Semantic UI](https://react.semantic-ui.com/usage) - The UI frontend

6915
admin/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,94 +1,42 @@
{
"name": "react-passport-redux-example",
"version": "1.0.0",
"description": "Demonstration of PassportJS authentication in React w/ Redux.",
"main": "devStart.js",
"scripts": {
"build:lib": "babel src/server --out-dir lib/server",
"build:umd": "webpack --config webpack/webpack.config.dev.js",
"build:umd:min": "webpack --config webpack/webpack.config.prod.js",
"build": "npm run clean && npm run build:lib && npm run build:umd && npm run build:umd:min",
"postinstall": "npm run build",
"clean": "rimraf lib public/assets",
"server": "better-npm-run server",
"server:dev": "better-npm-run server:dev",
"start": "npm run server",
"start:dev": "npm run server:dev"
},
"betterScripts": {
"server": {
"command": "node lib/server/index.js",
"env": {
"NODE_ENV": "production",
"MIN_EXT": ".min"
}
},
"server:dev": {
"command": "node devStart.js",
"env": {
"NODE_ENV": "development"
}
}
},
"name": "admin",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/runtime": "7.3.1",
"@vx/axis": "^0.0.184",
"@vx/curve": "^0.0.182",
"@vx/glyph": "^0.0.183",
"@vx/group": "^0.0.183",
"@vx/legend": "^0.0.183",
"@vx/mock-data": "^0.0.185",
"@vx/responsive": "0.0.184",
"@vx/scale": "0.0.182",
"@vx/shape": "0.0.184",
"@vx/tooltip": "0.0.184",
"axios": "^0.18.0",
"babel-loader": "8.0.5",
"bcrypt-nodejs": "^0.0.3",
"better-npm-run": "^0.1.0",
"body-parser": "^1.18.2",
"connect-mongo": "^2.0.1",
"connected-react-router": "6.3.1",
"csv-parse": "^4.3.3",
"d3-time-format": "^2.1.3",
"express": "^4.16.2",
"express-session": "^1.15.6",
"fs-extra": "^7.0.1",
"lodash": "4.17.11",
"mongoose": "^5.4",
"multer": "^1.3.0",
"node-mailjet": "*",
"node-uuid": "^1.4.8",
"passport": "^0.4.0",
"passport-local": "^1.0.0",
"react": "16.8.2",
"react-dom": "16.8.2",
"react-redux": "^6.0.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"redux": "^4.0.1",
"redux-thunk": "^2.2.0",
"semantic-ui-react": "^0.85.0"
"@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"
},
"devDependencies": {
"@babel/cli": "7.2.3",
"@babel/core": "7.3.3",
"@babel/plugin-proposal-class-properties": "7.3.3",
"@babel/plugin-proposal-export-default-from": "7.2.0",
"@babel/plugin-proposal-object-rest-spread": "7.3.2",
"@babel/plugin-transform-runtime": "7.2.0",
"@babel/preset-env": "7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/preset-stage-1": "^7.0.0",
"@babel/register": "^7.0.0",
"babel-plugin-lodash": "3.3.4",
"detect-node": "2.0.4",
"redux-logger": "^3.0.6",
"rimraf": "2.6.3",
"uglifyjs-webpack-plugin": "2.1.1",
"webpack": "4.29.5",
"webpack-cli": "3.2.3",
"webpack-dev-middleware": "3.5.2",
"webpack-hot-middleware": "2.24.3"
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

BIN
admin/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

43
admin/public/index.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React Redux App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
admin/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
admin/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

2
admin/public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

39
admin/src/App.css Normal file
View File

@@ -0,0 +1,39 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-float infinite 3s ease-in-out;
}
}
.App-header {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
}
.App-link {
color: rgb(112, 76, 182);
}
@keyframes App-logo-float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(10px)
}
100% {
transform: translateY(0px)
}
}

58
admin/src/App.js Normal file
View File

@@ -0,0 +1,58 @@
import React from 'react';
import logo from './logo.svg';
import { Counter } from './features/counter/Counter';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Counter />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<span>
<span>Learn </span>
<a
className="App-link"
href="https://reactjs.org/"
target="_blank"
rel="noopener noreferrer"
>
React
</a>
<span>, </span>
<a
className="App-link"
href="https://redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux
</a>
<span>, </span>
<a
className="App-link"
href="https://redux-toolkit.js.org/"
target="_blank"
rel="noopener noreferrer"
>
Redux Toolkit
</a>
,<span> and </span>
<a
className="App-link"
href="https://react-redux.js.org/"
target="_blank"
rel="noopener noreferrer"
>
React Redux
</a>
</span>
</header>
</div>
);
}
export default App;

15
admin/src/App.test.js Normal file
View File

@@ -0,0 +1,15 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import store from './app/store';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>
);
expect(getByText(/learn/i)).toBeInTheDocument();
});

View File

@@ -2,10 +2,6 @@ import React, { Component } from "react"
import NavigationContainer from "./NavigationContainer"
class App extends Component {
constructor(props) {
super(props);
}
render() {
return(
<div>

View File

@@ -1,6 +1,6 @@
import React, { Component } from "react";
import { Link } from "react-router-dom";
import { Menu, Dropdown, Icon, Header } from "semantic-ui-react";
import { Menu, Dropdown } from "semantic-ui-react";
const navStyle = {
marginBottom: "30px"
@@ -21,37 +21,32 @@ class Navigation extends Component {
};
render() {
var profileLink = account_server + "/profile";
var billingLink = account_server + "/billing";
var profileLink = process.env.REACT_APP_ACCOUNT_SERVER + "/profile";
return (
<div>
{this.props.user.authenticated ? (
<Menu style={navStyle}>
<Menu.Item name="systems" header>
{site_name}
</Menu.Item>
<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">
<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 href={billingLink}>Billing</Dropdown.Item> */}
<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 style={blankStyle} />
)}
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { connect } from "react-redux"
import * as userActions from "../../User/user-actions"
import Navigation from "../pure/Navigation"
import * as userActions from "../User/user-actions"
import Navigation from "./Navigation"
// Function passed in to `connect` to subscribe to Redux store updates.

View File

@@ -1,13 +1,12 @@
// in src/restricted.js
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import * as userActions from "../../User/user-actions"
import * as types from "../../User/user-constants";
import * as types from "../User/user-constants";
import axios from "axios";
/**
* Higher-order component (HOC) to wrap restricted pages
*/
export default (BaseComponent, store) => {
const restricted = (BaseComponent, store) => {
class Restricted extends Component {
componentWillMount() {
this.checkAuthentication(this.props);
@@ -24,7 +23,7 @@ export default (BaseComponent, store) => {
const authenticated = store.getState().user.authenticated;
if (!authenticated) {
axios
.get(account_server + "/authenticated", {withCredentials: true})
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/authenticated", {withCredentials: true})
.then(response => {
if (response.data.success) {
const user = response.data.user
@@ -33,14 +32,14 @@ export default (BaseComponent, store) => {
data: user
});
} else {
window.location = account_server + "/login?nextLocation=admin";
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
/*history.replace({ pathname: '/login',
state: { nextPathname: params.location.pathname } });*/
}
})
.catch(response => {
if (response instanceof Error) {
window.location = account_server + "/login?nextLocation=admin";
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/login?nextLocation=admin";
// Something happened during logout that triggered an Error
console.log("Error", response.message);
}
@@ -54,3 +53,4 @@ export default (BaseComponent, store) => {
}
return withRouter(Restricted);
}
export default restricted

View File

@@ -4,33 +4,18 @@ import { Container, Header } from "semantic-ui-react";
class About extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Container text>
<Header size='large'>Share the Air</Header>
<p>{site_name} makes it easy to share and archive recordings from <a href="https://github.com/robotastic/trunk-recorder">Trunk Recorder</a>.
<Header size='large'>Share the Air</Header>
<p>{process.env.REACT_APP_SITE_NAME} makes it easy to share and archive recordings from <a href="https://github.com/robotastic/trunk-recorder">Trunk Recorder</a>.
Sharing access to these radio systems allows for other members of your community to follow local events.
Raising awareness of what our local fire, police and EMS have to go through everyday should
lead to greater appreciation for all the work they do that goes largely unseen.</p>
<p>Each of the radio systems comes from different contributers around the country. The different
systems may come and go as new ones are added and taken down. {site_name} only maintains the archive that
<p>Each of the radio systems comes from different contributers around the country. The different
systems may come and go as new ones are added and taken down. {process.env.REACT_APP_SITE_NAME} only maintains the archive that
is available online.</p>
<p> - Luke <a href="mailto:{admin_email}?Subject={site_name}" target="_top">{admin_email}</a> </p>
<p> - Luke <a href="mailto:{process.env.REACT_APP_ADMIN_EMAIL}?Subject={process.env.REACT_APP_SITE_NAME}" target="_top">{process.env.REACT_APP_ADMIN_EMAIL}</a> </p>
</Container>
);
}

View File

@@ -17,11 +17,6 @@ import {
SAVE_ORDER_SUCCESS_GROUP
} from "./group-constants";
function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
}
function updateItemInArray(array, updatedItem) {
const updatedItems = array.map(item => {
@@ -73,7 +68,7 @@ const group = (
});
case DELETE_SUCCESS_GROUP:
const delGroups = state.items[action.data.shortName].filter(x => x._id != action.data.groupId);
const delGroups = state.items[action.data.shortName].filter(x => x._id !== action.data.groupId);
return Object.assign({}, state, {
isWaiting: false,
items: {

View File

@@ -1,45 +1,19 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Link } from 'react-router-dom'
import SystemCard from "./SystemCard";
import {
Container,
Divider,
Header,
Card,
Form,
Grid,
Segment,
Input,
Icon,
Button,
Message
} from "semantic-ui-react";
// ----------------------------------------------------
const requestMessageStyle = {
color: "red"
};
function SystemList(props) {
const systems = props.systems;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
// ----------------------------------------------------
class ListSystems extends Component {
constructor(props) {
super(props);
}
state = {
requestMessage: "",
}
componentDidMount() {

View File

@@ -260,7 +260,7 @@ class System extends Component {
}
handleUpgradePlan() {
window.location = account_server + "/update-plans";
window.location = process.env.REACT_APP_ACCOUNT_SERVER + "/update-plans";
}
handleUpload(file) {
@@ -374,7 +374,7 @@ class System extends Component {
'",\n\t\t\t"apiKey": "' +
system.key +
'"\n\t\t}\n\t],\n\t"uploadServer": "' +
backend_server +
process.env.REACT_APP_BACKEND_SERVER +
'"\n}';
switch (system.systemType) {
@@ -457,7 +457,7 @@ class System extends Component {
</Segment>
<Segment>
<Header size="small">Upload Server</Header>
{backend_server}
{process.env.REACT_APP_BACKEND_SERVER}
</Segment>
<Segment>
<Header size="small">Example</Header>

View File

@@ -3,36 +3,22 @@ import React, { Component } from "react";
import {
Card,
Icon,
Label,
Header
} from "semantic-ui-react";
function planName(planType) {
switch(planType) {
case freePlanValue:
return "Free";
case proPlanValue:
return "Pro";
}
}
class SystemCard extends Component {
constructor(props) {
super(props);
}
render() {
var location = "";
const system = this.props.system;
var plan=null;
if (system) {
if (system.planType) {
plan = planName(system.planType);
}
switch (system.systemType) {
case "state":
location = system.state;
@@ -46,6 +32,8 @@ render() {
case "international":
location = system.country;
break;
default:
location = "unknown";
}
}
return (
@@ -57,7 +45,7 @@ render() {
{system.name}
<Header.Subheader>{system.screenName}</Header.Subheader>
</Header.Content>
</Header>{plan&&<Label color='orange' ribbon='right'>{plan}</Label>}
</Header>
</Card.Content><Card.Content>
<Card.Description>{system.description}</Card.Description>

View File

@@ -5,7 +5,7 @@ import {
Header
} from "semantic-ui-react";
class EditSystem extends Component {
class UpdateSystem extends Component {
constructor(props) {
super(props);
this.handleSystemSubmit = this.handleSystemSubmit.bind(this);
@@ -45,4 +45,4 @@ class EditSystem extends Component {
}
}
export default EditSystem
export default UpdateSystem

View File

@@ -1,5 +1,5 @@
import { connect } from "react-redux"
import EditSystem from "./EditSystem"
import UpdateSystem from "./UpdateSystem"
import * as systemActions from "./system-actions"
@@ -15,4 +15,4 @@ const mapStateToProps = (state, props) => {
export default connect(
mapStateToProps,
systemActions
)(EditSystem)
)(UpdateSystem)

View File

@@ -39,7 +39,7 @@ export function fetch(shortName) {
dispatch(beginFetch());
return axios
.get(backend_server + "/" + shortName + "/errors")
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/errors")
.then(response => {
if (response.data) {
var data ={

View File

@@ -67,7 +67,7 @@ function fetchSystemError(data) {
data };
}
function beginFetchErrors() {
function beginFetchError() {
return { type: types.FETCH_ERROR };
}
@@ -103,11 +103,9 @@ export function updateSystem(data) {
return dispatch => {
dispatch(beginUpdateSystem());
return makeUserRequest("post", data, "/systems/" + data.shortName)
return makeUserRequest("post", data, process.env.REACT_APP_BACKEND_SERVER + "/systems/" + data.shortName)
.then(response => {
if (response.data.success) {
const user = response.data.user
dispatch(updateSystemSuccess(response.data.system));
} else {
dispatch(updateSystemError());
@@ -139,7 +137,7 @@ export function fetchErrors(shortName) {
dispatch(beginFetchError());
return axios
.get(backend_server + "/" + shortName + "/systems")
.get(process.env.REACT_APP_BACKEND_SERVER + "/" + shortName + "/systems")
.then(response => {
if (response.data) {
dispatch(fetchErrorsSuccess(shortName, response.data));
@@ -164,7 +162,7 @@ export function fetchSystems() {
dispatch(beginFetchSystem());
return axios
.get("/systems")
.get(process.env.REACT_APP_ADMIN_SERVER + "/systems")
.then(response => {
if (response.data.success) {
dispatch(fetchSystemSuccess(response.data.systems));
@@ -189,7 +187,7 @@ export function deleteSystem(shortName) {
return dispatch => {
dispatch(beginDeleteSystem());
return makeUserRequest("delete", null, "/systems/"+shortName)
return makeUserRequest("delete", null, process.env.REACT_APP_BACKEND_SERVER + "/systems/"+shortName)
.then(response => {
if (response.data.success) {
dispatch(deleteSystemSuccess({shortName: shortName}));
@@ -213,7 +211,7 @@ export function createSystem(data) {
return dispatch => {
dispatch(beginCreateSystem());
return makeUserRequest("post", data, "/systems")
return makeUserRequest("post", data, process.env.REACT_APP_BACKEND_SERVER + "/systems")
.then(response => {
if (response.data.success) {
dispatch(createSystemSuccess(response.data.system));

View File

@@ -13,12 +13,6 @@ import {
FETCH_ERROR_SYSTEM
} from "./system-constants";
function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
}
function updateObjectInArray(array, shortName, updatedItem) {
return array.map( item => {
if(item.shortName !== shortName) {
@@ -43,6 +37,7 @@ const system = (
) => {
switch (action.type) {
case DELETE_SYSTEM:
case UPDATE_SYSTEM:
case FETCH_SYSTEM:
case CREATE_SYSTEM:
@@ -80,6 +75,7 @@ const system = (
return Object.assign({}, state, { isWaiting: false,
items: newItems});
case DELETE_ERROR_SYSTEM:
case UPDATE_ERROR_SYSTEM:
case FETCH_ERROR_SYSTEM:
case CREATE_ERROR_SYSTEM:

View File

@@ -10,27 +10,6 @@ import {
FETCH_ERROR_TALKGROUP
} from "./talkgroup-constants";
function updateObject(oldObject, newValues) {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
}
function updateItemInArray(array, itemId, updateItemCallback) {
const updatedItems = array.map(item => {
if(item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
}
const talkgroup = (
state = {
isWaiting: false,

View File

@@ -24,7 +24,7 @@ export function manualLogout() {
dispatch(beginLogout());
return axios
.get(account_server + "/logout", {withCredentials: true})
.get(process.env.REACT_APP_ACCOUNT_SERVER + "/logout", {withCredentials: true})
.then(response => {
if (response.data.success) {
dispatch(logoutSuccess());

View File

@@ -1,80 +0,0 @@
import React from "react"
import { render } from "react-dom"
import { Provider } from "react-redux"
import { Switch, Route} from 'react-router-dom'
import { applyMiddleware, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import { ConnectedRouter } from 'connected-react-router'
import thunk from "redux-thunk"
import {createLogger} from "redux-logger"
import isNode from "detect-node"
import createRootReducer from './reducers'
import { createBrowserHistory } from 'history'
import About from "./components/pure/About"
import App from "./components/container/App"
import NavigationContainer from "./components/container/NavigationContainer"
import Restricted from "./components/container/Restricted"
// System
import SystemContainer from "./System/SystemContainer"
import EditSystemContainer from "./System/EditSystemContainer"
import CreateSystemContainer from "./System/CreateSystemContainer"
import ListSystemsContainer from "./System/ListSystemsContainer"
// Create a history of your choosing (we're using a browser history in this case)
export const history = createBrowserHistory()
// Build the middleware for intercepting and dispatching navigation actions
const historyMiddleware = routerMiddleware(history)
let middleware = [
thunk,
historyMiddleware
]
// only add Redux-Logger on the client-side because it causes problems server-side.
if (!isNode) {
middleware.push(createLogger())
}
// Add the reducer to your store on the `router` key
// Also apply our middleware for navigatin
const store = createStore(
createRootReducer(history), // root reducer with router state
applyMiddleware(thunk,
historyMiddleware, createLogger())
)
/*
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={Restricted(EditSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={EditSystemContainer} />
<Route path="/create-system" component={CreateSystemContainer} />
<Route path="/list-systems" component={ListSystemsContainer} />
*/
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<App>
<Switch>
<Route path="/about" component={About} />
<Route exact path="/" component={Restricted(ListSystemsContainer, store)} />
{/* -- System -- */}
<Route path="/system/:shortName" component={Restricted(SystemContainer, store)} />
<Route path="/update-system/:shortName" component={Restricted(EditSystemContainer, store)} />
<Route path="/create-system" component={Restricted(CreateSystemContainer, store)} />
<Route path="/list-systems" component={Restricted(ListSystemsContainer, store)} />
</Switch>
</App>
</div>
</ConnectedRouter>
</Provider>
, document.getElementById("app"))

View File

@@ -1,31 +0,0 @@
import React, { Component } from "react";
import { Modal, Icon, Button, Header } from "semantic-ui-react";
class Message extends Component {
constructor(props) {
super(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>
<Icon name='checkmark' /> Got it
</Button>
</Modal.Actions>
</Modal>
);
}
}
export default Message;

View File

@@ -1,5 +0,0 @@
.field {
padding-top: 10px;
padding-left: 10px;
padding-right: 10px;
}

View File

@@ -1,21 +0,0 @@
import { combineReducers } from "redux"
import { connectRouter } from 'connected-react-router'
import system from "../System/system-reducer"
import statistic from "../System/statistic-reducer"
import talkgroup from "../Talkgroups/talkgroup-reducer"
import group from "../Group/group-reducer"
import error from "../System/error-reducer"
import permission from "../Permission/permission-reducer"
import user from "../User/user-reducer"
export default (history) => combineReducers({
router: connectRouter(history),
system,
talkgroup,
statistic,
error,
group,
permission,
user
})

View File

@@ -0,0 +1,60 @@
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

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

View File

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

13
admin/src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

37
admin/src/index.js Normal file
View File

@@ -0,0 +1,37 @@
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 'semantic-ui-css/semantic.min.css'
import App from "./App/App"
import Restricted from "./App/Restricted"
import About from "./Components/About"
import ListSystemsContainer from "./System/ListSystemsContainer"
import UpdateSystemContainer from "./System/UpdateSystemContainer"
import CreateSystemContainer from "./System/CreateSystemContainer"
const store = configureStore(/* provide initial state if any */)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<>
<App>
<Switch>
<Route path="/about" component={About} />
<Route path="/update-system/:shortName" component={UpdateSystemContainer} />
<Route path="/create-system" component={CreateSystemContainer} />
<Route path="/list-systems" component={ListSystemsContainer} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</App>
</>
</ConnectedRouter>
</Provider>,
document.getElementById('root')
)

1
admin/src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,26 @@
// https://github.com/supasate/connected-react-router
// configureStore.js
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
preloadedState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
thunk,
// ... other middlewares ...
),
),
)
return store
}

View File

@@ -0,0 +1,23 @@
// from https://github.com/supasate/connected-react-router
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
import systemReducer from "../System/system-reducer"
import talkgroupReducer from "../Talkgroups/talkgroup-reducer"
import groupReducer from "../Group/group-reducer"
import errorReducer from "../System/error-reducer"
import permissionReducer from "../Permission/permission-reducer"
import userReducer from "../User/user-reducer"
const createRootReducer = (history) => combineReducers({
router: connectRouter(history),
system: systemReducer,
talkgroup: talkgroupReducer,
group: groupReducer,
user: userReducer,
error: errorReducer,
permission: permissionReducer
})
export default createRootReducer

View File

@@ -1,86 +0,0 @@
import path from "path"
import express from "express"
import session from "express-session"
import bodyParser from "body-parser"
import connectMongo from "connect-mongo"
import secrets from "./secrets"
const MongoStore = connectMongo(session)
var cookie_domain = process.env['COOKIE_DOMAIN'] != null ? process.env['COOKIE_DOMAIN'] : '.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var backend_server = process.env['BACKEND_SERVER'] != null ? process.env['BACKEND_SERVER'] : 'https://api.openmhz.com';
var frontend_server = process.env['FRONTEND_SERVER'] != null ? process.env['FRONTEND_SERVER'] : 'https://openmhz.com';
var socket_server = process.env['SOCKET_SERVER'] != null ? process.env['SOCKET_SERVER'] : 'wss://socket.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
export default function(app, passport) {
app.set("port", 3008)
// X-Powered-By header has no functional value.
// Keeping it makes it easier for an attacker to build the site's profile
// It can be removed safely
app.disable("x-powered-by")
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(express.static(path.join(process.cwd(), 'public')));
const sess = {
resave: true,
saveUninitialized: true,
secret: secrets.sessionSecret,
proxy: true,
name: "sessionId",
cookie: {
httpOnly: true,
secure: false,
domain: cookie_domain
},
store: new MongoStore({
url: secrets.db,
autoReconnect: true
})
}
var node_env = process.env.NODE_ENV;
console.log('--------------------------');
console.log('===> 😊 Starting Server . . .');
console.log('===> Environment: ' + node_env);
/*
if(node_env === 'production') {
console.log('===> 🚦 Note: In order for authentication to work in production');
console.log('===> you will need a secure HTTPS connection');
sess.cookie.secure = true; // Serve secure cookies
}*/
app.use(session(sess))
app.use(passport.initialize())
app.use(passport.session())
app.use('/*', function(req, res, next) {
var allowedOrigins = [ account_server];
allowedOrigins.push(frontend_server);
allowedOrigins.push(backend_server);
allowedOrigins.push(socket_server);
var origin = req.headers.origin;
if (allowedOrigins.indexOf(origin) > -1) {
res.setHeader('Access-Control-Allow-Origin', origin);
} else if (req.headers["user-agent"] == 'TrunkRecorder1.0') {
res.setHeader('Access-Control-Allow-Origin', "*");
} else {
res.setHeader('Access-Control-Allow-Origin', "*");
if (origin) {
console.warn("forcing CORS for: " + origin + " referer: " + req.headers.referer);
}
}
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS,WEBSOCKET');
res.header('Access-Control-Allow-Credentials', 'true');
res.header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Max-Age");
res.header('Access-Control-Max-Age', '600');
next();
});
}

View File

@@ -1,36 +0,0 @@
/*
Configuring local strategy to authenticate strategies
Code modified from : https://github.com/madhums/node-express-mongoose-demo/blob/master/config/passport/local.js
*/
import mongoose from "mongoose"
import { Strategy as LocalStrategy } from "passport-local"
import User from "../../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.
*/
export default new LocalStrategy({
usernameField: "email"
}, (email, password, done) => {
User.findOne({
$or: [{
email
}, {
local: {
email
}
}]
}, (err, user) => {
if(!user) return done(null, false, { message: `Email ${email} not found` })
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user)
} else {
return done(null, false, { message: "Invalid email or password" })
}
})
})
})

View File

@@ -1,26 +0,0 @@
/* Initializing PassportJS */
import User from "../models/user"
import local from "./passport-strategies/local"
export default function (app, passport) {
// Configure Passport authenticated session persistence.
//
// In order to restore authentication state across HTTP requests, Passport needs
// to serialize users into and deserialize users out of the session. The
// typical implementation of this is as simple as supplying the user ID when
// serializing, and querying the user record by ID from the database when
// deserializing.
passport.serializeUser((user, done) => {
done(null, user.id)
})
passport.deserializeUser((id, done) => {
User.findById(id, (err, user) => {
done(err, user)
})
})
// use the following strategies
passport.use(local)
}

View File

@@ -1,15 +0,0 @@
/** Important **/
/** You should not be committing this file to GitHub **/
/** Repeat: DO! NOT! COMMIT! THIS! FILE! TO! YOUR! REPO! **/
var host = process.env['MONGO_NODE_DRIVER_HOST'] != null ? process.env['MONGO_NODE_DRIVER_HOST'] : 'localhost';
var port = process.env['MONGO_NODE_DRIVER_PORT'] != null ? process.env['MONGO_NODE_DRIVER_PORT'] : 27017;
var mongoUrl = 'mongodb://' + host + ':' + port + '/scanner';
const secrets = {
db: mongoUrl,
sessionSecret: "letthisbeyoursecret"
}
export default secrets

View File

@@ -1,297 +0,0 @@
import Group from "../models/group";
import System from "../models/system";
import mongoose from "mongoose";
import passport from "passport";
//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;
}
if (req.params.groupId) {
var groupId = req.params.groupId.toLowerCase();
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;
}
}
);
}
}
);
});
};
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.reorderGroups = function(req, res, next) {
//router.post('/system/:shortName/group_order', isLoggedIn, function(req, res) {
process.nextTick(function() {
System.findOne(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, system) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var groupOrder = JSON.parse(req.body.groupOrder);
for (var i = 0; i < groupOrder.length; i++) {
console.log("in group: " + groupOrder[i] + " position: " + i);
Group.findOneAndUpdate(
{
_id: new mongoose.Types.ObjectId(groupOrder[i]),
shortName: req.params.shortName.toLowerCase()
},
{
position: i
},
{
new: true,
upsert: false
},
function(err, group) {
console.log("Save group: " + group._id + " position: " + group.position);
if (err) {
res.json({
success: false,
message: err
});
console.log(
"Error: Group Order - " +
err +
" Shortname: " +
req.params.shortName.toLowerCase()
);
return;
}
}
);
}
res.json({
success: true
});
}
);
});
};
exports.upsertGroup = function(req, res, next) {
//router.post('/system/:shortName/group/:groupId?', isLoggedIn, function(req, res) {
process.nextTick(function() {
System.findOne(
{
shortName: req.params.shortName.toLowerCase()
},
function(err, system) {
if (err) {
console.error(err);
res.json({ success: false, message: err });
return;
}
if (!system) {
console.error(err);
res.json({
success: false,
message: "That Short Name does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
res.json({
success: false,
message: "You are not the user associated with this system."
});
return;
}
var message = "";
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
var talkgroups = JSON.parse(req.body.talkgroups);
console.log(
"Talkgroups: " +
talkgroups +
" for group: " +
groupName +
" array: " +
Array.isArray(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;
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,243 +0,0 @@
import mongoose from "mongoose";
import passport from "passport";
import Permission from "../models/permission";
import System from "../models/system";
import User from "../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

@@ -1,510 +0,0 @@
import mongoose from "mongoose";
import passport from "passport";
import System from "../models/system";
import SystemStat from "../models/system_stat";
import Talkgroup from "../models/talkgroup";
import Group from "../models/group";
import secrets from "../config/secrets";
import uuid from 'node-uuid';
const crypto = require("crypto");
exports.isLoggedIn = function (req, res, next) {
if (req.isAuthenticated()) return next();
res.redirect(account_server + "/login");
};
// -------------------------------------------
exports.listSystems = 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;
}
var 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,
planType,
showScreenName
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
planType,
showScreenName
}))(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;
}
);
}
);
};
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)
});
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!
}
);
Talkgroup.remove({
shortName: shortName
},
function (err) {
if (err) return console.error(err);
// removed!
}
);
Group.remove({
shortName: shortName
},
function (err) {
if (err) return console.error(err);
// removed!
}
);
}
exports.deleteSystem = 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;
}
);
};
// -------------------------------------------
exports.ownSystem = async function (req, res, next) {
try {
var system = await System.findOne({
shortName: req.body.shortName.toLowerCase()
});
if (!system) {
console.error("ERROR: OwnSystem - system does not exist: " + req.body.shortName.toLowerCase());
res.json({
success: false,
message: "That System does not exist."
});
return;
}
if (!system.userId.equals(req.user._id)) {
console.error("ERROR: OwnSystem - not users system " + req.body.shortName.toLowerCase());
res.json({
success: false,
message: "You are not the user associated with this system."
});
console.error("System: " + system.userId + " User: " + req.user._id);
return;
}
res.locals.test = "Yoho!"
res.locals.system = system;
console.log(system);
console.log(res.locals);
next();
} catch (err) {
console.error("ERROR: Unique ShortName - caught: " + err);
res.json({
success: false,
message: err
});
return;
}
}
// -------------------------------------------
exports.uniqueShortName = async function (req, res, next) {
console.error("locals: " + res.locals.shortName + " body: " + req.body.shortName )
try {
var system = await System.findOne({
shortName: res.locals.shortName.toLowerCase()
});
if (system) {
console.error("ERROR: Unique Shortname already in use: " + req.body.shortName.toLowerCase());
res.json({
success: false,
message: "Short Name already in use"
});
return;
} else {
next();
}
} catch (err) {
console.error("ERROR: Unique ShortName - caught: " + err);
res.json({
success: false,
message: err
});
return;
}
}
// -------------------------------------------
exports.updateSystem = async function (req, res, next) {
console.log(res.locals)
res.locals.system.name = res.locals.name;
res.locals.system.city = res.locals.city;
res.locals.system.state = res.locals.state;
res.locals.system.county = res.locals.county;
res.locals.system.country = res.locals.country;
res.locals.system.description = res.locals.description;
res.locals.system.systemType = res.locals.systemType;
res.locals.system.showScreenName = res.locals.showScreenName;
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
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
key,
showScreenName
}))(res.locals.system);
returnSys.id = res.locals.system._id;
res.json({
success: true,
system: returnSys
});
return;
}
});
}
// -------------------------------------------
exports.validateSystem = async function (req, res, next) {
try {
res.locals.showScreenName = req.body.showScreenName;
if (!req.body.name || (req.body.name.length < 2)) {
console.error("ERROR: Validate System - req.body.name");
res.json({
success: false,
message: "System Name is Required"
});
return;
}
res.locals.name = req.body.name.replace(/[^\w\s\.\,\-\_]/gi, '');
if (!req.body.description || (req.body.description.length < 2)) {
console.error("ERROR: Validate System - req.body.description");
res.json({
success: false,
message: "System Description is Required"
});
return;
}
res.locals.description = req.body.description.replace(/[^\w\s\.\,\-\_]/gi, '');
if (!req.body.shortName || (req.body.shortName.length < 2)) {
console.error("ERROR: Validate System - req.body.shortName");
res.json({
success: false,
message: "System ShortName is Required"
});
return;
}
var tempShortName = req.body.shortName.replace(/[^\w]/gi, '');
res.locals.shortName = tempShortName.toLowerCase();
// -------------------- Handle all the different SystemTypes of Locations
// 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.json({
success: false,
message: "Select system location"
});
return;
}
// copy it over
res.locals.systemType = req.body.systemType;
// now check to make sure the correct things for location is filled out
if (req.body.systemType === "state" ||
req.body.systemType === "city" ||
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.json({
success: false,
message: "Select the State for the System"
});
return;
}
res.locals.state = req.body.state;
}
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.json({
success: false,
message: "Enter the city for the System"
});
return;
}
res.locals.city = req.body.city.replace(/[^\w\s\.\,\-\_]/gi, '');
}
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.json({
success: false,
message: "Enter the county for the System"
});
return;
}
res.locals.county = req.body.county.replace(/[^\w\s\.\,\-\_]/gi, '');
}
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.json({
success: false,
message: "Enter the country for the System"
});
return;
}
res.locals.country = req.body.country.replace(/[^\w\s\.\,\-\_]/gi, '');
}
} catch (err) {
console.error("ERROR: Validate System - caught: " + err);
res.json({
success: false,
message: err
});
return;
}
next();
}
// -------------------------------------------
exports.createSystem = async function (req, res, next) {
var key = uuid();
key = crypto
.createHash("sha256")
.update(key)
.update("salt")
.digest("hex");
const system = (({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
showScreenName
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
showScreenName
}))(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,
key
}) => ({
name,
shortName,
description,
systemType,
city,
state,
county,
country,
userId,
showScreenName,
key
}))(newSys);
returnSys.id = newSys._id;
res.json({
success: true,
system: returnSys
});
return;
});
}

View File

@@ -1,253 +0,0 @@
import mongoose from "mongoose";
import passport from "passport";
import Talkgroup from "../models/talkgroup";
import System from "../models/system";
import secrets from "../config/secrets";
import fs from 'fs-extra';
const crypto = require("crypto");
var parse = require('csv-parse');
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 {
for (var i = 0; i < data.length; i++) {
var row = data[i];
var newTg = new Talkgroup();
newTg.num = parseInt(row.num);
newTg.alpha = row.alpha;
newTg.mode = row.mode;
newTg.description = row.description;
newTg.tag = row.tag.toLowerCase();
newTg.group = row.group.toLowerCase();
newTg.priority = row.priority;
newTg.shortName = shortName;
await newTg.save(function(err) {
if (err) {
console.log("Error: CSV import: " + err);
callback(err, null);
}
});
}
callback(null, data);
}
}
);
}
fs.createReadStream(filename).pipe(parser);
// removed!
}
);
}
//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;
}
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;
}
});
} else {
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;
}
);
};

View File

@@ -1,221 +0,0 @@
import fs from "fs"
import mongoose from "mongoose"
import express from "express"
import passport from "passport"
import webpack from "webpack"
import path from "path"
import config from "../../webpack/webpack.config.dev.js"
import secrets from "./config/secrets"
import configurePassport from "./config/passport"
import configureExpress from "./config/express"
import systems from "./controllers/systems"
import groups from "./controllers/groups"
import permissions from "./controllers/permissions"
import talkgroups from "./controllers/talkgroups"
import Permission from "./models/permission";
import "./models/user"
import multer from 'multer'
var upload = multer({
dest: 'uploads/'
});
// -------------------------------------------
const app = express()
// -------------------------------------------
const connect = () => {
mongoose.connect(secrets.db, (err, res) => {
if (err) {
console.log(`Error connecting to ${secrets.db}. ${err}`)
} else {
console.log(`Successfully connected to ${secrets.db}.`)
}
})
}
connect()
mongoose.connection.on("error", console.error)
mongoose.connection.on("disconnected", connect)
// -------------------------------------------
const isDev = process.env.NODE_ENV === "development"
// if in development mode set up the middleware required for hot reloading and rebundling
if(isDev) {
const compiler = webpack(config)
app.use(require("webpack-dev-middleware")(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}))
app.use(require("webpack-hot-middleware")(compiler))
}
/*
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;
}*/
function isOwner(req, res, next) {
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);
}
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
if ( permission.role >= 15) {
return next();
} else {
res.status(401).send({
success: false,
message: "Insufficent Permission, role: " + permission.role
});
}
});
}
}
function isAdmin(req, res, next) {
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);
}
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
if (permission.role >= 10) {
return next();
} else {
res.status(401).send({
success: false,
message: "Insufficent Permission, role: " + permission.role
});
}
});
}
}
function isLoggedIn(req, res, next) {
if (req.isAuthenticated()) return next();
res.status(401).send({
success: false,
message: "Not Authenticated."
});
};
var admin_server = process.env['ADMIN_SERVER'] != null ? process.env['ADMIN_SERVER'] : 'https://admin.openmhz.com';
var admin_email = process.env['ADMIN_EMAIL'] != null ? process.env['ADMIN_EMAIL'] : "luke@openmhz.com";
var beta_frontend_server = process.env['BETA_FRONTEND_SERVER'] != null ? process.env['BETA_FRONTEND_SERVER'] : 'https://beta.openmhz.com';
var backend_server = process.env['BACKEND_SERVER'] != null ? process.env['BACKEND_SERVER'] : 'https://api.openmhz.com';
var frontend_server = process.env['FRONTEND_SERVER'] != null ? process.env['FRONTEND_SERVER'] : 'https://openmhz.com';
var media_server = process.env['MEDIA_SERVER'] != null ? process.env['MEDIA_SERVER'] : 'https://media.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var socket_server = process.env['SOCKET_SERVER'] != null ? process.env['SOCKET_SERVER'] : 'wss://api.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var account_server = process.env['ACCOUNT_SERVER'] != null ? process.env['ACCOUNT_SERVER'] : 'https://account.openmhz.com'; //'https://s3.amazonaws.com/robotastic';
var site_name = process.env['SITE_NAME'] != null ? process.env['SITE_NAME'] : "OpenMHz";
const proPlanValue = process.env['PRO_PLAN'] != null ? process.env['PRO_PLAN'] : 10;
const freePlanValue = process.env['FREE_PLAN'] != null ? process.env['FREE_PLAN'] : 0;
// -------------------------------------------
configurePassport(app, passport)
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.get("/talkgroups/:shortName/export", isLoggedIn, talkgroups.exportTalkgroups)
app.post("/groups/:shortName/reorder", isLoggedIn, groups.reorderGroups);
app.post("/groups/:shortName/:groupId?", isLoggedIn, groups.upsertGroup);
app.get("/groups/:shortName/:groupId?", isLoggedIn, groups.getGroups);
app.delete("/groups/:shortName/:groupId", isLoggedIn, groups.deleteGroup);
app.get("/systems", isLoggedIn, systems.listSystems)
app.delete("/systems/:shortName", isLoggedIn, systems.deleteSystem)
app.post("/systems/:shortName", [isLoggedIn, systems.ownSystem, systems.validateSystem, systems.updateSystem])
app.post("/systems", [isLoggedIn, systems.uniqueShortName, systems.validateSystem, systems.createSystem])
app.get("*", (req, res, next) => {
// if we are in production mode then an extension will be provided, usually ".min"
const minified = process.env.MIN_EXT || ""
// this is the HTML we will send to the client when they request any page. React and React Router
// will take over once the scripts are loaded client-side
const appHTML =
`<!doctype html>
<html lang="">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<title>${site_name} Admin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="/style/style.css"></link>
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/semantic-ui@2.4.1/dist/semantic.min.css" />
<script>
const admin_server = "${admin_server}";
const backend_server = "${backend_server}";
const media_server = "${media_server}";
const socket_server = "${socket_server}";
const account_server = "${account_server}";
const site_name = "${site_name}";
const admin_email = "${admin_email}";
const proPlanValue = ${proPlanValue};
const freePlanValue = ${freePlanValue};
</script>
<style>
body {
font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="/assets/app${minified}.js"></script>
</body>
</html>`
res.status(200).end(appHTML)
})
// start listening to incoming requests
app.listen(app.get("port"), app.get("host"), (err) => {
if (err) {
console.err(err.stack)
} else {
console.log(`App listening on port ${app.get("port")} [${process.env.NODE_ENV} mode]`)
}
})

View File

@@ -1,14 +0,0 @@
var mongoose = require('mongoose');
var groupSchema = mongoose.Schema({
shortName: String,
groupName: String,
groupId: String,
position: Number,
talkgroups: [Number]
});
module.exports = mongoose.model('Group', groupSchema);

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

@@ -1,27 +0,0 @@
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var systemSchema = mongoose.Schema({
name: String,
shortName: String,
systemType: String,
county: String,
country: String,
city: String,
state: String,
description: String,
showScreenName: Boolean,
planType: {type: Number, default: 0},
userId: mongoose.Schema.Types.ObjectId,
key: String
});
systemSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
systemSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('System', systemSchema);

View File

@@ -1,12 +0,0 @@
var mongoose = require('mongoose');
var systemStatSchema = mongoose.Schema({
callTotals: [Number],
errorTotals: [Number],
usageBytes: Number,
usageMb: Number,
shortName: String
}, { collection: 'system_stats' });
module.exports = mongoose.model('SystemStat', systemStatSchema);

View File

@@ -1,17 +0,0 @@
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var talkgroupSchema = mongoose.Schema({
shortName: String,
userId: String,
num: Number,
mode: String,
alpha: String,
description: String,
tag: String,
group: String,
priority: Number
});
module.exports = mongoose.model('Talkgroup', talkgroupSchema);

View File

@@ -1,92 +0,0 @@
// Defining a User Model in mongoose
// Code modified from https://github.com/sahat/hackathon-starter
import bcrypt from "bcrypt-nodejs"
import mongoose from "mongoose"
import crypto from "crypto"
const UserSchema = new mongoose.Schema({
local: {
name: String,
email: String,
password: String
},
email: {
type: String,
unique: true,
lowercase: true
},
password: String,
screenName: {
type: String,
unique: true,
lowercase: true
},
firstName: String,
lastName: String,
location: String,
email: String,
resetPasswordToken: String,
resetPasswordTTL: Date,
confirmEmail: {
type: Boolean,
default: false
},
customerId: {
type: String,
default: ""
},
confirmEmailToken: String,
confirmEmailTTL: Date,
admin: {
type: Boolean,
default: false
},
terms: {
type: Number,
default: 0
},
ver: {
type: Number,
default: 1.1
},
sysCount: Number,
lastLogin: { type : Date, default: Date.now }
})
/**
* Password hash middleware.
*/
UserSchema.pre("save", function(next) {
var user = this
if (!user.isModified("password")) return next()
bcrypt.genSalt(8, (err, salt) => {
if (err) return next(err)
bcrypt.hash(user.password, salt, null, (err, hash) => {
if (err) return next(err)
user.password = hash;
user.local.password = hash;
next()
})
})
})
/*
Defining our own custom document instance method
*/
UserSchema.methods = {
comparePassword: function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.local.password, (err, isMatch) => {
if (err) return cb(err)
cb(null, isMatch)
})
}
}
/**
* Statics
*/
UserSchema.statics = {}
export default mongoose.model("User", UserSchema)

View File

@@ -1,5 +0,0 @@
.field {
padding-top: 10px;
padding-left: 5px;
padding-right: 5px;
}

137
admin/src/serviceWorker.js Normal file
View File

@@ -0,0 +1,137 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

5
admin/src/setupTests.js Normal file
View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@@ -1,16 +0,0 @@
db = db.getMongo().getDB( "scanner" );
db.users.find().forEach(
function(user){
db.systems.updateMany(
{ "user": { $eq: user.email } },
{
$set: { "userId": user._id, "showDisplayName": false },
}
);
print("Upgrading: " + user.email);
});

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