diff --git a/account/server/controllers/systems.js b/account/server/controllers/systems.js index 8c6a83b..8ce71bb 100644 --- a/account/server/controllers/systems.js +++ b/account/server/controllers/systems.js @@ -18,6 +18,7 @@ exports.getUserSystems = async function (_id) { name, shortName, description, + status, systemType, city, state, @@ -31,6 +32,7 @@ exports.getUserSystems = async function (_id) { name, shortName, description, + status, systemType, city, state, diff --git a/account/server/models/system.js b/account/server/models/system.js index ca6cf93..a2aa65e 100644 --- a/account/server/models/system.js +++ b/account/server/models/system.js @@ -10,6 +10,7 @@ var systemSchema = mongoose.Schema({ city: String, state: String, description: String, + status: String, showScreenName: Boolean, planType: {type: Number, default: 0}, userId: mongoose.Schema.Types.ObjectId, diff --git a/admin/server/controllers/systems.js b/admin/server/controllers/systems.js index 37723df..af28c99 100644 --- a/admin/server/controllers/systems.js +++ b/admin/server/controllers/systems.js @@ -61,7 +61,7 @@ exports.listUserSystems = async function (req,res,next) { for (let index = 0; index < users.length; index++) { let user = users[index].toObject(); const userId = new mongoose.Types.ObjectId(user._id); - const systems = await System.find({ userId: userId }, "name shortName description systemType city state county country lastActive"); + const systems = await System.find({ userId: userId }, "name shortName description status systemType city state county country lastActive"); user["systems"] = systems; list.push(user); @@ -89,6 +89,7 @@ exports.listAllSystems = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -105,6 +106,7 @@ exports.listAllSystems = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -153,6 +155,7 @@ exports.listSystems = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -167,6 +170,7 @@ exports.listSystems = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -359,6 +363,7 @@ exports.updateSystem = async function (req, res, next) { res.locals.system.county = res.locals.county; res.locals.system.country = res.locals.country; res.locals.system.description = res.locals.description; + res.locals.system.status = res.locals.status; res.locals.system.systemType = res.locals.systemType; res.locals.system.showScreenName = res.locals.showScreenName; res.locals.system.allowContact = res.locals.allowContact; @@ -377,6 +382,7 @@ exports.updateSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -391,6 +397,7 @@ exports.updateSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -428,7 +435,7 @@ exports.validateSystem = async function (req, res, next) { 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"); + console.error("ERROR: Validate System - req.body.description: " + req.body.description); res.status(500) res.json({ success: false, @@ -436,7 +443,9 @@ exports.validateSystem = async function (req, res, next) { }); return; } - res.locals.description = req.body.description.replace(/[^\w\s\.\,\-\_]/gi, ''); + res.locals.description = req.body.description.replace(/[^\w\s\.\,\-\_\(\)\'\"\!\?\*\&\^\%\$\#\@\r\n\t\`\~\[\]\/\\]/gi, ''); + + res.locals.status = req.body.status.replace(/[^\w\s\.\,\-\_\(\)\'\"\!\?\*\&\^\%\$\#\@\r\n\t\`\~\[\]\/\\]/gi, ''); if (!req.body.shortName || (req.body.shortName.length < 2)) { console.error("ERROR: Validate System - req.body.shortName"); @@ -546,6 +555,7 @@ exports.createSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -558,6 +568,7 @@ exports.createSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -584,6 +595,7 @@ exports.createSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, @@ -598,6 +610,7 @@ exports.createSystem = async function (req, res, next) { name, shortName, description, + status, systemType, city, state, diff --git a/admin/server/models/system.js b/admin/server/models/system.js index b19efe9..92c99cb 100644 --- a/admin/server/models/system.js +++ b/admin/server/models/system.js @@ -11,6 +11,7 @@ var systemSchema = mongoose.Schema({ city: String, state: String, description: String, + status: String, showScreenName: Boolean, allowContact: { type: Boolean, diff --git a/admin/src/System/System.js b/admin/src/System/System.js index 11d5a8e..a81750f 100644 --- a/admin/src/System/System.js +++ b/admin/src/System/System.js @@ -236,7 +236,6 @@ const System = () => { setGroupOrder(newOrder); } }, [groupsData]) - useEffect(() => { if (isSystemsSuccess && systemsData.stats[shortName]) { processStatistics(systemsData.stats[shortName]) @@ -328,7 +327,12 @@ const System = () => {
Description
{system.description} - + {system.status.length>0 && + +
Status Message
+ {system.status} +
+ } } /> Download Link diff --git a/frontend/src/Call/Calls.js b/frontend/src/Call/Calls.js index fb03323..989e462 100644 --- a/frontend/src/Call/Calls.js +++ b/frontend/src/Call/Calls.js @@ -14,7 +14,9 @@ import { Label, Menu, Icon, - Sidebar + Sidebar, + Message, + Sticky } from "semantic-ui-react"; import "./CallPlayer.css"; import queryString from '../query-string'; @@ -447,9 +449,10 @@ function Calls(props) { + {activityVisible ? - : + : } ); diff --git a/frontend/src/Call/components/SupportModal.js b/frontend/src/Common/SupportModal.js similarity index 73% rename from frontend/src/Call/components/SupportModal.js rename to frontend/src/Common/SupportModal.js index 6226040..9703095 100644 --- a/frontend/src/Call/components/SupportModal.js +++ b/frontend/src/Common/SupportModal.js @@ -2,9 +2,11 @@ import {useState} from 'react'; import { Modal, Button, + ButtonContent, + ButtonGroup, + Divider, Icon } from "semantic-ui-react"; -import "./FilterModal.css"; function SupportModal(props) { @@ -14,16 +16,18 @@ function SupportModal(props) { const handleClose = () => props.onClose(); return ( - setOpen(false)} onOpen={() => setOpen(true)} trigger={
Support OpenMHz
} size='tiny' > + setOpen(false)} onOpen={() => setOpen(true)} trigger={props.trigger} size='tiny' > Support OpenMHz

If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.

-

No worries if not, I am not having problems keeping it going.

- + + Donate + + + +
diff --git a/frontend/src/Event/EventPlayer.js b/frontend/src/Event/EventPlayer.js index 9c10606..fad3d3d 100644 --- a/frontend/src/Event/EventPlayer.js +++ b/frontend/src/Event/EventPlayer.js @@ -1,21 +1,21 @@ -import React, { useEffect, useLayoutEffect, useState, useRef } from "react"; -import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'; -import { skipToken } from '@reduxjs/toolkit/query' +import React, { useEffect, useState, useRef } from "react"; +import { useNavigate } from 'react-router-dom'; import MediaPlayer from "../Call/components/MediaPlayer"; -import SupportModal from "../Call/components/SupportModal"; +import SupportModal from "../Common/SupportModal"; import EventCallInfo from "./EventCallInfo"; import ListEventCalls from "./ListEventCalls"; -import { useSelector, useDispatch } from 'react-redux' +import { useDispatch } from 'react-redux' import { Container, - Label, Rail, Sticky, Menu, Icon, - Sidebar + Sidebar, + Button, + ButtonContent } from "semantic-ui-react"; @@ -118,7 +118,12 @@ function EventPlayer(props) { handleAutoPlay(autoPlay)}>Autoplay - + + + Donate + + + } /> Download Link diff --git a/frontend/src/Main/Main.js b/frontend/src/Main/Main.js index e8bf294..1d6f565 100644 --- a/frontend/src/Main/Main.js +++ b/frontend/src/Main/Main.js @@ -1,10 +1,13 @@ import React, { useState, useEffect, useRef } from "react"; import { Link, useNavigate } from 'react-router-dom' import SystemCard from "../System/SystemCard"; +import SupportModal from "../Common/SupportModal"; import "./Main.css"; import { createMedia } from "@artsy/fresnel"; import { Button, + ButtonGroup, + ButtonContent, Container, Divider, Grid, @@ -40,7 +43,7 @@ const { Media } = AppMedia; * such things. */ const HomepageHeading = ({ mobile }) => ( - +
{ +const DesktopContainer = (props) => { const [fixed, setFixed] = useState(false); - const hideFixedMenu = () => setFixed(false) + const hideFixedMenu = () => setFixed(false) const showFixedMenu = () => setFixed(true) - - const { children } = props - return ( - -
+ const { children } = props + + return ( + +
- + + + +
{process.env.REACT_APP_SITE_NAME}
+ Listen + Events + About +
+
+ +
+
- - -
{process.env.REACT_APP_SITE_NAME}
- Listen - Events - About -
-
- - -
- - {children} -
- ) - } + {children} + + ) +} /* DesktopContainer.propTypes = { @@ -146,55 +150,57 @@ const MobileContainer = (props) => { const handleToggle = () => setSidebarOpened(!sidebarOpened) - const { children } = props + const { children } = props - return ( - - - - - Home - - Systems - About - + return ( + + + + + Home + + Systems + About + - +
-
-
-
+
+
+
- + - - - - - {process.env.REACT_APP_SITE_NAME} - - + + + + + {process.env.REACT_APP_SITE_NAME} + +
- {children} -
-
-
- ) - } + {children} + +
+
+ ) +} /* MobileContainer.propTypes = { children: PropTypes.node, @@ -213,16 +219,16 @@ const Main = (props) => { const [visible, setVisible] = useState(true); const [currentSystem, setCurrentSystem] = useState(0); const navigate = useNavigate(); - const {data:systems, isSuccess} = useGetSystemsQuery(); //= selectAllSystems(); + const { data: systems, isSuccess } = useGetSystemsQuery(); //= selectAllSystems(); function useInterval(callback, delay) { const savedCallback = useRef(); - + // Remember the latest callback. useEffect(() => { savedCallback.current = callback; }, [callback]); - + // Set up the interval. useEffect(() => { function tick() { @@ -235,133 +241,159 @@ const Main = (props) => { }, [delay]); } - const advanceSystem = () => { - if (isSuccess) { - let nextSystem = currentSystem + 1; - if (nextSystem > systems.systems.length) { - nextSystem = 0; - } - setCurrentSystem(nextSystem); - setVisible(!visible); + const advanceSystem = () => { + if (isSuccess) { + let nextSystem = currentSystem + 1; + if (nextSystem > systems.systems.length) { + nextSystem = 0; } - //this.setState({ visible: !this.state.visible, currentSystem }); + setCurrentSystem(nextSystem); + setVisible(!visible); } - - useInterval( ()=>{ advanceSystem()}, 3000) + //this.setState({ visible: !this.state.visible, currentSystem }); + } + + useInterval(() => { advanceSystem() }, 3000) -//https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux -//https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter + //https://stackoverflow.com/questions/36559661/how-can-i-dispatch-from-child-components-in-react-redux + //https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter let system = false; if (isSuccess) { - system = systems.systems[currentSystem]; + system = systems.systems[currentSystem]; } return ( - <> - - -
- - - - - { system && ( + <> + + +
+ + + + + {system && ( - navigate("/system/" + system.shortName)}/> + navigate("/system/" + system.shortName)} /> - )} - - - - - - - - - - - - - - -
- All The Calls -
-

Every call, on every talkgroup is recorded

-
- -
- - Go back in time -
-

- Missed a call? Not a problem, everything is archived! -

-
-
-
-
- - -
- Don't just scan, hear it all! -
-

- Most large cities use trunked radio systems to get the most use out of their assigned radio spectrum. With trunked systems, the transmission are constantly hopping to different frequencies. Using a cheap Software Defined Radio (SDR), it is possible capture all of the transmission on a system. - Instead of scanning to a single frequency, SDR capturea wide swathes of spectrum, covering all of the frequencies a system could use. -

- - Get Involved - -
- Want to record your local radio system? -
-

- Join us! Record a local radio system and share it with the world. If you have a spare computer, it is easy to get started. With a $25 SDR or two, you can capture an entire system. - It does take a little bit of work to get things setup, but we are here to help and answer questions. -

- - - -
-
-
- - - - - - - - -
- The End -
-
- +
)} +
+ + + + + +
+
+
+ + + + + + + + Donate + + + + } /> + + + + + + + - - - - - - - ); + + + + +
+ All The Calls +
+

Every call, on every talkgroup is recorded

+
+ +
+ + Go back in time +
+

+ Missed a call? Not a problem, everything is archived! +

+
+
+
+
+ + +
+ Don't just scan, hear it all! +
+

+ Most large cities use trunked radio systems to get the most use out of their assigned radio spectrum. With trunked systems, the transmission are constantly hopping to different frequencies. Using a cheap Software Defined Radio (SDR), it is possible capture all of the transmission on a system. + Instead of scanning to a single frequency, SDR capturea wide swathes of spectrum, covering all of the frequencies a system could use. +

+ + Get Involved + +
+ Want to record your local radio system? +
+

+ Join us! Record a local radio system and share it with the world. If you have a spare computer, it is easy to get started. With a $25 SDR or two, you can capture an entire system. + It does take a little bit of work to get things setup, but we are here to help and answer questions. +

+ + + +
+
+
+ + + + + + + + +
+ The End +
+
+ + + + +
+
+
+
+ +
+ ); } diff --git a/frontend/src/System/SystemCard.js b/frontend/src/System/SystemCard.js index 1e44199..1606e9b 100644 --- a/frontend/src/System/SystemCard.js +++ b/frontend/src/System/SystemCard.js @@ -3,114 +3,141 @@ import { Card, Icon, Label, - Grid + Grid, + Segment, + Popup, + Button } from "semantic-ui-react"; -function SystemCard (props) { +function SystemCard(props) { + + const { system, keepShort, onClick, onContactClick } = props; + var location = ""; + var clientCount = ""; + var callAvg = ""; + let description = system.description; + let statusMessage = ""; - const {system, keepShort, onClick, onContactClick} = props; - var location = ""; - var clientCount = ""; - var callAvg = ""; - let description = system.description; - - - - const handleContactClick = (event) => { - event.stopPropagation(); - onContactClick(system); + if (system.status && system.status.length > 0) { + statusMessage = system.status + if (statusMessage.length > 25) { + statusMessage = statusMessage.substring(0, 25) + "..."; } + } - const onCardClick = (event) => { - onClick(); - } - if (keepShort && (description.length > 100)) { - description = description.substring(0, 96) + "..."; - } - if (system.callAvg) { - callAvg = Math.round(system.callAvg * 10) / 10 + "/min" - } - if (system.clientCount === 1) { - clientCount = "1 listener"; - } else if (system.clientCount > 1) { - clientCount = system.clientCount + " listeners" - } - let status = (<>) - if (system.active) { - status = ( - + const handleContactClick = (event) => { + event.stopPropagation(); + onContactClick(system); + } + + const onCardClick = (event) => { + onClick(); + } + + if (keepShort && (description.length > 100)) { + description = description.substring(0, 96) + "..."; + } + + + + + if (keepShort) { + statusMessage = ""; + } + + if (system.callAvg) { + callAvg = Math.round(system.callAvg * 10) / 10 + "/min" + } + if (system.clientCount === 1) { + clientCount = "1 listener"; + } else if (system.clientCount > 1) { + clientCount = system.clientCount + " listeners" + } + + let status = (<>) + + if (system.active) { + status = ( + {clientCount &&
{clientCount}
}
{callAvg &&
{callAvg}
} -
-
-
- ) - } else { - const lastActive = new Date(system.lastActive); - status = ( - - - - Last Active {lastActive.toLocaleDateString()} - - - ) - } +
+ ) + } else { + const lastActive = new Date(system.lastActive); + status = ( + + + + Last Active {lastActive.toLocaleDateString()} + + + - if (system) { - switch (system.systemType) { - case "state": - location = system.state; - break; - case "city": - location = system.city + ", " + system.state; - break; - case "county": - location = system.county + ", " + system.state; - break; - case "international": - location = system.country; - break; - default: - location = system.city + ", " + system.state; - } + ) + } + + if (system) { + switch (system.systemType) { + case "state": + location = system.state; + break; + case "city": + location = system.city + ", " + system.state; + break; + case "county": + location = system.county + ", " + system.state; + break; + case "international": + location = system.country; + break; + default: + location = system.city + ", " + system.state; } - return ( - - - {system.name} - {location} - - + } + return ( + + + {system.name} + {location} + + - + {system.screenName && } {system.allowContact && } - - + + + + + {description} + + + {statusMessage && - {description} + {statusMessage}} /> - - - - - {status} - - - ); + } + + + + + {status} + + + ); } export default SystemCard; diff --git a/nginx-proxy/test/vhosts/site.template b/nginx-proxy/test/vhosts/site.template index 81b1097..aa12004 100644 --- a/nginx-proxy/test/vhosts/site.template +++ b/nginx-proxy/test/vhosts/site.template @@ -4,6 +4,7 @@ server { server_name ${DOMAIN_NAME} www.${DOMAIN_NAME}; + location /.well-known/acme-challenge/ { root /var/www/certbot; }