diff --git a/admin/server/controllers/systems.js b/admin/server/controllers/systems.js index 0eefb52..05d5ac7 100644 --- a/admin/server/controllers/systems.js +++ b/admin/server/controllers/systems.js @@ -13,7 +13,44 @@ exports.isLoggedIn = function (req, res, next) { // ------------------------------------------- -exports.listUserSystems = async function (req, res, next) { + +exports.listActiveUsers = async function (req,res,next) { + if (!req.user.admin) { + res.status(401); + res.json({ success: false, message: "Not Authorized" }); + return; + } + let users = {}; + let fromDate = new Date(Date.now() - 60 * 60 * 24 * 30 * 1000); + const systems = await System.find({lastActive: {$gte: fromDate}}).populate('userId', "screenName").catch( err => { + console.error("Error - get_systems: " + err.message); + res.json({ + success: false, + message: err + }); + return; + }); + for (let index = 0; index < systems.length; index++) { + const system = systems[index]; + + if (!users.hasOwnProperty(system.userId.toString())) { + const user = await User.findById(system.userId, "_id email firstName lastName lastLogin"); + users[system.userId.toString()] = user.email; + } + + } + + let list = []; + for (const id in users) { + list.push(users[id]); + } + + res.json(list); + return; +} + + +exports.listUserSystems = async function (req,res,next) { if (!req.user.admin) { res.status(401); res.json({ success: false, message: "Not Authorized" }); diff --git a/admin/server/index.js b/admin/server/index.js index d6ddb96..8033f75 100644 --- a/admin/server/index.js +++ b/admin/server/index.js @@ -153,6 +153,7 @@ app.get("/groups/:shortName/:groupId?", isLoggedIn, groups.getGroups); app.delete("/groups/:shortName/:groupId", isLoggedIn, groups.deleteGroup); app.get("/systems", isLoggedIn, systems.listSystems) app.get("/admin/systems", isAdmin, systems.listAllSystems) +app.get("/admin/users/active", isAdmin, systems.listActiveUsers) app.get("/admin/users", isAdmin, systems.listUserSystems) app.delete("/systems/:shortName", isLoggedIn, systems.deleteSystem) app.post("/systems/:shortName", [isLoggedIn, systems.ownSystem, systems.validateSystem, systems.updateSystem]) diff --git a/admin/src/ActiveUsers/ActiveUsers.js b/admin/src/ActiveUsers/ActiveUsers.js new file mode 100644 index 0000000..7facb25 --- /dev/null +++ b/admin/src/ActiveUsers/ActiveUsers.js @@ -0,0 +1,37 @@ +import { + Container, + Header, + Table, + Icon +} from "semantic-ui-react"; +import { useGetActiveUsersQuery, } from '../features/api/apiSlice' + + + +// ---------------------------------------------------- +const ActiveUsers = () => { + const { data: users, isSuccess } = useGetActiveUsersQuery(); + + +//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 + + return ( +
+ +
Email of Active Users
+ + + {users && + users.map((user) => + + {user}, + )} + +
+
+ ); + +} + +export default ActiveUsers; diff --git a/admin/src/App/App.js b/admin/src/App/App.js index 8d5a75e..e818162 100644 --- a/admin/src/App/App.js +++ b/admin/src/App/App.js @@ -9,6 +9,7 @@ import AllSystems from "../AllSystems/AllSystems" import ListSystems from "../System/ListSystems" import UpdateSystem from "../System/UpdateSystem" import CreateSystem from "../System/CreateSystem" +import ActiveUsers from "../ActiveUsers/ActiveUsers" const App = (params) => { @@ -22,6 +23,7 @@ const App = (params) => { } /> } /> } /> + } /> } /> (
Miss
)} /> diff --git a/admin/src/App/Navigation.js b/admin/src/App/Navigation.js index e58fc72..8fee9c1 100644 --- a/admin/src/App/Navigation.js +++ b/admin/src/App/Navigation.js @@ -24,6 +24,10 @@ const Navigation = (props) => { {process.env.REACT_APP_SITE_NAME} + {admin && + + Active Users + } {admin && All Systems diff --git a/admin/src/features/api/apiSlice.js b/admin/src/features/api/apiSlice.js index 3af13dc..2edd0a0 100644 --- a/admin/src/features/api/apiSlice.js +++ b/admin/src/features/api/apiSlice.js @@ -140,6 +140,10 @@ export const apiSlice = createApi({ // The URL for the request is '/fakeApi/posts' query: () => ({ url: `/admin/users`, credentials: "include" }) }), + getActiveUsers: builder.query({ + // The URL for the request is '/fakeApi/posts' + query: () => ({ url: `/admin/users/active`, credentials: "include" }) + }), createSystem: builder.mutation({ query: (system) => ({ url: `/systems`, @@ -202,4 +206,4 @@ export const apiSlice = createApi({ }) // Export the auto-generated hook for the `getPosts` query endpoint -export const { useGetGroupsQuery, useGetSystemsQuery, useGetAllSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice +export const { useGetGroupsQuery, useGetSystemsQuery, useGetAllSystemsQuery, useGetActiveUsersQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice diff --git a/frontend/src/About/AboutComponent.js b/frontend/src/About/AboutComponent.js index 45db264..dbfa3d7 100644 --- a/frontend/src/About/AboutComponent.js +++ b/frontend/src/About/AboutComponent.js @@ -1,9 +1,27 @@ -import React from "react"; +import React, {useState} from "react"; import { Link } from 'react-router-dom' -import { Container, Header, Divider, Icon, Menu, Button } from "semantic-ui-react"; +import { Container, Header, Divider, Icon, Menu, Button, Accordion, } from "semantic-ui-react"; const AboutComponent = () => { + const [activeIndex, setActiveIndex] = useState(-1); + const handleClick = (e, titleProps) => { + const { index } = titleProps + const newIndex = activeIndex === index ? -1 : index + setActiveIndex(newIndex); + } + + const panels = [ + { + key: 'feeds', + title: {content: (Where do the different feeds come from?)}, + content: [ + 'It is a team effort! There are contributers from around country and even internationally, that are running Trunk Recorder to capture local radio systems and sending the transmissions to OpenMHz.', + ].join(' '), + } + ] + + return (
@@ -11,30 +29,32 @@ const AboutComponent = () => { Listen -
About
+
About

I wrote Trunk Recorder 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.

-

The latest version of this site makes it easy for other people running Trunk Recorder to share their recrodings. +

The latest version of this site makes it easy for other people running Trunk Recorder to share their recordings. 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.

+ lead to a greater appreciation for all the work they do, which goes largely unseen.

-

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.

+

The audio from each system is archived for 30 days, so you can go back and listen to events you may have missed.

- Luke luke@robotastic.com

-
Open Source
-

Have changes you would like to suggest? Want to run your own server for your community? The source code for OpenMHz has been released:

+
100% Open Source
+

Have changes you would like to suggest? Want to run your own server for your community? All of the code for OpenMHz is available as open source:

Trunk Server

Support

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

-

+
FAQ
+ +
);