diff --git a/admin/package.json b/admin/package.json index 17c4e63..6d3d461 100644 --- a/admin/package.json +++ b/admin/package.json @@ -23,7 +23,8 @@ "mongoose": "7.0.0", "multer": "1.4.5-lts.1", "passport": "0.6.0", - "passport-local": "1.0.0" + "passport-local": "1.0.0", + "redux-devtools-extension": "^2.13.9" }, "devDependencies": { "@reduxjs/toolkit": "^1.9.3", diff --git a/admin/server/controllers/systems.js b/admin/server/controllers/systems.js index 4e665ef..50047a9 100644 --- a/admin/server/controllers/systems.js +++ b/admin/server/controllers/systems.js @@ -84,86 +84,59 @@ exports.listSystems = async function (req, res, next) { return; }; -function remove_system(shortName) { - /* - var sysDir = config.mediaDirectory + "/" + shortName + "/" - console.log("Removing DIR: " + sysDir); - fs.remove(sysDir, function(err) { - if (err) return console.error(err) - }); +const remove_system = async (shortName) => { - Transmission.remove({ - shortName: shortName - }, function(err) { - if (err) return console.error(err); - // removed! - }); -*/ - System.remove({ + await System.deleteOne({ shortName: shortName - }, - function (err) { - if (err) return console.error(err); - // removed! - } - ); + }).catch( err => console.error(err)); - Talkgroup.remove({ + await Talkgroup.deleteMany({ shortName: shortName - }, - function (err) { - if (err) return console.error(err); - // removed! - } - ); + }).catch( err => console.error(err)); - Group.remove({ + + await Group.deleteMany({ shortName: shortName - }, - function (err) { - if (err) return console.error(err); - // removed! - } - ); + }).catch(err => console.error(err)); } -exports.deleteSystem = function (req, res, next) { +exports.deleteSystem = async function (req, res, next) { //router.get('/delete_system/:shortName', isLoggedIn, function(req, res) { - System.findOne({ + var system = await System.findOne({ shortName: req.params.shortName.toLowerCase() - }, - function (err, system) { - if (err) { - res.json({ - success: false, - message: err - }); - return; - } - if (!system) { - res.json({ - success: false, - message: "That Short Name does not exist." - }); - return; - } - if (!system.userId.equals(req.user._id)) { - console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id)); - res.json({ - success: false, - message: "You are not the user associated with this system." - }); - return; - } - console.log("Removing: " + system.name); - remove_system(system.shortName); - res.json({ - success: true - }); - return; - } - ); -}; + }).catch(err => { + res.status(500); + res.json({ + success: false, + message: err + }); + return; + }); + if (!system) { + res.status(500); + res.json({ + success: false, + message: "That Short Name does not exist." + }); + return; + } + if (!system.userId.equals(req.user._id)) { + console.log("Error deleting syste - Wrong User - sys: " + system.userId + " profile: " + mongoose.Types.ObjectId(req.user._id)); + res.status(401); + res.json({ + success: false, + message: "You are not the user associated with this system." + }); + return; + } + console.log("Removing: " + system.name); + remove_system(system.shortName); + res.json({ + success: true + }); + return; +} + // ------------------------------------------- exports.ownSystem = async function (req, res, next) { @@ -245,7 +218,7 @@ exports.uniqueShortName = async function (req, res, next) { } catch (err) { console.error("ERROR: Unique ShortName - caught: " + err); res.status(500); - res.json({ message: "ERROR: Unique ShortName - caught: " + err}); + res.json({ message: "ERROR: Unique ShortName - caught: " + err }); return; } @@ -267,45 +240,45 @@ exports.updateSystem = async function (req, res, next) { res.locals.system.ignoreUnknownTalkgroup = res.locals.ignoreUnknownTalkgroup; - await res.locals.system.save().catch( err => { - res.status(500); - res.json({ - success: false, - message: err - }); - return; + await res.locals.system.save().catch(err => { + res.status(500); + res.json({ + success: false, + message: err }); - var returnSys = (({ - name, - shortName, - description, - systemType, - city, - state, - county, - country, - userId, - key, - showScreenName, - ignoreUnknownTalkgroup - }) => ({ - name, - shortName, - description, - systemType, - city, - state, - county, - country, - userId, - key, - showScreenName, - ignoreUnknownTalkgroup - }))(res.locals.system); - returnSys.id = res.locals.system._id; - res.json(returnSys); - return; - } + return; + }); + var returnSys = (({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + key, + showScreenName, + ignoreUnknownTalkgroup + }) => ({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + key, + showScreenName, + ignoreUnknownTalkgroup + }))(res.locals.system); + returnSys.id = res.locals.system._id; + res.json(returnSys); + return; +} @@ -466,46 +439,46 @@ exports.createSystem = async function (req, res, next) { }))(res.locals); system.key = key; system.userId = new mongoose.Types.ObjectId(req.user._id); - const newSys = await System.create(system).catch( err => { + const newSys = await System.create(system).catch(err => { - console.error(err); - res.status(500) - res.json({ - success: false, - message: err - }); - return; + console.error(err); + res.status(500) + res.json({ + success: false, + message: err + }); + return; }); - var returnSys = (({ - name, - shortName, - description, - systemType, - city, - state, - county, - country, - userId, - showScreenName, - ignoreUnknownTalkgroup, - key - }) => ({ - name, - shortName, - description, - systemType, - city, - state, - county, - country, - userId, - showScreenName, - ignoreUnknownTalkgroup, - key - }))(newSys); - returnSys.id = newSys._id; - res.json( returnSys); - return; + var returnSys = (({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + showScreenName, + ignoreUnknownTalkgroup, + key + }) => ({ + name, + shortName, + description, + systemType, + city, + state, + county, + country, + userId, + showScreenName, + ignoreUnknownTalkgroup, + key + }))(newSys); + returnSys.id = newSys._id; + res.json(returnSys); + return; } \ No newline at end of file diff --git a/admin/src/Group/ListGroups.js b/admin/src/Group/ListGroups.js index 3d2f94c..4be34f7 100644 --- a/admin/src/Group/ListGroups.js +++ b/admin/src/Group/ListGroups.js @@ -26,8 +26,15 @@ const ListGroups = (props) => { //https://stackoverflow.com/questions/42597602/react-onclick-pass-event-with-parameter const groups = props.groups; + let groupsDisplay = []; if (groups) { - let groupsDisplay = props.order.map( id => props.groups.find( group => group._id == id )); + // if a group gets deleted, it will still be listed in the Order array for a little. + for (const id of props.order) { + const group = props.groups.find( group => group._id == id ); + if (group) { + groupsDisplay.push(group) + } + } return ( @@ -40,6 +47,7 @@ const ListGroups = (props) => { {groupsDisplay.map((group, i) => ( + {group.groupName} {group.talkgroups.length} @@ -65,7 +73,9 @@ const ListGroups = (props) => { /> - ))} + + ) + )}
diff --git a/admin/src/System/System.js b/admin/src/System/System.js index 004a93d..d986ca4 100644 --- a/admin/src/System/System.js +++ b/admin/src/System/System.js @@ -8,7 +8,7 @@ import UpdatePermissionModal from "../Permission/UpdatePermissionModalContainer. import AddPermissionModal from "../Permission/AddPermissionModalContainer.js"; import ErrorChart from "./ResponsiveErrorChart" import CallChart from "./ResponsiveCallChart" -import { useGetSystemsQuery, useGetTalkgroupsQuery, useGetGroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useImportTalkgroupsMutation, useSaveGroupOrderMutation } from '../features/api/apiSlice' +import { useGetSystemsQuery, useGetTalkgroupsQuery, useGetGroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useDeleteSystemMutation, useImportTalkgroupsMutation, useSaveGroupOrderMutation } from '../features/api/apiSlice' import { Button, Confirm, @@ -27,13 +27,15 @@ import { const System = (props) => { const { shortName } = useParams(); + const navigate = useNavigate(); const { data: systemsData, isSuccess: isSystemsSuccess } = useGetSystemsQuery(); const { data: talkgroupsData, isSuccess: isTalkgroupsSuccess } = useGetTalkgroupsQuery(shortName); const { data: groupsData, isSuccess: isGroupsSuccess } = useGetGroupsQuery(shortName); const { data: errorsData, isSuccess: isErrorsSuccess } = useGetErrorsQuery(shortName); const [deleteGroupAPI, { isLoading: isDeleting }] = useDeleteGroupMutation(); const [reorderGroupsAPI, { isLoading: isReordering }] = useSaveGroupOrderMutation(); - const [importTalkgroupsAPI, { isLoading: isImporting}] = useImportTalkgroupsMutation(); + const [importTalkgroupsAPI, { isLoading: isImporting }] = useImportTalkgroupsMutation(); + const [deleteSystemAPI ] = useDeleteSystemMutation(); const [openSystemDeleteConfirm, setOpenSystemDeleteConfirm] = useState(false); const [openPermissionDeleteConfirm, setOpenPermissionDeleteConfirm] = useState(false); const [openMessage, setOpenMessage] = useState(false); @@ -98,32 +100,34 @@ const System = (props) => { var minValue = 0; var maxValue = 0; var MS_PER_MINUTE = 60000; - for (var j = 0; j < statistic.callTotals.length; j++) { - var spotsBack = statistic.callTotals.length - j; - var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); - if (time < minDate) minDate = time; - if (statistic.callTotals[j] > maxValue) - maxValue = statistic.callTotals[j]; - callTotals.push({ y: statistic.callTotals[j], x: time }); - } - for (var j = 0; j < statistic.errorTotals.length; j++) { - var spotsBack = statistic.errorTotals.length - j; - var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); - if (time < minDate) minDate = time; - if (statistic.errorTotals[j] > maxValue) - maxValue = statistic.errorTotals[j]; - errorTotals.push({ x: time, y: statistic.errorTotals[j] }); - } - const callData = { - minDate: minDate, - maxDate: maxDate, - minValue: minValue, - maxValue: maxValue, - callTotals: callTotals, - errorTotals: errorTotals - }; + if (statistic) { + for (var j = 0; j < statistic.callTotals.length; j++) { + var spotsBack = statistic.callTotals.length - j; + var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); + if (time < minDate) minDate = time; + if (statistic.callTotals[j] > maxValue) + maxValue = statistic.callTotals[j]; + callTotals.push({ y: statistic.callTotals[j], x: time }); + } + for (var j = 0; j < statistic.errorTotals.length; j++) { + var spotsBack = statistic.errorTotals.length - j; + var time = new Date(now - spotsBack * 15 * MS_PER_MINUTE); + if (time < minDate) minDate = time; + if (statistic.errorTotals[j] > maxValue) + maxValue = statistic.errorTotals[j]; + errorTotals.push({ x: time, y: statistic.errorTotals[j] }); + } + const callData = { + minDate: minDate, + maxDate: maxDate, + minValue: minValue, + maxValue: maxValue, + callTotals: callTotals, + errorTotals: errorTotals + }; - setCallData(callData); + setCallData(callData); + } } @@ -167,7 +171,7 @@ const System = (props) => { } } - const reorderGroup = (oldIndex, newIndex) => { + const reorderGroup = (oldIndex, newIndex) => { if ((newIndex < 0) || (newIndex > (groupOrder.length - 1))) { return } @@ -187,28 +191,28 @@ const System = (props) => { } const saveGroupOrder = async () => { - await reorderGroupsAPI({shortName: shortName, order: {groupOrder: JSON.stringify(groupOrder)}}); + await reorderGroupsAPI({ shortName: shortName, order: { groupOrder: JSON.stringify(groupOrder) } }); } const handleUpload = async (file) => { const formData = new FormData(); - formData.append('file',file) - await importTalkgroupsAPI({shortName:shortName, file:formData}); + formData.append('file', file) + await importTalkgroupsAPI({ shortName: shortName, file: formData }); } - const handleSystemDeleteConfirm = () => { + const handleSystemDeleteConfirm = async () => { setOpenSystemDeleteConfirm(false); - /* - this.props.systemActions - .deleteSystem(this.props.system.shortName) - .then(requestMessage => { - if (requestMessage) { - // report to the user is there was a problem during login - this.setState({ requestMessage: requestMessage, openMessage: true }); - } - });*/ + try { + const originalPromiseResult = await deleteSystemAPI(shortName).unwrap(); + navigate("/list-systems") + } catch (error) { + const message = error.data.message; + console.log(message); + setRequestMessage(message); + } + }; const handleSystemDeleteCancel = () => setOpenSystemDeleteConfirm(false); @@ -218,7 +222,7 @@ const System = (props) => { system = systemsData.systems.find(sys => sys.shortName == shortName); } - useEffect(() => { + useLayoutEffect(() => { if (groupsData) { let newOrder = []; groupsData.forEach(group => { diff --git a/admin/src/features/api/apiSlice.js b/admin/src/features/api/apiSlice.js index 83a5542..3c38fc4 100644 --- a/admin/src/features/api/apiSlice.js +++ b/admin/src/features/api/apiSlice.js @@ -90,7 +90,17 @@ export const apiSlice = createApi({ credentials: "include" }), - invalidatesTags: (result, error, group) => [{ type: 'Group', id: group._id }], + async onQueryStarted(group, { dispatch, queryFulfilled }) { + try { + await queryFulfilled + const patchResult = dispatch( + apiSlice.util.updateQueryData('getGroups', group.shortName, (groups) => { + const groupIndex = groups.findIndex((obj => obj.groupId == group.groupId)); + groups.splice(groupIndex,1); + }) + ) + } catch { } + }, }), saveGroupOrder: builder.mutation({ query: ({shortName, order}) => ({ @@ -138,7 +148,8 @@ export const apiSlice = createApi({ try { const { data: newSystem } = await queryFulfilled const patchResult = dispatch( - apiSlice.util.updateQueryData('getSystems', system.shortName, (systems) => { + apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => { + const systems = systemsData.systems; systems.push(newSystem); }) ) @@ -156,7 +167,8 @@ export const apiSlice = createApi({ try { const { data: updatedSystem } = await queryFulfilled const patchResult = dispatch( - apiSlice.util.updateQueryData('getSystems', system.shortName, (systems) => { + apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => { + const systems = systemsData.systems; const systemIndex = systems.findIndex((obj => obj.shortName == updatedSystem.shortName)); systems[systemIndex] = updatedSystem; }) @@ -164,8 +176,27 @@ export const apiSlice = createApi({ } catch {} }, }), + deleteSystem: builder.mutation({ + query: (shortName) => ({ + url: `/systems/${shortName}`, + method: 'DELETE', + credentials: "include" + }), + async onQueryStarted(shortName, { dispatch, queryFulfilled }) { + try { + await queryFulfilled + const patchResult = dispatch( + apiSlice.util.updateQueryData('getSystems', undefined, (systemsData) => { + const systems = systemsData.systems; + const systemIndex = systems.findIndex((obj => obj.shortName == shortName)); + systems.splice(systemIndex, 1); + }) + ) + } catch {} + }, + }), }) }) // Export the auto-generated hook for the `getPosts` query endpoint -export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice +export const { useGetGroupsQuery, useGetSystemsQuery, useGetTalkgroupsQuery, useGetErrorsQuery, useDeleteGroupMutation, useCreateGroupMutation, useCreateSystemMutation, useUpdateSystemMutation, useDeleteSystemMutation, useSaveGroupOrderMutation, useUpdateGroupMutation, useImportTalkgroupsMutation } = apiSlice diff --git a/admin/src/redux-router/configureStore.js b/admin/src/redux-router/configureStore.js index 5313985..57ce03e 100644 --- a/admin/src/redux-router/configureStore.js +++ b/admin/src/redux-router/configureStore.js @@ -7,14 +7,16 @@ import { createRouterMiddleware } from '@lagunovsky/redux-react-router' import createRootReducer from './reducers' import thunk from 'redux-thunk'; import { apiSlice } from '../features/api/apiSlice' +import { composeWithDevTools } from 'redux-devtools-extension' +//const composedEnhancers = composeWithDevTools(...enhancers) export const history = createBrowserHistory() export default function configureStore(preloadedState) { const store = createStore( createRootReducer(history), // root reducer with router state preloadedState, - compose( + composeWithDevTools( applyMiddleware( createRouterMiddleware(history), // for dispatching history actions thunk, diff --git a/admin/yarn.lock b/admin/yarn.lock index 8d4e193..af82f12 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -8392,6 +8392,11 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^1.0.0" +redux-devtools-extension@^2.13.9: + version "2.13.9" + resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7" + integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A== + redux-thunk@2.4.2, redux-thunk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz"