mirror of
https://github.com/openmhz/trunk-server.git
synced 2026-11-13 09:17:30 -05:00
25
README.md
25
README.md
@@ -99,9 +99,9 @@ On MacOS, make the changes in: */etc/hosts*
|
|||||||
# when the system is booting. Do not change this entry.
|
# when the system is booting. Do not change this entry.
|
||||||
##
|
##
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
127.0.0.1 openmhz.test api.openmhz.test admin.openmhz.test media.openmhz.test
|
127.0.0.1 openmhz.test account.openmhz.test api.openmhz.test admin.openmhz.test media.openmhz.test
|
||||||
````
|
````
|
||||||
Then load these values into local DNS: `dscacheutil –flushcache`
|
Then load these values into local DNS: `sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder`
|
||||||
|
|
||||||
|
|
||||||
- In root dir, run: `./docker-test.sh build`
|
- In root dir, run: `./docker-test.sh build`
|
||||||
@@ -144,6 +144,27 @@ From the Host OS run:
|
|||||||
```bash
|
```bash
|
||||||
docker exec -i -t $(docker ps -a | grep mongo | awk '{print $1}') /bin/bash
|
docker exec -i -t $(docker ps -a | grep mongo | awk '{print $1}') /bin/bash
|
||||||
```
|
```
|
||||||
|
Then launch the `mongo` CLI tool and find any users you have created
|
||||||
|
```bash
|
||||||
|
mongo
|
||||||
|
use scanner
|
||||||
|
db.users.find()
|
||||||
|
```
|
||||||
|
|
||||||
|
Now swap out the ObjectId for the user, and then run this command. It will make it so you don't need to confirm the email.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
db.users.updateOne(
|
||||||
|
{ "_id" : ObjectId("63a620d0a63b087b005f6726") },
|
||||||
|
{
|
||||||
|
$set: { "confirmEmail" : true },
|
||||||
|
$currentDate: { lastModified: true }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
There are a few scripts included with the container:
|
There are a few scripts included with the container:
|
||||||
- **clean.js** This script removes all Calls that are over 30 days old
|
- **clean.js** This script removes all Calls that are over 30 days old
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"mongoose": "5.11.10",
|
"mongoose": "5.11.10",
|
||||||
"node-mailjet": "3.3.1",
|
"node-mailjet": "3.3.1",
|
||||||
"passport": "0.4.1",
|
"passport": "0.4.1",
|
||||||
"passport-local": "1.0.0"
|
"passport-local": "1.0.0",
|
||||||
|
"passport-twitter": "1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
account/server/config/passport-strategies/twitter.js
Normal file
18
account/server/config/passport-strategies/twitter.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
var Strategy = require('passport-twitter').Strategy;
|
||||||
|
const User = require("../../models/user");
|
||||||
|
|
||||||
|
const twitter = new Strategy({
|
||||||
|
consumerKey: process.env['TWITTER_CONSUMER_KEY'],
|
||||||
|
consumerSecret: process.env['TWITTER_CONSUMER_SECRET'],
|
||||||
|
callbackURL: '/oauth/callback'
|
||||||
|
},
|
||||||
|
function(token, tokenSecret, profile, cb) {
|
||||||
|
// In this example, the user's Twitter profile is supplied as the user
|
||||||
|
// record. In a production-quality application, the Twitter profile should
|
||||||
|
// be associated with a user record in the application's database, which
|
||||||
|
// allows for account linking and authentication with other identity
|
||||||
|
// providers.
|
||||||
|
return cb(null, profile);
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = local;
|
||||||
File diff suppressed because one or more lines are too long
@@ -235,14 +235,7 @@ exports.upsertGroup = function(req, res, next) {
|
|||||||
var message = "";
|
var message = "";
|
||||||
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
|
var groupName = req.body.groupName.replace(/[^\w\s]/g, "");
|
||||||
var talkgroups = JSON.parse(req.body.talkgroups);
|
var talkgroups = JSON.parse(req.body.talkgroups);
|
||||||
console.log(
|
|
||||||
"Talkgroups: " +
|
|
||||||
talkgroups +
|
|
||||||
" for group: " +
|
|
||||||
groupName +
|
|
||||||
" array: " +
|
|
||||||
Array.isArray(talkgroups)
|
|
||||||
);
|
|
||||||
if (!talkgroups.length || !Array.isArray(talkgroups)) {
|
if (!talkgroups.length || !Array.isArray(talkgroups)) {
|
||||||
message = "Talkgroups need to be selected, group can not be empty ";
|
message = "Talkgroups need to be selected, group can not be empty ";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ exports.listSystems = function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
key,
|
key,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}) => ({
|
}) => ({
|
||||||
name,
|
name,
|
||||||
shortName,
|
shortName,
|
||||||
@@ -62,7 +63,8 @@ exports.listSystems = function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
key,
|
key,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}))(obj);
|
}))(obj);
|
||||||
if (obj.showScreenName) {
|
if (obj.showScreenName) {
|
||||||
rObj.screenName = req.user.screenName;
|
rObj.screenName = req.user.screenName;
|
||||||
@@ -203,11 +205,7 @@ exports.ownSystem = async function (req, res, next) {
|
|||||||
console.error("System: " + system.userId + " User: " + req.user._id);
|
console.error("System: " + system.userId + " User: " + req.user._id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.locals.test = "Yoho!"
|
|
||||||
res.locals.system = system;
|
res.locals.system = system;
|
||||||
console.log(system);
|
|
||||||
console.log(res.locals);
|
|
||||||
next();
|
next();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -271,7 +269,6 @@ exports.uniqueShortName = async function (req, res, next) {
|
|||||||
|
|
||||||
exports.updateSystem = async function (req, res, next) {
|
exports.updateSystem = async function (req, res, next) {
|
||||||
|
|
||||||
console.log(res.locals)
|
|
||||||
res.locals.system.name = res.locals.name;
|
res.locals.system.name = res.locals.name;
|
||||||
res.locals.system.city = res.locals.city;
|
res.locals.system.city = res.locals.city;
|
||||||
res.locals.system.state = res.locals.state;
|
res.locals.system.state = res.locals.state;
|
||||||
@@ -280,6 +277,7 @@ exports.updateSystem = async function (req, res, next) {
|
|||||||
res.locals.system.description = res.locals.description;
|
res.locals.system.description = res.locals.description;
|
||||||
res.locals.system.systemType = res.locals.systemType;
|
res.locals.system.systemType = res.locals.systemType;
|
||||||
res.locals.system.showScreenName = res.locals.showScreenName;
|
res.locals.system.showScreenName = res.locals.showScreenName;
|
||||||
|
res.locals.system.ignoreUnknownTalkgroup = res.locals.ignoreUnknownTalkgroup;
|
||||||
|
|
||||||
res.locals.system.save(function (err) {
|
res.locals.system.save(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -300,7 +298,8 @@ exports.updateSystem = async function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
key,
|
key,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}) => ({
|
}) => ({
|
||||||
name,
|
name,
|
||||||
shortName,
|
shortName,
|
||||||
@@ -312,7 +311,8 @@ exports.updateSystem = async function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
key,
|
key,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}))(res.locals.system);
|
}))(res.locals.system);
|
||||||
returnSys.id = res.locals.system._id;
|
returnSys.id = res.locals.system._id;
|
||||||
res.json({
|
res.json({
|
||||||
@@ -329,7 +329,7 @@ exports.updateSystem = async function (req, res, next) {
|
|||||||
exports.validateSystem = async function (req, res, next) {
|
exports.validateSystem = async function (req, res, next) {
|
||||||
try {
|
try {
|
||||||
res.locals.showScreenName = req.body.showScreenName;
|
res.locals.showScreenName = req.body.showScreenName;
|
||||||
|
res.locals.ignoreUnknownTalkgroup = req.body.ignoreUnknownTalkgroup ;
|
||||||
if (!req.body.name || (req.body.name.length < 2)) {
|
if (!req.body.name || (req.body.name.length < 2)) {
|
||||||
console.error("ERROR: Validate System - req.body.name");
|
console.error("ERROR: Validate System - req.body.name");
|
||||||
res.json({
|
res.json({
|
||||||
@@ -456,7 +456,8 @@ exports.createSystem = async function (req, res, next) {
|
|||||||
state,
|
state,
|
||||||
county,
|
county,
|
||||||
country,
|
country,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}) => ({
|
}) => ({
|
||||||
name,
|
name,
|
||||||
shortName,
|
shortName,
|
||||||
@@ -466,7 +467,8 @@ exports.createSystem = async function (req, res, next) {
|
|||||||
state,
|
state,
|
||||||
county,
|
county,
|
||||||
country,
|
country,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}))(res.locals);
|
}))(res.locals);
|
||||||
system.key = key;
|
system.key = key;
|
||||||
system.userId = mongoose.Types.ObjectId(req.user._id);
|
system.userId = mongoose.Types.ObjectId(req.user._id);
|
||||||
@@ -490,6 +492,7 @@ exports.createSystem = async function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
showScreenName,
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup ,
|
||||||
key
|
key
|
||||||
}) => ({
|
}) => ({
|
||||||
name,
|
name,
|
||||||
@@ -502,6 +505,7 @@ exports.createSystem = async function (req, res, next) {
|
|||||||
country,
|
country,
|
||||||
userId,
|
userId,
|
||||||
showScreenName,
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup ,
|
||||||
key
|
key
|
||||||
}))(newSys);
|
}))(newSys);
|
||||||
returnSys.id = newSys._id;
|
returnSys.id = newSys._id;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ var systemSchema = mongoose.Schema({
|
|||||||
state: String,
|
state: String,
|
||||||
description: String,
|
description: String,
|
||||||
showScreenName: Boolean,
|
showScreenName: Boolean,
|
||||||
|
ignoreUnknownTalkgroup : Boolean,
|
||||||
userId: mongoose.Schema.Types.ObjectId,
|
userId: mongoose.Schema.Types.ObjectId,
|
||||||
key: String
|
key: String
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class SystemForm extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleInputChange = this.handleInputChange.bind(this);
|
this.handleInputChange = this.handleInputChange.bind(this);
|
||||||
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
|
this.handleScreenNameCheckboxChange = this.handleScreenNameCheckboxChange.bind(this);
|
||||||
|
this.handleTalkgroupCheckboxChange = this.handleTalkgroupCheckboxChange.bind(this);
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
this.checkInputs = this.checkInputs.bind(this);
|
this.checkInputs = this.checkInputs.bind(this);
|
||||||
this.render = this.render.bind(this);
|
this.render = this.render.bind(this);
|
||||||
@@ -30,6 +31,7 @@ class SystemForm extends Component {
|
|||||||
county: "",
|
county: "",
|
||||||
country: "",
|
country: "",
|
||||||
showScreenName: false,
|
showScreenName: false,
|
||||||
|
ignoreUnknownTalkgroup: false,
|
||||||
nameError: false,
|
nameError: false,
|
||||||
shortNameError: false,
|
shortNameError: false,
|
||||||
descriptionError: false,
|
descriptionError: false,
|
||||||
@@ -51,6 +53,7 @@ class SystemForm extends Component {
|
|||||||
this.state.county = this.props.system.county;
|
this.state.county = this.props.system.county;
|
||||||
this.state.country = this.props.system.country;
|
this.state.country = this.props.system.country;
|
||||||
this.state.showScreenName = this.props.system.showScreenName;
|
this.state.showScreenName = this.props.system.showScreenName;
|
||||||
|
this.state.ignoreUnknownTalkgroup = this.props.system.ignoreUnknownTalkgroup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we directly to an updating url, the system fetch will come back after the constructer, so we need to catch this and update the values
|
// If we directly to an updating url, the system fetch will come back after the constructer, so we need to catch this and update the values
|
||||||
@@ -66,6 +69,7 @@ class SystemForm extends Component {
|
|||||||
county: this.props.system.county,
|
county: this.props.system.county,
|
||||||
country: this.props.system.country,
|
country: this.props.system.country,
|
||||||
showScreenName: this.props.system.showScreenName,
|
showScreenName: this.props.system.showScreenName,
|
||||||
|
ignoreUnknownTalkgroup: this.props.system.ignoreUnknownTalkgroup,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,10 +152,13 @@ class SystemForm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
handleInputChange = (e, { name, value }) => this.setState({ [name]: value });
|
||||||
handleCheckboxChange() {
|
handleScreenNameCheckboxChange() {
|
||||||
this.setState({ showScreenName: !this.state.showScreenName });
|
this.setState({ showScreenName: !this.state.showScreenName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTalkgroupCheckboxChange() {
|
||||||
|
this.setState({ ignoreUnknownTalkgroup: !this.state.ignoreUnknownTalkgroup });
|
||||||
|
}
|
||||||
handleSubmit(event) {
|
handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let inputError = this.checkInputs();
|
let inputError = this.checkInputs();
|
||||||
@@ -166,7 +173,8 @@ class SystemForm extends Component {
|
|||||||
state,
|
state,
|
||||||
county,
|
county,
|
||||||
country,
|
country,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}) => ({
|
}) => ({
|
||||||
name,
|
name,
|
||||||
shortName,
|
shortName,
|
||||||
@@ -176,7 +184,8 @@ class SystemForm extends Component {
|
|||||||
state,
|
state,
|
||||||
county,
|
county,
|
||||||
country,
|
country,
|
||||||
showScreenName
|
showScreenName,
|
||||||
|
ignoreUnknownTalkgroup
|
||||||
}))(this.state);
|
}))(this.state);
|
||||||
this.props.onSubmit(system);
|
this.props.onSubmit(system);
|
||||||
}
|
}
|
||||||
@@ -294,13 +303,16 @@ class SystemForm extends Component {
|
|||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Divider horizontal />
|
<Divider horizontal />
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
|
|
||||||
<p>Your Screen Name: {this.props.screenName}</p>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
onChange={this.handleCheckboxChange}
|
onChange={this.handleScreenNameCheckboxChange}
|
||||||
checked={this.state.showScreenName}
|
checked={this.state.showScreenName}
|
||||||
label="Show in System Listing" />
|
label="Display Screen Name in System Listing" />
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Checkbox
|
||||||
|
onChange={this.handleTalkgroupCheckboxChange}
|
||||||
|
checked={this.state.ignoreUnknownTalkgroup}
|
||||||
|
label="Ignore Unknown Talkgroups" />
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
<Divider horizontal />
|
<Divider horizontal />
|
||||||
<Form.TextArea
|
<Form.TextArea
|
||||||
@@ -386,19 +398,19 @@ class SystemForm extends Component {
|
|||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
) : (
|
) : (
|
||||||
<Form.Field>
|
<Form.Field>
|
||||||
<Form.Dropdown
|
<Form.Dropdown
|
||||||
name="state"
|
name="state"
|
||||||
onChange={this.handleInputChange}
|
onChange={this.handleInputChange}
|
||||||
error={this.state.stateError}
|
error={this.state.stateError}
|
||||||
value={this.state.state}
|
value={this.state.state}
|
||||||
options={stateOptions}
|
options={stateOptions}
|
||||||
scrolling
|
scrolling
|
||||||
label="State"
|
label="State"
|
||||||
placeholder="Select State..."
|
placeholder="Select State..."
|
||||||
/>
|
/>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ const system = (
|
|||||||
key: action.data.key,
|
key: action.data.key,
|
||||||
userId: action.data.userId,
|
userId: action.data.userId,
|
||||||
id: action.data.id,
|
id: action.data.id,
|
||||||
showScreenName: action.data.showScreenName
|
showScreenName: action.data.showScreenName,
|
||||||
|
ignoreUnknownTalkgroup : action.data.ignoreUnknownTalkgroup
|
||||||
});
|
});
|
||||||
return Object.assign({}, state, { isWaiting: false,
|
return Object.assign({}, state, { isWaiting: false,
|
||||||
items: newItems});
|
items: newItems});
|
||||||
|
|||||||
@@ -64,9 +64,11 @@ process.nextTick(function() {
|
|||||||
var shortName = req.params.shortName.toLowerCase();
|
var shortName = req.params.shortName.toLowerCase();
|
||||||
var apiKey = req.body.api_key;
|
var apiKey = req.body.api_key;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
System.findOne({
|
System.findOne({
|
||||||
'shortName': shortName
|
'shortName': shortName
|
||||||
}, async function(err, item) {
|
},["key", "ignoreUnknownTalkgroup"], async function(err, item) {
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err);
|
console.warn("[" + req.params.shortName + "] Error /:shortName/upload - Error: " + err);
|
||||||
@@ -98,6 +100,29 @@ process.nextTick(function() {
|
|||||||
var emergency = parseInt(req.body.emergency);
|
var emergency = parseInt(req.body.emergency);
|
||||||
|
|
||||||
|
|
||||||
|
if (item.ignoreUnknownTalkgroup == true) {
|
||||||
|
//console.log("ignore unknown talkgroup ");
|
||||||
|
//await Talkgroup.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc => !!doc)
|
||||||
|
talkgroupExists = await Talkgroup.exists({
|
||||||
|
'shortName': shortName,
|
||||||
|
'num': talkgroupNum
|
||||||
|
});
|
||||||
|
//console.log("In talkgroup: " + talkgroupExists);
|
||||||
|
//talkgroupExists = await Talkgroup.findOne({ 'shortName': shortName, 'num': talkgroupNum }).select({ _id: 1 }).lean().then(doc => !!doc).exec();
|
||||||
|
if (!talkgroupExists) {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(req.file.path)
|
||||||
|
//file removed
|
||||||
|
} catch(err) {
|
||||||
|
console.log("[" + call.shortName + "] error deleting: " + req.file.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500);
|
||||||
|
res.send("Talkgroup does not exist, skipping.\n");
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
// Add in an API Key check
|
// Add in an API Key check
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -107,7 +132,6 @@ process.nextTick(function() {
|
|||||||
// Mongoose does not allow 'errors' to be used in the Schema for call. need to rename to error.
|
// Mongoose does not allow 'errors' to be used in the Schema for call. need to rename to error.
|
||||||
var badFreqList = JSON.parse(req.body.freq_list);
|
var badFreqList = JSON.parse(req.body.freq_list);
|
||||||
var freqList = badFreqList.map(obj =>{
|
var freqList = badFreqList.map(obj =>{
|
||||||
|
|
||||||
obj.error = obj.errors;
|
obj.error = obj.errors;
|
||||||
delete obj.errors;
|
delete obj.errors;
|
||||||
return obj;
|
return obj;
|
||||||
|
|||||||
426
backend/index.js
426
backend/index.js
@@ -32,6 +32,7 @@ const server = require('http').createServer(app);
|
|||||||
|
|
||||||
const io = require('socket.io')(server, {});
|
const io = require('socket.io')(server, {});
|
||||||
|
|
||||||
|
var Twitter = require('twitter-lite');
|
||||||
|
|
||||||
|
|
||||||
io.origins('*:*');
|
io.origins('*:*');
|
||||||
@@ -43,50 +44,71 @@ io.origins((origin, callback) => {
|
|||||||
callback(null, true);
|
callback(null, true);
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
io.on('connection', function(socket){
|
io.on('connection', function (socket) {
|
||||||
//console.log('a user connected');
|
//console.log('a user connected');
|
||||||
socket.on('disconnect', function(){
|
socket.on('disconnect', function () {
|
||||||
//console.log('user disconnected');
|
//console.log('user disconnected');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// This section of code enable a Twitter client to tweet out messages when a new call comes in
|
||||||
|
|
||||||
|
/*
|
||||||
|
const client = new Twitter({
|
||||||
|
subdomain: "api", // "api" is the default (change for other subdomains)
|
||||||
|
version: "1.1", // version "1.1" is the default (change for other subdomains)
|
||||||
|
consumer_key: process.env.TWITTER_CONSUMER_KEY, // from Twitter.
|
||||||
|
consumer_secret: process.env.TWITTER_CONSUMER_SECRET, // from Twitter.
|
||||||
|
access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY, // from your User (oauth_token)
|
||||||
|
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET // from your User (oauth_token_secret)
|
||||||
|
});
|
||||||
|
|
||||||
|
client
|
||||||
|
.get("account/verify_credentials")
|
||||||
|
.then(results => {
|
||||||
|
console.log("Connected to Twitter");
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
*/
|
||||||
|
|
||||||
function getRole(req) {
|
function getRole(req) {
|
||||||
if (req.params.shortName && req.user) {
|
if (req.params.shortName && req.user) {
|
||||||
var short_name = req.params.shortName.toLowerCase();
|
var short_name = req.params.shortName.toLowerCase();
|
||||||
var userId = req.user._id;
|
var userId = req.user._id;
|
||||||
Permission.findOne({'userId': userId, 'shortName': short_name}, function(err, permission) {
|
Permission.findOne({ 'userId': userId, 'shortName': short_name }, function (err, permission) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error - getRole userId: ' + userId + ' shortName: ' + short_name + ' error: ' + err);
|
console.error('Error - getRole userId: ' + userId + ' shortName: ' + short_name + ' error: ' + err);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
|
console.log('Found - getRole userId: ' + userId + ' shortName: ' + short_name + ' role: ' + permission.role);
|
||||||
|
|
||||||
return permission.role;
|
return permission.role;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUser(req, res, next) {
|
function isUser(req, res, next) {
|
||||||
if (req.params.shortName) {
|
if (req.params.shortName) {
|
||||||
var short_name = req.params.shortName.toLowerCase();
|
var short_name = req.params.shortName.toLowerCase();
|
||||||
System.findOne({'shortName': short_name}, function(err, system) {
|
System.findOne({ 'shortName': short_name }, function (err, system) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error - System not found: ' + short_name);
|
console.error('Error - System not found: ' + short_name);
|
||||||
res.status(505).send({
|
res.status(505).send({
|
||||||
|
success: false,
|
||||||
|
message: "System Not Found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!system.private) return next();
|
||||||
|
|
||||||
|
// Looks like the system is private...
|
||||||
|
|
||||||
|
if (getRole(req) > 0) return next();
|
||||||
|
res.status(401).send({
|
||||||
success: false,
|
success: false,
|
||||||
message: "System Not Found"
|
message: "Insufficent Permission."
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if (!system.private) return next();
|
|
||||||
|
|
||||||
// Looks like the system is private...
|
|
||||||
|
|
||||||
if (getRole(req) > 0) return next();
|
|
||||||
res.status(401).send({
|
|
||||||
success: false,
|
|
||||||
message: "Insufficent Permission."
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +122,13 @@ configureExpress(app, passport)
|
|||||||
|
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
mongoose.connect(secrets.db, (err, res) => {
|
mongoose.connect(secrets.db, (err, res) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(`Error connecting to ${secrets.db}. ${err}`)
|
console.log(`Error connecting to ${secrets.db}. ${err}`)
|
||||||
} else {
|
} else {
|
||||||
console.log(`Successfully connected to ${secrets.db}.`)
|
console.log(`Successfully connected to ${secrets.db}.`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
connect()
|
connect()
|
||||||
|
|
||||||
@@ -115,7 +137,7 @@ mongoose.connection.on("disconnected", connect)
|
|||||||
|
|
||||||
|
|
||||||
var upload = multer({
|
var upload = multer({
|
||||||
dest: config.uploadDirectory
|
dest: config.uploadDirectory
|
||||||
});
|
});
|
||||||
|
|
||||||
var clients = [];
|
var clients = [];
|
||||||
@@ -123,7 +145,7 @@ var clients = [];
|
|||||||
function addSystemClients(req, res, next) {
|
function addSystemClients(req, res, next) {
|
||||||
var systemClients = {};
|
var systemClients = {};
|
||||||
for (var key in clients) {
|
for (var key in clients) {
|
||||||
if (clients.hasOwnProperty(key)) {
|
if (clients.hasOwnProperty(key)) {
|
||||||
const client = clients[key];
|
const client = clients[key];
|
||||||
const shortName = client.shortName;
|
const shortName = client.shortName;
|
||||||
if (client.active) {
|
if (client.active) {
|
||||||
@@ -150,14 +172,27 @@ app.get('/:shortName/calls/newer', calls.get_newer_calls);
|
|||||||
app.get('/:shortName/calls/older', calls.get_older_calls);
|
app.get('/:shortName/calls/older', calls.get_older_calls);
|
||||||
app.get('/:shortName/calls/:time/older', calls.get_iphone_calls);
|
app.get('/:shortName/calls/:time/older', calls.get_iphone_calls);
|
||||||
app.get('/:shortName/calls', calls.get_calls);
|
app.get('/:shortName/calls', calls.get_calls);
|
||||||
app.post('/:shortName/star/:id', calls.add_star, function(req,res) {
|
app.post('/:shortName/star/:id', calls.add_star);
|
||||||
notify_clients(req.call);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/
|
/*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/
|
||||||
app.post('/:shortName/upload', upload.single('call'), uploads.upload, function(req,res) {
|
app.post('/:shortName/upload', upload.single('call'), uploads.upload, async function (req, res) {
|
||||||
notify_clients(req.call);
|
notify_clients(req.call);
|
||||||
|
|
||||||
|
// This section is for sending out tweets when new calls come in
|
||||||
|
|
||||||
|
/*
|
||||||
|
if ((req.call.shortName == "dcfd") && (req.call.talkgroupNum == 101)) {
|
||||||
|
var call_time = new Date(req.call.time);
|
||||||
|
var call_link = "https://openmhz.com/system/dcfd?call-id=" + req.call._id + "&time=" + call_time.getTime();
|
||||||
|
|
||||||
|
var call_time_label = call_time.toLocaleDateString('en-US', { timeZone: "America/New_York" }) + " " + call_time.toLocaleTimeString('en-US', { timeZone: "America/New_York" })
|
||||||
|
var status = req.call.len + " sec transmission\n" + call_time_label + "\n" + call_link;
|
||||||
|
const response = await client.post('statuses/update', {
|
||||||
|
status: status,
|
||||||
|
});
|
||||||
|
//console.log("Tweet Response: " + response);
|
||||||
|
}*/
|
||||||
});
|
});
|
||||||
|
|
||||||
/*------ SYSTEMS ----------*/
|
/*------ SYSTEMS ----------*/
|
||||||
@@ -171,41 +206,41 @@ app.get('/:shortName/groups', groups.get_groups);
|
|||||||
|
|
||||||
|
|
||||||
/*------ STATS ----------*/
|
/*------ STATS ----------*/
|
||||||
app.get('/:shortName/errors',stats.get_errors);
|
app.get('/:shortName/errors', stats.get_errors);
|
||||||
app.get('/:shortName/stats', stats.get_stats);
|
app.get('/:shortName/stats', stats.get_stats);
|
||||||
|
|
||||||
|
|
||||||
function get_clients(req, res) {
|
function get_clients(req, res) {
|
||||||
if (req.params.shortName) {
|
if (req.params.shortName) {
|
||||||
var short_name = req.params.shortName.toLowerCase();
|
var short_name = req.params.shortName.toLowerCase();
|
||||||
} else {
|
} else {
|
||||||
var short_name = null;
|
var short_name = null;
|
||||||
}
|
}
|
||||||
var total = 0;
|
var total = 0;
|
||||||
var response = [];
|
var response = [];
|
||||||
for (var key in clients) {
|
for (var key in clients) {
|
||||||
if (clients.hasOwnProperty(key)) {
|
if (clients.hasOwnProperty(key)) {
|
||||||
total++;
|
total++;
|
||||||
if (!short_name || (clients[key].shortName == short_name)) {
|
if (!short_name || (clients[key].shortName == short_name)) {
|
||||||
var age = (Date.now() - clients[key].timestamp) / 1000;
|
var age = (Date.now() - clients[key].timestamp) / 1000;
|
||||||
var obj = {
|
var obj = {
|
||||||
shortName: clients[key].shortName,
|
shortName: clients[key].shortName,
|
||||||
filterCode: clients[key].filterCode,
|
filterCode: clients[key].filterCode,
|
||||||
filterName: clients[key].filterName,
|
filterName: clients[key].filterName,
|
||||||
filterType: clients[key].filterType,
|
filterType: clients[key].filterType,
|
||||||
filterStarred: clients[key].filterStarred,
|
filterStarred: clients[key].filterStarred,
|
||||||
active: clients[key].active,
|
active: clients[key].active,
|
||||||
talkgroupNums: clients[key].talkgroupNums,
|
talkgroupNums: clients[key].talkgroupNums,
|
||||||
timestamp: age
|
timestamp: age
|
||||||
}
|
|
||||||
response.push(obj);
|
|
||||||
}
|
}
|
||||||
|
response.push(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.push({"total": total});
|
}
|
||||||
|
response.push({ "total": total });
|
||||||
|
|
||||||
res.contentType('json');
|
res.contentType('json');
|
||||||
res.send(JSON.stringify(response));
|
res.send(JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*------ CLIENTS ----------*/
|
/*------ CLIENTS ----------*/
|
||||||
@@ -214,75 +249,73 @@ app.get('/:shortName/clients', get_clients);
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.use(function(err, req, res, next) {
|
app.use(function (err, req, res, next) {
|
||||||
console.error("Caught an error");
|
console.error("Caught an error");
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
/*
|
/*
|
||||||
res.status(err.status || 500);
|
res.status(err.status || 500);
|
||||||
res.contentType('json');
|
res.contentType('json');
|
||||||
res.send(JSON.stringify({
|
res.send(JSON.stringify({
|
||||||
message: err.message,
|
message: err.message,
|
||||||
error: err
|
error: err
|
||||||
}));*/
|
}));*/
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.get('/:shortName/clients', get_clients);
|
app.get('/:shortName/clients', get_clients);
|
||||||
app.get('/clients', get_clients);
|
app.get('/clients', get_clients);
|
||||||
|
|
||||||
function notify_clients(call) {
|
function notify_clients(call) {
|
||||||
call.type = "calls";
|
call.type = "calls";
|
||||||
var sent = 0;
|
var sent = 0;
|
||||||
|
|
||||||
for (var key in clients) {
|
for (var key in clients) {
|
||||||
if (clients.hasOwnProperty(key)) {
|
if (clients.hasOwnProperty(key)) {
|
||||||
var client = clients[key];
|
var client = clients[key];
|
||||||
if (client.active) {
|
if (client.active) {
|
||||||
if (client.shortName == call.shortName.toLowerCase()) {
|
if (client.shortName == call.shortName.toLowerCase()) {
|
||||||
// if client is not filtering for stars, or if the client is filtering and the call has stars
|
// if client is not filtering for stars, or if the client is filtering and the call has stars
|
||||||
if (!client.filterStarred || call.star) {
|
if (!client.filterStarred || call.star) {
|
||||||
if (client.filterCode == "") {
|
if (client.filterCode == "") {
|
||||||
sent++;
|
sent++;
|
||||||
|
client.socket.emit("new message", JSON.stringify(call));
|
||||||
|
} else if (client.filterType == "unit") {
|
||||||
|
var codeArray = client.filterCode.split(',');
|
||||||
|
var success = false;
|
||||||
|
for (var j = 0; j < codeArray.length; ++j) {
|
||||||
|
for (var k = 0; k < call.srcList.length; k++) {
|
||||||
|
if (codeArray[j] == call.srcList[k]) {
|
||||||
|
sent++;
|
||||||
|
client.socket.emit("new message", JSON.stringify(call));
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
var codeArray = client.talkgroupNums;
|
||||||
|
for (var j = 0; j < codeArray.length; ++j) {
|
||||||
|
if (codeArray[j] == call.talkgroupNum) {
|
||||||
client.socket.emit("new message", JSON.stringify(call));
|
client.socket.emit("new message", JSON.stringify(call));
|
||||||
} else if (client.filterType == "unit") {
|
sent++
|
||||||
var codeArray = client.filterCode.split(',');
|
break;
|
||||||
var success = false;
|
}
|
||||||
for (var j = 0; j < codeArray.length; ++j) {
|
|
||||||
for (var k = 0; k < call.srcList.length; k++) {
|
|
||||||
if (codeArray[j] == call.srcList[k]) {
|
|
||||||
sent++;
|
|
||||||
client.socket.emit("new message", JSON.stringify(call));
|
|
||||||
success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var codeArray = client.talkgroupNums;
|
|
||||||
for (var j = 0; j < codeArray.length; ++j) {
|
|
||||||
if (codeArray[j] == call.talkgroupNum) {
|
|
||||||
client.socket.emit("new message", JSON.stringify(call));
|
|
||||||
sent++
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sent > 0) {
|
||||||
if (sent > 0) {
|
//console.log("[" + call.shortName.toLowerCase() + "] Sent call to " + sent + " clients");
|
||||||
//console.log("[" + call.shortName.toLowerCase() + "] Sent call to " + sent + " clients");
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -292,93 +325,90 @@ function notify_clients(call) {
|
|||||||
|
|
||||||
|
|
||||||
io.sockets.on('connection', function (client) {
|
io.sockets.on('connection', function (client) {
|
||||||
clients[client.id] = {socket: client, active: false};
|
clients[client.id] = { socket: client, active: false };
|
||||||
clients[client.id].timestamp = new Date();
|
clients[client.id].timestamp = new Date();
|
||||||
client.on('start', function (data) {
|
client.on('start', function (data) {
|
||||||
if (clients[client.id] && data.shortName) {
|
if (clients[client.id] && data.shortName) {
|
||||||
clients[client.id].active = true;
|
clients[client.id].active = true;
|
||||||
clients[client.id].shortName = data.shortName.toLowerCase();
|
clients[client.id].shortName = data.shortName.toLowerCase();
|
||||||
clients[client.id].filterCode = String(data.filterCode);
|
clients[client.id].filterCode = String(data.filterCode);
|
||||||
clients[client.id].filterName = data.filterName;
|
clients[client.id].filterName = data.filterName;
|
||||||
clients[client.id].filterStarred = data.filterStarred;
|
clients[client.id].filterStarred = data.filterStarred;
|
||||||
clients[client.id].filterType = String(data.filterType);
|
clients[client.id].filterType = String(data.filterType);
|
||||||
clients[client.id].talkgroupNums = [];
|
clients[client.id].talkgroupNums = [];
|
||||||
clients[client.id].timestamp = new Date();
|
clients[client.id].timestamp = new Date();
|
||||||
if ((data.filterType == "group") && (data.filterCode.indexOf(',') == -1)) {
|
if ((data.filterType == "group") && (data.filterCode.indexOf(',') == -1)) {
|
||||||
db.get().collection("groups", function(err, groupCollection) {
|
db.get().collection("groups", function (err, groupCollection) {
|
||||||
if (err || !groupCollection) {
|
if (err || !groupCollection) {
|
||||||
console.error("Error - unable to open groups collection: " + err);
|
console.error("Error - unable to open groups collection: " + err);
|
||||||
delete clients[client.id];
|
delete clients[client.id];
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (!ObjectID.isValid(data.filterCode)) {
|
|
||||||
console.error("Error - Socket - Invalid Group ID: " + data.filterCode);
|
|
||||||
delete clients[client.id];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
groupCollection.findOne({
|
|
||||||
'shortName': data.shortName.toLowerCase(),
|
|
||||||
'_id': ObjectID.createFromHexString(data.filterCode)
|
|
||||||
}, function(err, group) {
|
|
||||||
if (err) {
|
|
||||||
console.warn("[" + data.shortName.toLowerCase() + "] Error - WebSocket Group ID not Found! Error: " + err + " Group ID: " + data.filterCode);
|
|
||||||
delete clients[client.id];
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (group && clients[client.id]) {
|
|
||||||
|
|
||||||
clients[client.id].talkgroupNums = group.talkgroups;
|
|
||||||
} else {
|
|
||||||
console.error("Error - Socket: Invalid group or Client: " + data.filterCode + " Shortname: " + data.shortName + " ClientID: " + client.id);
|
|
||||||
delete clients[client.id];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} if ((data.filterType == "talkgroup") && Array.isArray(data.filterCode)) {
|
|
||||||
|
|
||||||
clients[client.id].talkgroupNums = data.filterCode;
|
|
||||||
}
|
|
||||||
//console.log("[" + data.shortName.toLowerCase() + "] WebSocket Updating - Client: " + client.id + " code set to: " + data.filterCode + " type set to: " + data.filterType + " TGS: " + clients[client.id].talkgroupNums);
|
|
||||||
} else{
|
|
||||||
console.error("Error - Socket.io [Start] either client not found or no Short Name");
|
|
||||||
}
|
}
|
||||||
});
|
if (!ObjectID.isValid(data.filterCode)) {
|
||||||
client.on('stop', function (data) {
|
console.error("Error - Socket - Invalid Group ID: " + data.filterCode);
|
||||||
if (clients[client.id] ) {
|
delete clients[client.id];
|
||||||
clients[client.id].active = false;
|
return;
|
||||||
} else{
|
}
|
||||||
console.error("Error - Socket.io [Stop] either client not found ");
|
groupCollection.findOne({
|
||||||
|
'shortName': data.shortName.toLowerCase(),
|
||||||
|
'_id': ObjectID.createFromHexString(data.filterCode)
|
||||||
|
}, function (err, group) {
|
||||||
|
if (err) {
|
||||||
|
console.warn("[" + data.shortName.toLowerCase() + "] Error - WebSocket Group ID not Found! Error: " + err + " Group ID: " + data.filterCode);
|
||||||
|
delete clients[client.id];
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (group && clients[client.id]) {
|
||||||
|
clients[client.id].talkgroupNums = group.talkgroups;
|
||||||
|
} else {
|
||||||
|
console.error("Error - Socket: Invalid group or Client: " + data.filterCode + " Shortname: " + data.shortName + " ClientID: " + client.id);
|
||||||
|
delete clients[client.id];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} if ((data.filterType == "talkgroup") && Array.isArray(data.filterCode)) {
|
||||||
|
clients[client.id].talkgroupNums = data.filterCode;
|
||||||
}
|
}
|
||||||
});
|
//console.log("[" + data.shortName.toLowerCase() + "] WebSocket Updating - Client: " + client.id + " code set to: " + data.filterCode + " type set to: " + data.filterType + " TGS: " + clients[client.id].talkgroupNums);
|
||||||
client.on('disconnect', function() {
|
} else {
|
||||||
delete clients[client.id];
|
console.error("Error - Socket.io [Start] either client not found or no Short Name");
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
client.on('stop', function (data) {
|
||||||
|
if (clients[client.id]) {
|
||||||
|
clients[client.id].active = false;
|
||||||
|
} else {
|
||||||
|
console.error("Error - Socket.io [Stop] either client not found ");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.on('disconnect', function () {
|
||||||
|
delete clients[client.id];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//console.log("stats: " + util.inspect(call_stats));
|
//console.log("stats: " + util.inspect(call_stats));
|
||||||
// Connect to Mongo on start
|
// Connect to Mongo on start
|
||||||
db.connect(function(err) {
|
db.connect(function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Unable to connect to Mongo.')
|
console.log('Unable to connect to Mongo.')
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
} else {
|
} else {
|
||||||
stats.init_stats();
|
stats.init_stats();
|
||||||
|
|
||||||
server.listen(app.get("port"), (err) => {
|
server.listen(app.get("port"), (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.err(err.stack)
|
console.err(err.stack)
|
||||||
} else {
|
} else {
|
||||||
console.log(`App listening on port ${app.get("port")} [${process.env.NODE_ENV} mode]`)
|
console.log(`App listening on port ${app.get("port")} [${process.env.NODE_ENV} mode]`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ var systemSchema = mongoose.Schema({
|
|||||||
showScreenName: Boolean,
|
showScreenName: Boolean,
|
||||||
callAvg: Number,
|
callAvg: Number,
|
||||||
callCount: Number,
|
callCount: Number,
|
||||||
|
ignoreUnknownTalkgroup : Boolean,
|
||||||
active: {type: Boolean, default: false},
|
active: {type: Boolean, default: false},
|
||||||
planType: {type: Number, default: 0},
|
planType: {type: Number, default: 0},
|
||||||
userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
|
userId: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var bcrypt = require('bcrypt-nodejs');
|
|
||||||
|
|
||||||
var talkgroupSchema = mongoose.Schema({
|
var talkgroupSchema = mongoose.Schema({
|
||||||
shortName: String,
|
shortName: String,
|
||||||
|
|||||||
2313
backend/package-lock.json
generated
2313
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
|||||||
"get-folder-size": "^1.0.0",
|
"get-folder-size": "^1.0.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mongodb": "2.2.36",
|
"mongodb": "2.2.36",
|
||||||
"mongoose": "5.3.12",
|
"mongoose": "5.12.3",
|
||||||
"multer": "1.3.0",
|
"multer": "1.3.0",
|
||||||
"node-ffprobe": "github:ListenerApproved/node-ffprobe",
|
"node-ffprobe": "github:ListenerApproved/node-ffprobe",
|
||||||
"node-schedule": "1.2.4",
|
"node-schedule": "1.2.4",
|
||||||
|
|||||||
1386
backend/yarn.lock
1386
backend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,10 @@ services:
|
|||||||
- S3_ENDPOINT
|
- S3_ENDPOINT
|
||||||
- S3_BUCKET
|
- S3_BUCKET
|
||||||
- S3_PROFILE
|
- S3_PROFILE
|
||||||
|
- TWITTER_CONSUMER_KEY
|
||||||
|
- TWITTER_CONSUMER_SECRET
|
||||||
|
- TWITTER_ACCESS_TOKEN_KEY
|
||||||
|
- TWITTER_ACCESS_TOKEN_SECRET
|
||||||
volumes:
|
volumes:
|
||||||
- "~/.aws:/home/app/.aws"
|
- "~/.aws:/home/app/.aws"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -153,6 +157,7 @@ services:
|
|||||||
- REACT_APP_BACKEND_SERVER
|
- REACT_APP_BACKEND_SERVER
|
||||||
- REACT_APP_FRONTEND_SERVER
|
- REACT_APP_FRONTEND_SERVER
|
||||||
- REACT_APP_SITE_NAME
|
- REACT_APP_SITE_NAME
|
||||||
|
- REACT_APP_GOOGLE_ANALYTICS
|
||||||
- NODE_ENV
|
- NODE_ENV
|
||||||
image: "frontend:${TAG}-${STAGE}"
|
image: "frontend:${TAG}-${STAGE}"
|
||||||
environment:
|
environment:
|
||||||
@@ -160,6 +165,7 @@ services:
|
|||||||
- REACT_APP_BACKEND_SERVER
|
- REACT_APP_BACKEND_SERVER
|
||||||
- REACT_APP_FRONTEND_SERVER
|
- REACT_APP_FRONTEND_SERVER
|
||||||
- REACT_APP_SITE_NAME
|
- REACT_APP_SITE_NAME
|
||||||
|
- REACT_APP_GOOGLE_ANALYTICS
|
||||||
depends_on:
|
depends_on:
|
||||||
- mongo
|
- mongo
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ ENV PATH /app/node_modules/.bin:$PATH
|
|||||||
ARG REACT_APP_BACKEND_SERVER
|
ARG REACT_APP_BACKEND_SERVER
|
||||||
ARG REACT_APP_SITE_NAME
|
ARG REACT_APP_SITE_NAME
|
||||||
ARG REACT_APP_FRONTEND_SERVER
|
ARG REACT_APP_FRONTEND_SERVER
|
||||||
|
ARG REACT_APP_GOOGLE_ANALYTICS
|
||||||
ARG NODE_ENV
|
ARG NODE_ENV
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|||||||
@@ -3,27 +3,27 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@artsy/fresnel": "1.3.1",
|
"@artsy/fresnel": "6.1.0",
|
||||||
"@reduxjs/toolkit": "^1.1.0",
|
"@reduxjs/toolkit": "^1.9.1",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"axios": "0.21.1",
|
"axios": "1.2.1",
|
||||||
"connect-mongo": "3.2.0",
|
"connected-react-router": "^6.9.3",
|
||||||
"connected-react-router": "6.8.0",
|
"date-fns": "2.29.3",
|
||||||
"date-fns": "2.16.1",
|
"ejs": "3.1.8",
|
||||||
"ejs": "3.1.6",
|
"history": "^5.3.0",
|
||||||
"mongoose": "5.11.17",
|
"react": "^18.2.0",
|
||||||
"react": "^17.0.1",
|
"react-audio-player": "0.17.0",
|
||||||
"react-audio-player": "0.14.0",
|
"react-datepicker": "4.8.0",
|
||||||
"react-datepicker": "3.3.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dom": "^17.0.1",
|
"react-ga": "^3.3.1",
|
||||||
"react-redux": "^7.1.3",
|
"react-redux": "^8.0.5",
|
||||||
"react-router-dom": "5.2.0",
|
"react-router-dom": "6.6.1",
|
||||||
"react-scripts": "4.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.4.2",
|
||||||
"semantic-ui-css": "2.4.1",
|
"semantic-ui-css": "2.5.0",
|
||||||
"semantic-ui-react": "2.0.1"
|
"semantic-ui-react": "2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ function getCard(req, res, next) {
|
|||||||
function (err, item) {
|
function (err, item) {
|
||||||
if (item) {
|
if (item) {
|
||||||
var time = new Date(item.time);
|
var time = new Date(item.time);
|
||||||
var timeString = time.toLocaleTimeString("en-US");
|
var timeString = time.toLocaleTimeString('en-US', {timeZone: "America/New_York"});
|
||||||
var dateString = time.toDateString();
|
var dateString = time.toLocaleDateString('en-US', {timeZone: "America/New_York"});
|
||||||
//console.log(item)
|
//console.log(item)
|
||||||
res.render('card.ejs', {
|
res.render('card.ejs', {
|
||||||
item: item,
|
item: item,
|
||||||
@@ -135,15 +135,15 @@ async function getCalls(req, res, next) {
|
|||||||
async function (err, item) {
|
async function (err, item) {
|
||||||
if (item) {
|
if (item) {
|
||||||
var time = new Date(item.time);
|
var time = new Date(item.time);
|
||||||
var timeString = time.toLocaleTimeString("en-US");
|
var timeString = time.toLocaleTimeString('en-US', {timeZone: "America/New_York"});
|
||||||
var dateString = time.toDateString();
|
var dateString = time.toDateString('en-US', {timeZone: "America/New_York"});
|
||||||
//console.log(item)
|
//console.log(item)
|
||||||
const tg_coll = db.get().collection('talkgroups');
|
const tg_coll = db.get().collection('talkgroups');
|
||||||
const tg = await tg_coll.findOne({ "num": item.talkgroupNum, 'shortName': req.params.shortName.toLowerCase()})
|
const tg = await tg_coll.findOne({ "num": item.talkgroupNum, 'shortName': req.params.shortName.toLowerCase()})
|
||||||
var title = item.len + " second transmission"
|
var title = item.len + " second transmission"
|
||||||
|
|
||||||
if (tg) {
|
if (tg) {
|
||||||
console.log(tg)
|
//console.log(tg)
|
||||||
title = tg.description;
|
title = tg.description;
|
||||||
}
|
}
|
||||||
const callId = req.query["call-id"];
|
const callId = req.query["call-id"];
|
||||||
|
|||||||
47
frontend/src/About/AboutComponent.js
Normal file
47
frontend/src/About/AboutComponent.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react";
|
||||||
|
|
||||||
|
|
||||||
|
class AboutComponent extends Component {
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Menu fixed="top">
|
||||||
|
<Link to="/"><Menu.Item link><Icon name='arrow left' /> Home</Menu.Item></Link>
|
||||||
|
<Link to="/systems"><Menu.Item link>Listen</Menu.Item></Link>
|
||||||
|
</Menu>
|
||||||
|
<Container text>
|
||||||
|
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h1">About<Icon name='compass' /></Header></Divider>
|
||||||
|
|
||||||
|
<p>I wrote <a href="https://github.com/robotastic/trunk-recorder">Trunk Recorder</a> because I was curious
|
||||||
|
about what my local fire station was up to and I put together the original version of OpenMHz because I
|
||||||
|
figured other people might want to listen to the recordings too.</p>
|
||||||
|
<p>The latest version of this site makes it easy for other people running Trunk Recorder to share their recrodings.
|
||||||
|
I am hoping that making it easier to listen to what our local fire, police and EMS have to go through everyday will
|
||||||
|
lead to greater appreciation for all the work they do that goes largely unseen. I am personally amazed by the number
|
||||||
|
of medical runs the DC's Fire and EMS department has to cover every day, and the shocking number of them that are ODs.</p>
|
||||||
|
|
||||||
|
<p>Each system should have a 30 day archive. This is to help make sure things don't get overloaded and make sure I don't get a crazy bill
|
||||||
|
at the end of the month.</p>
|
||||||
|
<p> - Luke <a href="mailto:luke@robotastic.com?Subject=OpenMHz" target="_top">luke@robotastic.com</a> </p>
|
||||||
|
|
||||||
|
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Open Source</Header></Divider>
|
||||||
|
<p>Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:</p>
|
||||||
|
<p><a href="https://github.com/openmhz/trunk-server"><Icon name='github' />Trunk Server</a></p>
|
||||||
|
<Divider horizontal style={{ paddingTop: "4em", paddingBottom: "2em" }}><Header as="h2">Support</Header></Divider>
|
||||||
|
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
|
||||||
|
|
||||||
|
<p><a href="https://github.com/sponsors/robotastic"><Button>
|
||||||
|
Support
|
||||||
|
</Button></a></p>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutComponent;
|
||||||
@@ -40,10 +40,8 @@ class CallPlayer extends React.Component {
|
|||||||
this.addCall = this.addCall.bind(this);
|
this.addCall = this.addCall.bind(this);
|
||||||
this.handlePusherClick = this.handlePusherClick.bind(this);
|
this.handlePusherClick = this.handlePusherClick.bind(this);
|
||||||
this.handleSidebarToggle = this.handleSidebarToggle.bind(this);
|
this.handleSidebarToggle = this.handleSidebarToggle.bind(this);
|
||||||
this.handleSupportToggle = this.handleSupportToggle.bind(this);
|
|
||||||
this.handleFilterClose = this.handleFilterClose.bind(this);
|
this.handleFilterClose = this.handleFilterClose.bind(this);
|
||||||
this.handleGroupClose = this.handleGroupClose.bind(this);
|
this.handleGroupClose = this.handleGroupClose.bind(this);
|
||||||
this.handleSupportClose = this.handleSupportClose.bind(this);
|
|
||||||
this.handleCalendarToggle = this.handleCalendarToggle.bind(this);
|
this.handleCalendarToggle = this.handleCalendarToggle.bind(this);
|
||||||
this.handleCalendarClose = this.handleCalendarClose.bind(this);
|
this.handleCalendarClose = this.handleCalendarClose.bind(this);
|
||||||
this.handleLiveToggle = this.handleLiveToggle.bind(this);
|
this.handleLiveToggle = this.handleLiveToggle.bind(this);
|
||||||
@@ -61,7 +59,6 @@ class CallPlayer extends React.Component {
|
|||||||
autoPlay: true,
|
autoPlay: true,
|
||||||
callId: false,
|
callId: false,
|
||||||
playTime: 0,
|
playTime: 0,
|
||||||
sourceIndex: 0,
|
|
||||||
callScroll: false,
|
callScroll: false,
|
||||||
callSelect: false,
|
callSelect: false,
|
||||||
urlOptions: false,
|
urlOptions: false,
|
||||||
@@ -69,8 +66,7 @@ class CallPlayer extends React.Component {
|
|||||||
sidebarOpened: false,
|
sidebarOpened: false,
|
||||||
filterVisible: false,
|
filterVisible: false,
|
||||||
groupVisible: false,
|
groupVisible: false,
|
||||||
calendarVisible: false,
|
calendarVisible: false
|
||||||
supportVisible: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +88,6 @@ class CallPlayer extends React.Component {
|
|||||||
filterVisible: !this.state.filterVisible
|
filterVisible: !this.state.filterVisible
|
||||||
})
|
})
|
||||||
|
|
||||||
handleSupportToggle = () => this.setState({
|
|
||||||
supportVisible: !this.state.supportVisible
|
|
||||||
})
|
|
||||||
|
|
||||||
handleContextRef = contextRef => this.setState({ contextRef })
|
handleContextRef = contextRef => this.setState({ contextRef })
|
||||||
changeUrl = url => this.props.callActions.changeUrl(url)
|
changeUrl = url => this.props.callActions.changeUrl(url)
|
||||||
|
|
||||||
@@ -188,6 +180,9 @@ class CallPlayer extends React.Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
if (state.callId) {
|
if (state.callId) {
|
||||||
|
if (search.length !== 1) {
|
||||||
|
search = search + '&';
|
||||||
|
}
|
||||||
search = search + `call-id=${state.callId}`
|
search = search + `call-id=${state.callId}`
|
||||||
}
|
}
|
||||||
if (props.filterDate) {
|
if (props.filterDate) {
|
||||||
@@ -232,12 +227,6 @@ class CallPlayer extends React.Component {
|
|||||||
this.props.callActions.fetchCalls();
|
this.props.callActions.fetchCalls();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSupportClose() {
|
|
||||||
this.setState({
|
|
||||||
supportVisible: !this.state.supportVisible
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFilterClose(didUpdate) {
|
handleFilterClose(didUpdate) {
|
||||||
this.setState({
|
this.setState({
|
||||||
filterVisible: !this.state.filterVisible
|
filterVisible: !this.state.filterVisible
|
||||||
@@ -256,7 +245,7 @@ class CallPlayer extends React.Component {
|
|||||||
const nextCall = this.props.callsById[nextCallId];
|
const nextCall = this.props.callsById[nextCallId];
|
||||||
var callUrl = nextCall.url;
|
var callUrl = nextCall.url;
|
||||||
|
|
||||||
this.setState({ callUrl: callUrl, callId: nextCallId, sourceIndex: 0, isPlaying: true }, () => { audio.playSource(callUrl) }); //scrollToComponent(this.currentCallRef.current);
|
this.setState({ callUrl: callUrl, callId: nextCallId, isPlaying: true }, () => { audio.playSource(callUrl) }); //scrollToComponent(this.currentCallRef.current);
|
||||||
if (this.currentCallRef.current) {
|
if (this.currentCallRef.current) {
|
||||||
this.currentCallRef.current.scrollIntoView({
|
this.currentCallRef.current.scrollIntoView({
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
@@ -275,7 +264,6 @@ class CallPlayer extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
callUrl: callUrl,
|
callUrl: callUrl,
|
||||||
callId: data.call._id,
|
callId: data.call._id,
|
||||||
sourceIndex: 0,
|
|
||||||
isPlaying: true
|
isPlaying: true
|
||||||
}, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current);
|
}, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current);
|
||||||
this.props.callActions.fetchCallInfo(data.call._id);
|
this.props.callActions.fetchCallInfo(data.call._id);
|
||||||
@@ -342,7 +330,6 @@ class CallPlayer extends React.Component {
|
|||||||
filter.live = false;
|
filter.live = false;
|
||||||
this.setState({
|
this.setState({
|
||||||
callId: _id,
|
callId: _id,
|
||||||
sourceIndex: 0,
|
|
||||||
urlOptions: true,
|
urlOptions: true,
|
||||||
autoPlay: false,
|
autoPlay: false,
|
||||||
callScroll: true,
|
callScroll: true,
|
||||||
@@ -490,7 +477,6 @@ class CallPlayer extends React.Component {
|
|||||||
var callUrl = call.url;
|
var callUrl = call.url;
|
||||||
this.setState({
|
this.setState({
|
||||||
callUrl: callUrl,
|
callUrl: callUrl,
|
||||||
sourceIndex: 0,
|
|
||||||
isPlaying: true,
|
isPlaying: true,
|
||||||
callSelect: false
|
callSelect: false
|
||||||
}, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current);
|
}, () => { audio.playSource(callUrl); }); //scrollToComponent(this.currentCallRef.current);
|
||||||
@@ -548,7 +534,7 @@ class CallPlayer extends React.Component {
|
|||||||
<FilterModal shortName={this.props.shortName} open={this.state.filterVisible} onClose={this.handleFilterClose} />
|
<FilterModal shortName={this.props.shortName} open={this.state.filterVisible} onClose={this.handleFilterClose} />
|
||||||
<CalendarModal open={this.state.calendarVisible} onClose={this.handleCalendarClose} archive={archive} key={this.props.shortName} />
|
<CalendarModal open={this.state.calendarVisible} onClose={this.handleCalendarClose} archive={archive} key={this.props.shortName} />
|
||||||
<GroupModal shortName={this.props.shortName} open={this.state.groupVisible} onClose={this.handleGroupClose} />
|
<GroupModal shortName={this.props.shortName} open={this.state.groupVisible} onClose={this.handleGroupClose} />
|
||||||
<SupportModal open={this.state.supportVisible} onClose={this.handleSupportClose} />
|
|
||||||
<Sidebar as={Menu} animation='overlay' inverted vertical visible={sidebarOpened}
|
<Sidebar as={Menu} animation='overlay' inverted vertical visible={sidebarOpened}
|
||||||
onClick={this.handlePusherClick} duration={50} width='thin'>
|
onClick={this.handlePusherClick} duration={50} width='thin'>
|
||||||
<Menu.Item onClick={this.handlePusherClick} >
|
<Menu.Item onClick={this.handlePusherClick} >
|
||||||
@@ -612,7 +598,7 @@ class CallPlayer extends React.Component {
|
|||||||
<Menu.Item active={this.state.autoPlay} onClick={() => this.switchAutoPlay()}><Icon name="level up" /><span className="desktop-only">Autoplay</span></Menu.Item>
|
<Menu.Item active={this.state.autoPlay} onClick={() => this.switchAutoPlay()}><Icon name="level up" /><span className="desktop-only">Autoplay</span></Menu.Item>
|
||||||
<MediaPlayer ref={this.audioRef} call={currentCall} onEnded={this.callEnded} onPlayPause={this.handlePlayPause} />
|
<MediaPlayer ref={this.audioRef} call={currentCall} onEnded={this.callEnded} onPlayPause={this.handlePlayPause} />
|
||||||
<Menu.Menu position="right" className="desktop-only">
|
<Menu.Menu position="right" className="desktop-only">
|
||||||
<Menu.Item onClick={this.handleSupportToggle}><Icon name="coffee" />Support OpenMHz</Menu.Item>
|
<Menu.Item><SupportModal/></Menu.Item>
|
||||||
<Menu.Item><a href={callDownload}><Icon name="download" />Download</a></Menu.Item>
|
<Menu.Item><a href={callDownload}><Icon name="download" />Download</a></Menu.Item>
|
||||||
<Menu.Item><a href={callLink}><Icon name="at" />Link</a></Menu.Item>
|
<Menu.Item><a href={callLink}><Icon name="at" />Link</a></Menu.Item>
|
||||||
</Menu.Menu>
|
</Menu.Menu>
|
||||||
|
|||||||
@@ -5,8 +5,31 @@ import * as groupActionCreators from "../Group/group-actions"
|
|||||||
import * as systemActionCreators from "../System/system-actions"
|
import * as systemActionCreators from "../System/system-actions"
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import CallPlayer from "./CallPlayer"
|
import CallPlayer from "./CallPlayer"
|
||||||
|
import {
|
||||||
|
useLocation,
|
||||||
|
useNavigate,
|
||||||
|
useParams
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
|
// glorious solution here: https://stackoverflow.com/questions/71097375/how-can-i-use-withrouter-in-react-router-v6-for-passing-props
|
||||||
|
function withRouter(Component) {
|
||||||
|
function ComponentWithRouterProp(props) {
|
||||||
|
let location = useLocation();
|
||||||
|
let navigate = useNavigate();
|
||||||
|
let params = useParams();
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
{...props}
|
||||||
|
router={{ location, navigate, params }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ComponentWithRouterProp;
|
||||||
|
}
|
||||||
|
|
||||||
function mapStateToProps(state, props) {
|
function mapStateToProps(state, props) {
|
||||||
|
const shortName = props.router.params.shortName;
|
||||||
return {
|
return {
|
||||||
callsById: state.call.byId,
|
callsById: state.call.byId,
|
||||||
callsAllIds: state.call.allIds,
|
callsAllIds: state.call.allIds,
|
||||||
@@ -18,14 +41,15 @@ function mapStateToProps(state, props) {
|
|||||||
filterTalkgroups: state.call.filterTalkgroups,
|
filterTalkgroups: state.call.filterTalkgroups,
|
||||||
filterGroupId: state.call.filterGroupId,
|
filterGroupId: state.call.filterGroupId,
|
||||||
filterStarred: state.call.filterStarred,
|
filterStarred: state.call.filterStarred,
|
||||||
system: state.system.items.find((item) => item.shortName === props.match.params.shortName),
|
system: state.system.items.find((item) => item.shortName === shortName),
|
||||||
groups: state.group.items[props.match.params.shortName],
|
groups: state.group.items[shortName],
|
||||||
talkgroups: state.talkgroup.items[props.match.params.shortName],
|
talkgroups: state.talkgroup.items[shortName],
|
||||||
callsIsWaiting: state.call.isWaiting,
|
callsIsWaiting: state.call.isWaiting,
|
||||||
systemIsWaiting: state.system.isWaiting,
|
systemIsWaiting: state.system.isWaiting,
|
||||||
talkgroupsIsWaiting: state.talkgroup.isWaiting,
|
talkgroupsIsWaiting: state.talkgroup.isWaiting,
|
||||||
groupsIsWaiting: state.group.isWaiting,
|
groupsIsWaiting: state.group.isWaiting,
|
||||||
shortName: props.match.params.shortName
|
location: props.router.location,
|
||||||
|
shortName: shortName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +63,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default connect(
|
export default withRouter(connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(CallPlayer)
|
)(CallPlayer))
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
Icon,
|
Icon,
|
||||||
Progress,
|
Progress,
|
||||||
Label
|
Label
|
||||||
} from "semantic-ui-react";
|
} from "semantic-ui-react";
|
||||||
import ReactAudioPlayer from 'react-audio-player'
|
import ReactAudioPlayer from 'react-audio-player'
|
||||||
|
|
||||||
@@ -23,22 +23,22 @@ class MediaPlayer extends React.Component {
|
|||||||
isPlaying: false
|
isPlaying: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handlePause = () => { this.setState({isPlaying: false}); this.props.onPlayPause(false); }
|
handlePause = () => { this.setState({ isPlaying: false }); this.props.onPlayPause(false); }
|
||||||
handlePlay = () => { this.setState({isPlaying: true}); this.props.onPlayPause(true); }
|
handlePlay = () => { this.setState({ isPlaying: true }); this.props.onPlayPause(true); }
|
||||||
playPause = () => {const audio = this.audioRef.current.audioEl.current; if (this.state.isPlaying) {audio.pause();} else {audio.play();}}
|
playPause = () => { const audio = this.audioRef.current.audioEl.current; if (this.state.isPlaying) { audio.pause(); } else { audio.play(); } }
|
||||||
playSource(callUrl) {
|
playSource(callUrl) {
|
||||||
const audio = this.audioRef.current.audioEl.current;
|
const audio = this.audioRef.current.audioEl.current;
|
||||||
var onEnded = this.props.onEnded;
|
var onEnded = this.props.onEnded;
|
||||||
audio.src=callUrl;
|
audio.src = callUrl;
|
||||||
|
|
||||||
var playPromise = audio.play();
|
var playPromise = audio.play();
|
||||||
|
|
||||||
// In browsers that don’t yet support this functionality,
|
// In browsers that don’t yet support this functionality,
|
||||||
// playPromise won’t be defined.
|
// playPromise won’t be defined.
|
||||||
if (playPromise !== undefined) {
|
if (playPromise !== undefined) {
|
||||||
playPromise.then(function() {
|
playPromise.then(function () {
|
||||||
|
|
||||||
}).catch(function(error) {
|
}).catch(function (error) {
|
||||||
console.log("Automatic playback failed: " + error);
|
console.log("Automatic playback failed: " + error);
|
||||||
onEnded();
|
onEnded();
|
||||||
// Show a UI element to let the user manually start playback.
|
// Show a UI element to let the user manually start playback.
|
||||||
@@ -48,7 +48,7 @@ class MediaPlayer extends React.Component {
|
|||||||
|
|
||||||
updatePlayProgress() {
|
updatePlayProgress() {
|
||||||
const audio = this.audioRef.current.audioEl.current;
|
const audio = this.audioRef.current.audioEl.current;
|
||||||
const {currentTime, duration} = audio;
|
const { currentTime, duration } = audio;
|
||||||
var call = this.props.call;
|
var call = this.props.call;
|
||||||
|
|
||||||
// this checks to see if it should display the next Source ID
|
// this checks to see if it should display the next Source ID
|
||||||
@@ -65,92 +65,60 @@ class MediaPlayer extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.call && (prevProps.call !== this.props.call)) {
|
if (this.props.call && (prevProps.call !== this.props.call)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
sourceIndex: 0
|
sourceIndex: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
componentDidUpdate() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
render(){
|
|
||||||
var playEnabled = { "disabled": true }
|
|
||||||
var sourceId = "-";
|
|
||||||
|
|
||||||
if (this.props.call) {
|
|
||||||
if (this.props.call.srcList.length > this.state.sourceIndex) {
|
|
||||||
sourceId = this.props.call.srcList[this.state.sourceIndex].src;
|
|
||||||
}
|
}
|
||||||
playEnabled = {};
|
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<Menu.Menu>
|
|
||||||
<ReactAudioPlayer
|
|
||||||
ref={this.audioRef}
|
|
||||||
onPause={this.handlePause}
|
|
||||||
onPlay={this.handlePlay}
|
|
||||||
listenInterval={500}
|
|
||||||
onListen={this.updatePlayProgress}
|
|
||||||
onEnded={this.props.onEnded}
|
|
||||||
autoPlay
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var playEnabled = { "disabled": true }
|
||||||
|
var sourceId = "-";
|
||||||
|
|
||||||
<Menu.Item onClick={this.playPause} >
|
if (this.props.call) {
|
||||||
|
if (this.props.call.srcList.length > this.state.sourceIndex) {
|
||||||
{
|
sourceId = this.props.call.srcList[this.state.sourceIndex].src;
|
||||||
this.state.isPlaying
|
|
||||||
? (<Icon name="pause"/>)
|
|
||||||
: (<Icon name="play"/>)
|
|
||||||
}
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item>
|
|
||||||
<Progress inverted percent={this.state.playProgress}/>
|
|
||||||
<Label color="black">
|
|
||||||
{this.state.playTime}
|
|
||||||
Sec
|
|
||||||
</Label>
|
|
||||||
<Label color="black" className="desktop-only">
|
|
||||||
{sourceId}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Menu>
|
|
||||||
|
|
||||||
|
|
||||||
/* <Media>
|
|
||||||
{
|
|
||||||
mediaProps => <Menu.Menu>
|
|
||||||
|
|
||||||
<Menu.Item onClick={() => mediaProps.playPause()} {...playEnabled} >
|
|
||||||
<Player src={callUrl} vendor="audio" onEnded={this.props.onEnded} autoPlay={true} onTimeUpdate={this.updatePlayProgress}/>
|
|
||||||
|
|
||||||
{
|
|
||||||
mediaProps.isPlaying
|
|
||||||
? (<Icon name="pause"/>)
|
|
||||||
: (<Icon name="play"/>)
|
|
||||||
}
|
|
||||||
</Menu.Item><Menu.Item>
|
|
||||||
<Progress inverted percent={this.state.playProgress}/>
|
|
||||||
<Label color="black">
|
|
||||||
{this.state.playTime}
|
|
||||||
Sec
|
|
||||||
</Label>
|
|
||||||
<Label color="black" className="desktop-only">
|
|
||||||
{sourceId}
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.Menu>
|
|
||||||
}
|
}
|
||||||
</Media>*/
|
playEnabled = {};
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Menu.Menu>
|
||||||
|
<ReactAudioPlayer
|
||||||
|
ref={this.audioRef}
|
||||||
|
onPause={this.handlePause}
|
||||||
|
onPlay={this.handlePlay}
|
||||||
|
listenInterval={100}
|
||||||
|
onListen={this.updatePlayProgress}
|
||||||
|
onEnded={this.props.onEnded}
|
||||||
|
autoPlay
|
||||||
|
/>
|
||||||
|
|
||||||
)
|
|
||||||
}
|
<Menu.Item onClick={this.playPause} >
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.isPlaying
|
||||||
|
? (<Icon name="pause" />)
|
||||||
|
: (<Icon name="play" />)
|
||||||
|
}
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item>
|
||||||
|
<Progress inverted percent={this.state.playProgress} />
|
||||||
|
<Label color="black">
|
||||||
|
{this.state.playTime}
|
||||||
|
Sec
|
||||||
|
</Label>
|
||||||
|
<Label color="black" className="desktop-only">
|
||||||
|
{sourceId}
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export default MediaPlayer;
|
export default MediaPlayer;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Component } from "react";
|
import {useState} from 'react';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
@@ -7,49 +7,35 @@ import {
|
|||||||
import "./FilterModal.css";
|
import "./FilterModal.css";
|
||||||
|
|
||||||
|
|
||||||
class SupportModal extends Component {
|
function SupportModal(props) {
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
this.handleDone = this.handleDone.bind(this);
|
|
||||||
this.handleClose = this.handleClose.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
const [open, setOpen] = useState(false);
|
||||||
open: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const handleClose = () => props.onClose();
|
||||||
|
|
||||||
handleClose = () => this.props.onClose(false);
|
return (
|
||||||
|
<Modal open={open} onClose={() => setOpen(false)} onOpen={() => setOpen(true)} trigger={<div><Icon name="coffee" />Support OpenMHz</div>} size='tiny' >
|
||||||
handleDone(event) {
|
<Modal.Header>Support OpenMHz</Modal.Header>
|
||||||
this.props.onClose();
|
<Modal.Content image>
|
||||||
}
|
<Icon size='massive' name="coffee" />
|
||||||
|
<Modal.Description>
|
||||||
render() {
|
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
|
||||||
return (
|
<p>No worries if not, I am not having problems keeping it going.</p>
|
||||||
|
<a href="https://github.com/sponsors/robotastic"><Button>
|
||||||
<Modal open={this.props.open} onClose={this.handleClose} size='tiny' >
|
|
||||||
<Modal.Header>Support OpenMHz</Modal.Header>
|
|
||||||
<Modal.Content image>
|
|
||||||
<Icon size='massive' name="coffee"/>
|
|
||||||
<Modal.Description>
|
|
||||||
<p>If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.</p>
|
|
||||||
<p>No worries if not, I am not having problems keeping it going.</p>
|
|
||||||
<a href="https://github.com/sponsors/robotastic"><Button>
|
|
||||||
Support
|
Support
|
||||||
</Button></a>
|
</Button></a>
|
||||||
</Modal.Description>
|
</Modal.Description>
|
||||||
|
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Actions>
|
<Modal.Actions>
|
||||||
<Button onClick={this.handleDone} >
|
<Button onClick={() => setOpen(false)} >
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
</Modal.Actions>
|
</Modal.Actions>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SupportModal;
|
export default SupportModal;
|
||||||
|
|||||||
93
frontend/src/features/api/apiSlice.js
Normal file
93
frontend/src/features/api/apiSlice.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Import the RTK Query methods from the React-specific entry point
|
||||||
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||||
|
|
||||||
|
|
||||||
|
function buildCallUrl(state, date, direction) {
|
||||||
|
var params = {};
|
||||||
|
var url = "";
|
||||||
|
|
||||||
|
|
||||||
|
if ((typeof direction === 'string') && (typeof date === 'number')) {
|
||||||
|
url = url + '/' + direction;
|
||||||
|
params["time"] = date;
|
||||||
|
} else {
|
||||||
|
if (state.call.filterDate){
|
||||||
|
url = url + '/older'
|
||||||
|
params["time"] = state.call.filterDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.call.filterStarred) {
|
||||||
|
params["filter-starred"] = true;
|
||||||
|
}
|
||||||
|
switch(state.call.filterType) {
|
||||||
|
case 1:
|
||||||
|
params["filter-type"] = "group";
|
||||||
|
params["filter-code"] = state.call.filterGroupId;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
params["filter-type"] = "talkgroup";
|
||||||
|
params["filter-code"] = state.call.filterTalkgroups;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 0:
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
|
if (this.filterCode != "") {
|
||||||
|
params["filter-code"] = this.filterCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterType == this.FilterType.Group) {
|
||||||
|
params["filter-type"] = "group";
|
||||||
|
params["filter-name"] = this.filterName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterType == this.FilterType.Talkgroup) {
|
||||||
|
params["filter-type"] = "talkgroup";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterType == this.FilterType.Unit) {
|
||||||
|
params["filter-type"] = "unit";
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
// url = startUp.backend_server + "/" + startUp.shortName.toLowerCase() + "/calls" + url + '?' + buildUrlParams(params);
|
||||||
|
url = process.env.REACT_APP_BACKEND_SERVER + "/" + state.call.shortName + "/calls" + url + '?' + buildUrlParams(params);
|
||||||
|
console.log("Trying to fetch data from this url: " + url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define our single API slice object
|
||||||
|
export const apiSlice = createApi({
|
||||||
|
// The cache reducer expects to be added at `state.api` (already default - this is optional)
|
||||||
|
reducerPath: 'api',
|
||||||
|
// All of our requests will have URLs starting with '/fakeApi'
|
||||||
|
baseQuery: fetchBaseQuery({ baseUrl: process.env.REACT_APP_BACKEND_SERVER }),
|
||||||
|
// The "endpoints" represent operations and requests for this server
|
||||||
|
endpoints: builder => ({
|
||||||
|
// The `getPosts` endpoint is a "query" operation that returns data
|
||||||
|
getGroups: builder.query({
|
||||||
|
// The URL for the request is '/fakeApi/posts'
|
||||||
|
query: (shortName) => ({ url: '/${shortName}/groups' })
|
||||||
|
}),
|
||||||
|
getSystems: builder.query({
|
||||||
|
// The URL for the request is '/fakeApi/posts'
|
||||||
|
query: () => ({ url: '/systems' })
|
||||||
|
}),
|
||||||
|
getTalkgroups: builder.query({
|
||||||
|
// The URL for the request is '/fakeApi/posts'
|
||||||
|
query: (shortName) => ({ url: '/${shortName}/talkgroups' })
|
||||||
|
}),
|
||||||
|
getCalls: builder.query({
|
||||||
|
// The URL for the request is '/fakeApi/posts'
|
||||||
|
query: ({shortName, filterType, filterCode}) => ({
|
||||||
|
url: "/${shortName}/calls",
|
||||||
|
method: "GET",
|
||||||
|
params: { filterType, filterCode },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export the auto-generated hook for the `getPosts` query endpoint
|
||||||
|
export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetCallsQuery } = apiSlice
|
||||||
0
frontend/src/features/call/callSlice.js
Normal file
0
frontend/src/features/call/callSlice.js
Normal file
11
frontend/src/features/group/groupSlice.js
Normal file
11
frontend/src/features/group/groupSlice.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const groupSlice = createSlice({
|
||||||
|
name: 'group',
|
||||||
|
initialState: [],
|
||||||
|
reducers: {
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Extract the action creators object and the reducer
|
||||||
|
const { actions, reducer } = groupSlice
|
||||||
|
// Export the reducer, either as a default or named export
|
||||||
|
export default reducer
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { Routes ,Route } from 'react-router-dom';
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { Provider } from 'react-redux'
|
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 configureStore, { history } from './redux-router/configureStore'
|
||||||
|
|
||||||
import 'semantic-ui-css/semantic.min.css'
|
import 'semantic-ui-css/semantic.min.css'
|
||||||
|
|
||||||
|
// withTracker
|
||||||
|
//import withTracker from './withTracker';
|
||||||
|
|
||||||
// Main
|
// Main
|
||||||
import MainContainer from "./Main/MainContainer"
|
import MainContainer from "./Main/MainContainer"
|
||||||
|
|
||||||
@@ -15,20 +19,22 @@ import ListSystemsContainer from "./System/ListSystemsContainer"
|
|||||||
// Call
|
// Call
|
||||||
import CallPlayerContainer from "./Call/CallPlayerContainer"
|
import CallPlayerContainer from "./Call/CallPlayerContainer"
|
||||||
|
|
||||||
const store = configureStore(/* provide initial state if any */)
|
import AboutComponent from "./About/AboutComponent"
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
|
const store = configureStore(/* provide initial state if any */)
|
||||||
|
const container = document.getElementById('root');
|
||||||
|
const root = createRoot(container);
|
||||||
|
root.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
|
<BrowserRouter>
|
||||||
<> { /* your usual react-router v4/v5 routing */ }
|
<Routes>
|
||||||
<Switch>
|
<Route exact path="/" element={<MainContainer/>} />
|
||||||
<Route exact path="/" component={MainContainer} />
|
<Route exact path="/systems" element={<ListSystemsContainer/>} />
|
||||||
<Route exact path="/systems" component={ListSystemsContainer} />
|
<Route exact path="/system/:shortName" element={<CallPlayerContainer/>} />
|
||||||
<Route exact path="/system/:shortName" component={CallPlayerContainer} />
|
<Route exact path="/about" element={<AboutComponent/>} />
|
||||||
<Route render={() => (<div>Miss</div>)} />
|
|
||||||
</Switch>
|
</Routes>
|
||||||
</>
|
</BrowserRouter>
|
||||||
</ConnectedRouter>
|
</Provider>
|
||||||
</Provider>,
|
|
||||||
document.getElementById('root')
|
|
||||||
)
|
)
|
||||||
@@ -1,27 +1,21 @@
|
|||||||
// https://github.com/supasate/connected-react-router
|
// https://github.com/supasate/connected-react-router
|
||||||
// configureStore.js
|
// 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()
|
import { apiSlice } from '../features/api/apiSlice'
|
||||||
|
|
||||||
export default function configureStore(preloadedState) {
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
|
||||||
const store = createStore(
|
|
||||||
createRootReducer(history), // root reducer with router state
|
export default function configureAppStore(preloadedState) {
|
||||||
preloadedState,
|
const store = configureStore({
|
||||||
composeEnhancers(
|
reducer: {
|
||||||
applyMiddleware(
|
[apiSlice.reducerPath]: apiSlice.reducer
|
||||||
routerMiddleware(history), // for dispatching history actions
|
},
|
||||||
thunk,
|
middleware: (getDefaultMiddleware) =>
|
||||||
// ... other middlewares ...
|
getDefaultMiddleware().concat(apiSlice.middleware),
|
||||||
),
|
preloadedState
|
||||||
),
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,13 @@ import systemReducer from "../System/system-reducer"
|
|||||||
import talkgroupReducer from "../Talkgroups/talkgroup-reducer"
|
import talkgroupReducer from "../Talkgroups/talkgroup-reducer"
|
||||||
import groupReducer from "../Group/group-reducer"
|
import groupReducer from "../Group/group-reducer"
|
||||||
import callReducer from "../Call/call-reducer"
|
import callReducer from "../Call/call-reducer"
|
||||||
|
import { apiSlice } from '../features/api/apiSlice'
|
||||||
|
|
||||||
const createRootReducer = (history) => combineReducers({
|
export default (history) => combineReducers({
|
||||||
router: connectRouter(history),
|
router: connectRouter(history),
|
||||||
system: systemReducer,
|
system: systemReducer,
|
||||||
talkgroup: talkgroupReducer,
|
talkgroup: talkgroupReducer,
|
||||||
group: groupReducer,
|
group: groupReducer,
|
||||||
call: callReducer
|
call: callReducer,
|
||||||
|
[apiSlice.reducerPath]: apiSlice.reducer
|
||||||
})
|
})
|
||||||
export default createRootReducer
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
39
frontend/src/withTracker.js
Normal file
39
frontend/src/withTracker.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactGA from 'react-ga';
|
||||||
|
|
||||||
|
export default function withTracker(WrappedComponent, options = {}) {
|
||||||
|
const trackPage = (page) => {
|
||||||
|
ReactGA.set({
|
||||||
|
page,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
ReactGA.pageview(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
const HOC = class extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
location: { pathname: page }
|
||||||
|
} = this.props;
|
||||||
|
trackPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
|
const {
|
||||||
|
location: { pathname: currentPage }
|
||||||
|
} = this.props;
|
||||||
|
const nextPage = nextProps.location.pathname;
|
||||||
|
|
||||||
|
if (currentPage !== nextPage) {
|
||||||
|
trackPage(nextPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <WrappedComponent {...this.props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return HOC;
|
||||||
|
}
|
||||||
13190
frontend/yarn.lock
13190
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -39,5 +39,19 @@ http {
|
|||||||
text/xml application/xml application/xml+rss text/javascript;
|
text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
server {
|
||||||
|
|
||||||
|
# Enable NGINX status module
|
||||||
|
location /nginx_status {
|
||||||
|
# freely available with open source NGINX
|
||||||
|
stub_status;
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# for open source NGINX < version 1.7.5
|
||||||
|
# stub_status on;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
node_modules/.yarn-integrity
generated
vendored
6
node_modules/.yarn-integrity
generated
vendored
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"nodeVersion": "v10.15.3",
|
"systemParams": "darwin-arm64-93",
|
||||||
"modulesFolders": [
|
"modulesFolders": [],
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"linkedModules": [],
|
"linkedModules": [],
|
||||||
"topLevelPatterns": [],
|
"topLevelPatterns": [],
|
||||||
|
|||||||
@@ -22,3 +22,7 @@ export REACT_APP_ACCOUNT_SERVER="${PROTOCOL}account.${DOMAIN_NAME}"
|
|||||||
export REACT_APP_ADMIN_SERVER="${PROTOCOL}admin.${DOMAIN_NAME}"
|
export REACT_APP_ADMIN_SERVER="${PROTOCOL}admin.${DOMAIN_NAME}"
|
||||||
export REACT_APP_COOKIE_DOMAIN=".${DOMAIN_NAME}"
|
export REACT_APP_COOKIE_DOMAIN=".${DOMAIN_NAME}"
|
||||||
|
|
||||||
|
export TWITTER_CONSUMER_KEY=""
|
||||||
|
export TWITTER_CONSUMER_SECRET=""
|
||||||
|
export TWITTER_ACCESS_TOKEN_KEY=""
|
||||||
|
export TWITTER_ACCESS_TOKEN_SECRET=""
|
||||||
Reference in New Issue
Block a user