mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-12 16:57:39 -05:00
Admin is mostly working
This commit is contained in:
39
admin/src/App.css
Normal file
39
admin/src/App.css
Normal 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
58
admin/src/App.js
Normal 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
15
admin/src/App.test.js
Normal 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();
|
||||
});
|
||||
@@ -2,10 +2,6 @@ import React, { Component } from "react"
|
||||
import NavigationContainer from "./NavigationContainer"
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return(
|
||||
<div>
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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: {
|
||||
@@ -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() {
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 ={
|
||||
@@ -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));
|
||||
@@ -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:
|
||||
@@ -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,
|
||||
@@ -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());
|
||||
@@ -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"))
|
||||
@@ -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;
|
||||
@@ -1,5 +0,0 @@
|
||||
.field {
|
||||
padding-top: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
60
admin/src/features/counter/Counter.js
Normal file
60
admin/src/features/counter/Counter.js
Normal 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>
|
||||
);
|
||||
}
|
||||
74
admin/src/features/counter/Counter.module.css
Normal file
74
admin/src/features/counter/Counter.module.css
Normal 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
|
||||
}
|
||||
42
admin/src/features/counter/counterSlice.js
Normal file
42
admin/src/features/counter/counterSlice.js
Normal 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
13
admin/src/index.css
Normal 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
37
admin/src/index.js
Normal 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
1
admin/src/logo.svg
Normal 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 |
26
admin/src/redux-router/configureStore.js
Normal file
26
admin/src/redux-router/configureStore.js
Normal 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
|
||||
}
|
||||
23
admin/src/redux-router/reducers.js
Normal file
23
admin/src/redux-router/reducers.js
Normal 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
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -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" })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -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!
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -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]`)
|
||||
}
|
||||
})
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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)
|
||||
@@ -1,5 +0,0 @@
|
||||
.field {
|
||||
padding-top: 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
137
admin/src/serviceWorker.js
Normal file
137
admin/src/serviceWorker.js
Normal 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
5
admin/src/setupTests.js
Normal 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';
|
||||
Reference in New Issue
Block a user