aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-05-21 09:49:26 -0700
committerAnton Tananaev <anton@traccar.org>2022-05-21 09:49:26 -0700
commit7e96816f94314dcdf071eeee6e74a95bcace329f (patch)
tree21938fcdbe6e8b7a651308555af77c49dc59e1c2
parent79dd42f0bdeef6d9f6331c0ec8301b2631a9cb90 (diff)
downloadtrackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.tar.gz
trackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.tar.bz2
trackermap-web-7e96816f94314dcdf071eeee6e74a95bcace329f.zip
Implement API error handling
-rw-r--r--modern/src/App.js4
-rw-r--r--modern/src/CachingController.js8
-rw-r--r--modern/src/SocketController.js8
-rw-r--r--modern/src/common/components/ErrorHandler.js26
-rw-r--r--modern/src/common/components/LinkField.js4
-rw-r--r--modern/src/common/components/PositionValue.js7
-rw-r--r--modern/src/common/components/RemoveDialog.js7
-rw-r--r--modern/src/common/components/SelectField.js2
-rw-r--r--modern/src/login/LoginPage.js23
-rw-r--r--modern/src/login/RegisterPage.js8
-rw-r--r--modern/src/login/ResetPasswordPage.js8
-rw-r--r--modern/src/main/DevicesList.js2
-rw-r--r--modern/src/map/GeofenceEditMap.js69
-rw-r--r--modern/src/other/EventPage.js4
-rw-r--r--modern/src/other/PositionPage.js2
-rw-r--r--modern/src/other/ReplayPage.js7
-rw-r--r--modern/src/reactHelper.js23
-rw-r--r--modern/src/reports/ChartReportPage.js7
-rw-r--r--modern/src/reports/EventReportPage.js7
-rw-r--r--modern/src/reports/RouteReportPage.js7
-rw-r--r--modern/src/reports/StatisticsPage.js7
-rw-r--r--modern/src/reports/StopReportPage.js7
-rw-r--r--modern/src/reports/SummaryReportPage.js7
-rw-r--r--modern/src/reports/TripReportPage.js7
-rw-r--r--modern/src/settings/AccumulatorsPage.js7
-rw-r--r--modern/src/settings/CalendarsPage.js2
-rw-r--r--modern/src/settings/CommandSendPage.js9
-rw-r--r--modern/src/settings/CommandsPage.js2
-rw-r--r--modern/src/settings/ComputedAttributesPage.js2
-rw-r--r--modern/src/settings/DriversPage.js2
-rw-r--r--modern/src/settings/GroupsPage.js2
-rw-r--r--modern/src/settings/MaintenancesPage.js2
-rw-r--r--modern/src/settings/NotificationsPage.js2
-rw-r--r--modern/src/settings/ServerPage.js7
-rw-r--r--modern/src/settings/UsersPage.js2
-rw-r--r--modern/src/settings/components/EditItemView.js10
-rw-r--r--modern/src/store/errors.js21
-rw-r--r--modern/src/store/index.js3
38 files changed, 258 insertions, 76 deletions
diff --git a/modern/src/App.js b/modern/src/App.js
index 0afefce4..fee94e5b 100644
--- a/modern/src/App.js
+++ b/modern/src/App.js
@@ -50,6 +50,7 @@ import PreferencesPage from './settings/PreferencesPage';
import BottomMenu from './common/components/BottomMenu';
import AccumulatorsPage from './settings/AccumulatorsPage';
import CommandSendPage from './settings/CommandSendPage';
+import ErrorHandler from './common/components/ErrorHandler';
const useStyles = makeStyles(() => ({
page: {
@@ -86,6 +87,8 @@ const App = () => {
if (items.length > 0) {
dispatch(devicesActions.select(items[0].id));
}
+ } else {
+ throw Error(await response.text());
}
history.push('/');
} else if (query.get('eventId')) {
@@ -158,6 +161,7 @@ const App = () => {
)}
</Route>
</Switch>
+ <ErrorHandler />
</ThemeProvider>
));
};
diff --git a/modern/src/CachingController.js b/modern/src/CachingController.js
index d6e20060..1c8fb43a 100644
--- a/modern/src/CachingController.js
+++ b/modern/src/CachingController.js
@@ -14,6 +14,8 @@ const CachingController = () => {
const response = await fetch('/api/geofences');
if (response.ok) {
dispatch(geofencesActions.update(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}
}, [authenticated]);
@@ -23,6 +25,8 @@ const CachingController = () => {
const response = await fetch('/api/groups');
if (response.ok) {
dispatch(groupsActions.update(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}
}, [authenticated]);
@@ -32,6 +36,8 @@ const CachingController = () => {
const response = await fetch('/api/drivers');
if (response.ok) {
dispatch(driversActions.update(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}
}, [authenticated]);
@@ -41,6 +47,8 @@ const CachingController = () => {
const response = await fetch('/api/maintenance');
if (response.ok) {
dispatch(maintenancesActions.update(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}
}, [authenticated]);
diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js
index cf01ff7f..9826d4b1 100644
--- a/modern/src/SocketController.js
+++ b/modern/src/SocketController.js
@@ -49,6 +49,8 @@ const SocketController = () => {
const response = await fetch('/api/server');
if (response.ok) {
dispatch(sessionActions.updateServer(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}, []);
@@ -57,6 +59,8 @@ const SocketController = () => {
const response = await fetch('/api/devices');
if (response.ok) {
dispatch(devicesActions.refresh(await response.json()));
+ } else {
+ throw Error(await response.text());
}
connectSocket();
return () => {
@@ -88,10 +92,6 @@ const SocketController = () => {
{notifications.map((notification) => (
<Snackbar
key={notification.id}
- anchorOrigin={{
- vertical: 'bottom',
- horizontal: 'right',
- }}
open={notification.show}
message={notification.message}
autoHideDuration={snackBarDurationLongMs}
diff --git a/modern/src/common/components/ErrorHandler.js b/modern/src/common/components/ErrorHandler.js
new file mode 100644
index 00000000..534dd012
--- /dev/null
+++ b/modern/src/common/components/ErrorHandler.js
@@ -0,0 +1,26 @@
+import { Snackbar } from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
+import React from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { usePrevious } from '../../reactHelper';
+import { errorsActions } from '../../store';
+
+const ErrorHandler = () => {
+ const dispatch = useDispatch();
+
+ const error = useSelector((state) => state.errors.errors.find(() => true));
+ const previousError = usePrevious(error);
+
+ return (
+ <Snackbar open={!!error}>
+ <Alert
+ onClose={() => dispatch(errorsActions.pop())}
+ severity="error"
+ >
+ {error || previousError}
+ </Alert>
+ </Snackbar>
+ );
+};
+
+export default ErrorHandler;
diff --git a/modern/src/common/components/LinkField.js b/modern/src/common/components/LinkField.js
index e11438df..0f6cc7ba 100644
--- a/modern/src/common/components/LinkField.js
+++ b/modern/src/common/components/LinkField.js
@@ -23,6 +23,8 @@ const LinkField = ({
const response = await fetch(endpointAll);
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, []);
@@ -31,6 +33,8 @@ const LinkField = ({
if (response.ok) {
const data = await response.json();
setLinked(data.map((it) => it.id));
+ } else {
+ throw Error(await response.text());
}
}, []);
diff --git a/modern/src/common/components/PositionValue.js b/modern/src/common/components/PositionValue.js
index 9f679605..1162a150 100644
--- a/modern/src/common/components/PositionValue.js
+++ b/modern/src/common/components/PositionValue.js
@@ -7,6 +7,7 @@ import {
import { useAttributePreference, usePreference } from '../util/preferences';
import { useTranslation } from './LocalizationProvider';
import { useAdministrator } from '../util/permissions';
+import { useCatch } from '../../reactHelper';
const PositionValue = ({ position, property, attribute }) => {
const t = useTranslation();
@@ -26,7 +27,7 @@ const PositionValue = ({ position, property, attribute }) => {
setAddress(position.address);
}, [position]);
- const showAddress = async () => {
+ const showAddress = useCatch(async () => {
const query = new URLSearchParams({
latitude: position.latitude,
longitude: position.longitude,
@@ -34,8 +35,10 @@ const PositionValue = ({ position, property, attribute }) => {
const response = await fetch(`/api/server/geocode?${query.toString()}`);
if (response.ok) {
setAddress(await response.text());
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const formatValue = () => {
switch (key) {
diff --git a/modern/src/common/components/RemoveDialog.js b/modern/src/common/components/RemoveDialog.js
index 6d191d6a..cbdbb05d 100644
--- a/modern/src/common/components/RemoveDialog.js
+++ b/modern/src/common/components/RemoveDialog.js
@@ -5,18 +5,21 @@ import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import { useTranslation } from './LocalizationProvider';
+import { useCatch } from '../../reactHelper';
const RemoveDialog = ({
open, endpoint, itemId, onResult,
}) => {
const t = useTranslation();
- const handleRemove = async () => {
+ const handleRemove = useCatch(async () => {
const response = await fetch(`/api/${endpoint}/${itemId}`, { method: 'DELETE' });
if (response.ok) {
onResult(true);
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<Dialog
diff --git a/modern/src/common/components/SelectField.js b/modern/src/common/components/SelectField.js
index 98473b16..5a9a176b 100644
--- a/modern/src/common/components/SelectField.js
+++ b/modern/src/common/components/SelectField.js
@@ -25,6 +25,8 @@ const SelectField = ({
const response = await fetch(endpoint);
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}
}, []);
diff --git a/modern/src/login/LoginPage.js b/modern/src/login/LoginPage.js
index ae7b982d..5b690cdc 100644
--- a/modern/src/login/LoginPage.js
+++ b/modern/src/login/LoginPage.js
@@ -50,15 +50,19 @@ const LoginPage = () => {
const handleSubmit = async (event) => {
event.preventDefault();
- const response = await fetch('/api/session', {
- method: 'POST',
- body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`),
- });
- if (response.ok) {
- const user = await response.json();
- dispatch(sessionActions.updateUser(user));
- history.push('/');
- } else {
+ try {
+ const response = await fetch('/api/session', {
+ method: 'POST',
+ body: new URLSearchParams(`email=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`),
+ });
+ if (response.ok) {
+ const user = await response.json();
+ dispatch(sessionActions.updateUser(user));
+ history.push('/');
+ } else {
+ throw Error(await response.text());
+ }
+ } catch (error) {
setFailed(true);
setPassword('');
}
@@ -153,7 +157,6 @@ const LoginPage = () => {
</Grid>
)}
<Snackbar
- anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={!!announcement && !announcementShown}
message={announcement}
action={(
diff --git a/modern/src/login/RegisterPage.js b/modern/src/login/RegisterPage.js
index cd6fc381..78728b58 100644
--- a/modern/src/login/RegisterPage.js
+++ b/modern/src/login/RegisterPage.js
@@ -7,6 +7,7 @@ import ArrowBackIcon from '@material-ui/icons/ArrowBack';
import LoginLayout from './LoginLayout';
import { useTranslation } from '../common/components/LocalizationProvider';
import { snackBarDurationShortMs } from '../common/util/duration';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
title: {
@@ -33,7 +34,7 @@ const RegisterPage = () => {
const [password, setPassword] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
- const handleSubmit = async () => {
+ const handleSubmit = useCatch(async () => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -41,13 +42,14 @@ const RegisterPage = () => {
});
if (response.ok) {
setSnackbarOpen(true);
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<LoginLayout>
<Snackbar
- anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={snackbarOpen}
onClose={() => history.push('/login')}
autoHideDuration={snackBarDurationShortMs}
diff --git a/modern/src/login/ResetPasswordPage.js b/modern/src/login/ResetPasswordPage.js
index 6f7e784f..93e154e3 100644
--- a/modern/src/login/ResetPasswordPage.js
+++ b/modern/src/login/ResetPasswordPage.js
@@ -8,6 +8,7 @@ import LoginLayout from './LoginLayout';
import { useTranslation } from '../common/components/LocalizationProvider';
import useQuery from '../common/util/useQuery';
import { snackBarDurationShortMs } from '../common/util/duration';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
title: {
@@ -36,7 +37,7 @@ const ResetPasswordPage = () => {
const [password, setPassword] = useState('');
const [snackbarOpen, setSnackbarOpen] = useState(false);
- const handleSubmit = async (event) => {
+ const handleSubmit = useCatch(async (event) => {
event.preventDefault();
let response;
if (!token) {
@@ -52,13 +53,14 @@ const ResetPasswordPage = () => {
}
if (response.ok) {
setSnackbarOpen(true);
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<LoginLayout>
<Snackbar
- anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={snackbarOpen}
onClose={() => history.push('/login')}
autoHideDuration={snackBarDurationShortMs}
diff --git a/modern/src/main/DevicesList.js b/modern/src/main/DevicesList.js
index 4da93547..6ff08c6a 100644
--- a/modern/src/main/DevicesList.js
+++ b/modern/src/main/DevicesList.js
@@ -164,6 +164,8 @@ const DeviceView = ({ updateTimestamp, onMenuClick, filter }) => {
const response = await fetch('/api/devices');
if (response.ok) {
dispatch(devicesActions.refresh(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/map/GeofenceEditMap.js b/modern/src/map/GeofenceEditMap.js
index 2eb86321..0d7950a2 100644
--- a/modern/src/map/GeofenceEditMap.js
+++ b/modern/src/map/GeofenceEditMap.js
@@ -2,13 +2,14 @@ import 'mapbox-gl/dist/mapbox-gl.css';
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
import MapboxDraw from '@mapbox/mapbox-gl-draw';
import theme from '@mapbox/mapbox-gl-draw/src/lib/theme';
-import { useCallback, useEffect } from 'react';
+import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { map } from './core/Map';
import { geofenceToFeature, geometryToArea } from './core/mapUtil';
-import { geofencesActions } from '../store';
+import { errorsActions, geofencesActions } from '../store';
+import { useCatchCallback } from '../reactHelper';
const draw = new MapboxDraw({
displayControlsDefault: false,
@@ -39,10 +40,12 @@ const GeofenceEditMap = () => {
const geofences = useSelector((state) => state.geofences.items);
- const refreshGeofences = useCallback(async () => {
+ const refreshGeofences = useCatchCallback(async () => {
const response = await fetch('/api/geofences');
if (response.ok) {
dispatch(geofencesActions.refresh(await response.json()));
+ } else {
+ throw Error(await response.text());
}
}, [dispatch]);
@@ -58,33 +61,45 @@ const GeofenceEditMap = () => {
const feature = event.features[0];
const newItem = { name: '', area: geometryToArea(feature.geometry) };
draw.delete(feature.id);
- const response = await fetch('/api/geofences', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(newItem),
- });
- if (response.ok) {
- const item = await response.json();
- history.push(`/settings/geofence/${item.id}`);
+ try {
+ const response = await fetch('/api/geofences', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(newItem),
+ });
+ if (response.ok) {
+ const item = await response.json();
+ history.push(`/settings/geofence/${item.id}`);
+ } else {
+ throw Error(await response.text());
+ }
+ } catch (error) {
+ dispatch(errorsActions.push(error.message));
}
};
map.on('draw.create', listener);
return () => map.off('draw.create', listener);
- }, [history]);
+ }, [dispatch, history]);
useEffect(() => {
const listener = async (event) => {
const feature = event.features[0];
- const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' });
- if (response.ok) {
- refreshGeofences();
+ try {
+ const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' });
+ if (response.ok) {
+ refreshGeofences();
+ } else {
+ throw Error(await response.text());
+ }
+ } catch (error) {
+ dispatch(errorsActions.push(error.message));
}
};
map.on('draw.delete', listener);
return () => map.off('draw.delete', listener);
- }, [refreshGeofences]);
+ }, [dispatch, refreshGeofences]);
useEffect(() => {
const listener = async (event) => {
@@ -92,20 +107,26 @@ const GeofenceEditMap = () => {
const item = Object.values(geofences).find((i) => i.id === feature.id);
if (item) {
const updatedItem = { ...item, area: geometryToArea(feature.geometry) };
- const response = await fetch(`/api/geofences/${feature.id}`, {
- method: 'PUT',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(updatedItem),
- });
- if (response.ok) {
- refreshGeofences();
+ try {
+ const response = await fetch(`/api/geofences/${feature.id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(updatedItem),
+ });
+ if (response.ok) {
+ refreshGeofences();
+ } else {
+ throw Error(await response.text());
+ }
+ } catch (error) {
+ dispatch(errorsActions.push(error.message));
}
}
};
map.on('draw.update', listener);
return () => map.off('draw.update', listener);
- }, [geofences, refreshGeofences]);
+ }, [dispatch, geofences, refreshGeofences]);
useEffect(() => {
draw.deleteAll();
diff --git a/modern/src/other/EventPage.js b/modern/src/other/EventPage.js
index 46f5e67c..7bcf90a8 100644
--- a/modern/src/other/EventPage.js
+++ b/modern/src/other/EventPage.js
@@ -37,6 +37,8 @@ const EventPage = () => {
const response = await fetch(`/api/events/${id}`);
if (response.ok) {
setEvent(await response.json());
+ } else {
+ throw Error(await response.text());
}
}
}, [id]);
@@ -49,6 +51,8 @@ const EventPage = () => {
if (positions.length > 0) {
setPosition(positions[0]);
}
+ } else {
+ throw Error(await response.text());
}
}
}, [event]);
diff --git a/modern/src/other/PositionPage.js b/modern/src/other/PositionPage.js
index 4c13955a..76bb560f 100644
--- a/modern/src/other/PositionPage.js
+++ b/modern/src/other/PositionPage.js
@@ -41,6 +41,8 @@ const PositionPage = () => {
if (positions.length > 0) {
setItem(positions[0]);
}
+ } else {
+ throw Error(await response.text());
}
} else {
setItem({});
diff --git a/modern/src/other/ReplayPage.js b/modern/src/other/ReplayPage.js
index 9bdf87be..c1c56ac1 100644
--- a/modern/src/other/ReplayPage.js
+++ b/modern/src/other/ReplayPage.js
@@ -18,6 +18,7 @@ import PositionsMap from '../map/PositionsMap';
import { formatTime } from '../common/util/formatter';
import ReportFilter from '../reports/components/ReportFilter';
import { useTranslation } from '../common/components/LocalizationProvider';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
root: {
@@ -121,7 +122,7 @@ const ReplayPage = () => {
}
}, [index, positions]);
- const handleSubmit = async (deviceId, from, to, _, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, _, headers) => {
setSelectedDeviceId(deviceId);
const query = new URLSearchParams({ deviceId, from, to });
const response = await fetch(`/api/positions?${query.toString()}`, { headers });
@@ -129,8 +130,10 @@ const ReplayPage = () => {
setIndex(0);
setPositions(await response.json());
setExpanded(false);
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<div className={classes.root}>
diff --git a/modern/src/reactHelper.js b/modern/src/reactHelper.js
index 124bb40a..c67f252b 100644
--- a/modern/src/reactHelper.js
+++ b/modern/src/reactHelper.js
@@ -1,4 +1,6 @@
-import { useRef, useEffect } from 'react';
+import { useRef, useEffect, useCallback } from 'react';
+import { useDispatch } from 'react-redux';
+import { errorsActions } from './store';
export const usePrevious = (value) => {
const ref = useRef();
@@ -10,14 +12,29 @@ export const usePrevious = (value) => {
/* eslint-disable */
export const useEffectAsync = (effect, deps) => {
+ const dispatch = useDispatch();
const ref = useRef();
useEffect(() => {
- effect().then((result) => ref.current = result);
+ effect()
+ .then((result) => ref.current = result)
+ .catch((error) => dispatch(errorsActions.push(error.message)));
+
return () => {
const result = ref.current;
if (result) {
result();
}
};
- }, deps);
+ }, [...deps, dispatch]);
+};
+
+export const useCatch = (method) => {
+ const dispatch = useDispatch();
+ return (...parameters) => {
+ method(...parameters).catch((error) => dispatch(errorsActions.push(error.message)));
+ };
+};
+
+export const useCatchCallback = (method, deps) => {
+ return useCallback(useCatch(method), deps);
};
diff --git a/modern/src/reports/ChartReportPage.js b/modern/src/reports/ChartReportPage.js
index dbc205fc..bb71fa95 100644
--- a/modern/src/reports/ChartReportPage.js
+++ b/modern/src/reports/ChartReportPage.js
@@ -11,6 +11,7 @@ import { useTranslation } from '../common/components/LocalizationProvider';
import PageLayout from '../common/components/PageLayout';
import ReportsMenu from './components/ReportsMenu';
import usePositionAttributes from '../common/attributes/usePositionAttributes';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles(() => ({
chart: {
@@ -35,7 +36,7 @@ const ChartReportPage = () => {
return result;
})();
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, mail,
});
@@ -48,8 +49,10 @@ const ChartReportPage = () => {
fixTime: formatDate(position.fixTime, 'HH:mm:ss'),
}));
setItems(formattedPositions);
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportChart']}>
diff --git a/modern/src/reports/EventReportPage.js b/modern/src/reports/EventReportPage.js
index 076979ba..f1b0c6bd 100644
--- a/modern/src/reports/EventReportPage.js
+++ b/modern/src/reports/EventReportPage.js
@@ -11,6 +11,7 @@ import PageLayout from '../common/components/PageLayout';
import ReportsMenu from './components/ReportsMenu';
import usePersistedState from '../common/util/usePersistedState';
import ColumnSelect from './components/ColumnSelect';
+import { useCatch } from '../reactHelper';
const typesArray = [
['allEvents', 'eventAll'],
@@ -53,7 +54,7 @@ const EventReportPage = () => {
const [eventTypes, setEventTypes] = useState(['allEvents']);
const [items, setItems] = useState([]);
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, mail,
});
@@ -68,8 +69,10 @@ const EventReportPage = () => {
window.location.assign(window.URL.createObjectURL(await response.blob()));
}
}
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const formatValue = (item, key) => {
switch (key) {
diff --git a/modern/src/reports/RouteReportPage.js b/modern/src/reports/RouteReportPage.js
index cd2c3694..118c7758 100644
--- a/modern/src/reports/RouteReportPage.js
+++ b/modern/src/reports/RouteReportPage.js
@@ -10,6 +10,7 @@ import usePersistedState from '../common/util/usePersistedState';
import PositionValue from '../common/components/PositionValue';
import ColumnSelect from './components/ColumnSelect';
import usePositionAttributes from '../common/attributes/usePositionAttributes';
+import { useCatch } from '../reactHelper';
const RouteReportPage = () => {
const t = useTranslation();
@@ -19,7 +20,7 @@ const RouteReportPage = () => {
const [columns, setColumns] = usePersistedState('routeColumns', ['fixTime', 'latitude', 'longitude', 'speed', 'address']);
const [items, setItems] = useState([]);
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, mail,
});
@@ -33,8 +34,10 @@ const RouteReportPage = () => {
window.location.assign(window.URL.createObjectURL(await response.blob()));
}
}
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'reportRoute']}>
diff --git a/modern/src/reports/StatisticsPage.js b/modern/src/reports/StatisticsPage.js
index 214773fe..b1652468 100644
--- a/modern/src/reports/StatisticsPage.js
+++ b/modern/src/reports/StatisticsPage.js
@@ -9,6 +9,7 @@ import ReportsMenu from './components/ReportsMenu';
import ReportFilter from './components/ReportFilter';
import usePersistedState from '../common/util/usePersistedState';
import ColumnSelect from './components/ColumnSelect';
+import { useCatch } from '../reactHelper';
const columnsArray = [
['captureTime', 'statisticsCaptureTime'],
@@ -30,13 +31,15 @@ const StatisticsPage = () => {
const [columns, setColumns] = usePersistedState('statisticsColumns', ['captureTime', 'activeUsers', 'activeDevices', 'messagesStored']);
const [items, setItems] = useState([]);
- const handleSubmit = async (_, from, to) => {
+ const handleSubmit = useCatch(async (_, from, to) => {
const query = new URLSearchParams({ from, to });
const response = await fetch(`/api/statistics?${query.toString()}`, { Accept: 'application/json' });
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={<ReportsMenu />} breadcrumbs={['reportTitle', 'statisticsTitle']}>
diff --git a/modern/src/reports/StopReportPage.js b/modern/src/reports/StopReportPage.js
index dd55b774..dc2ac19a 100644
--- a/modern/src/reports/StopReportPage.js
+++ b/modern/src/reports/StopReportPage.js
@@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout';
import ReportsMenu from './components/ReportsMenu';
import ColumnSelect from './components/ColumnSelect';
import usePersistedState from '../common/util/usePersistedState';
+import { useCatch } from '../reactHelper';
const columnsArray = [
['startTime', 'reportStartTime'],
@@ -33,7 +34,7 @@ const StopReportPage = () => {
const [columns, setColumns] = usePersistedState('stopColumns', ['startTime', 'endTime', 'startOdometer', 'address']);
const [items, setItems] = useState([]);
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, mail,
});
@@ -47,8 +48,10 @@ const StopReportPage = () => {
window.location.assign(window.URL.createObjectURL(await response.blob()));
}
}
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const formatValue = (item, key) => {
switch (key) {
diff --git a/modern/src/reports/SummaryReportPage.js b/modern/src/reports/SummaryReportPage.js
index e838cd9e..1b8fd8e0 100644
--- a/modern/src/reports/SummaryReportPage.js
+++ b/modern/src/reports/SummaryReportPage.js
@@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout';
import ReportsMenu from './components/ReportsMenu';
import usePersistedState from '../common/util/usePersistedState';
import ColumnSelect from './components/ColumnSelect';
+import { useCatch } from '../reactHelper';
const columnsArray = [
['startTime', 'reportStartDate'],
@@ -37,7 +38,7 @@ const SummaryReportPage = () => {
const [daily, setDaily] = useState(false);
const [items, setItems] = useState([]);
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, daily, mail,
});
@@ -51,8 +52,10 @@ const SummaryReportPage = () => {
window.location.assign(window.URL.createObjectURL(await response.blob()));
}
}
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const formatValue = (item, key) => {
switch (key) {
diff --git a/modern/src/reports/TripReportPage.js b/modern/src/reports/TripReportPage.js
index c9d620e8..6e230388 100644
--- a/modern/src/reports/TripReportPage.js
+++ b/modern/src/reports/TripReportPage.js
@@ -12,6 +12,7 @@ import PageLayout from '../common/components/PageLayout';
import ReportsMenu from './components/ReportsMenu';
import ColumnSelect from './components/ColumnSelect';
import usePersistedState from '../common/util/usePersistedState';
+import { useCatch } from '../reactHelper';
const columnsArray = [
['startTime', 'reportStartTime'],
@@ -39,7 +40,7 @@ const TripReportPage = () => {
const [columns, setColumns] = usePersistedState('tripColumns', ['startTime', 'endTime', 'distance', 'averageSpeed']);
const [items, setItems] = useState([]);
- const handleSubmit = async (deviceId, from, to, mail, headers) => {
+ const handleSubmit = useCatch(async (deviceId, from, to, mail, headers) => {
const query = new URLSearchParams({
deviceId, from, to, mail,
});
@@ -53,8 +54,10 @@ const TripReportPage = () => {
window.location.assign(window.URL.createObjectURL(await response.blob()));
}
}
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const formatValue = (item, key) => {
switch (key) {
diff --git a/modern/src/settings/AccumulatorsPage.js b/modern/src/settings/AccumulatorsPage.js
index 0fcde8d1..b22fe02e 100644
--- a/modern/src/settings/AccumulatorsPage.js
+++ b/modern/src/settings/AccumulatorsPage.js
@@ -8,6 +8,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { useTranslation } from '../common/components/LocalizationProvider';
import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
container: {
@@ -45,7 +46,7 @@ const AccumulatorsPage = () => {
}
}, [deviceId, position]);
- const handleSave = async () => {
+ const handleSave = useCatch(async () => {
const response = await fetch(`/api/devices/${deviceId}/accumulators`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
@@ -54,8 +55,10 @@ const AccumulatorsPage = () => {
if (response.ok) {
history.goBack();
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['sharedDeviceAccumulators']}>
diff --git a/modern/src/settings/CalendarsPage.js b/modern/src/settings/CalendarsPage.js
index 875530a8..06697647 100644
--- a/modern/src/settings/CalendarsPage.js
+++ b/modern/src/settings/CalendarsPage.js
@@ -26,6 +26,8 @@ const CalendarsView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/calendars');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/CommandSendPage.js b/modern/src/settings/CommandSendPage.js
index f7e62b24..a817e11d 100644
--- a/modern/src/settings/CommandSendPage.js
+++ b/modern/src/settings/CommandSendPage.js
@@ -9,6 +9,7 @@ import BaseCommandView from './components/BaseCommandView';
import SelectField from '../common/components/SelectField';
import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
container: {
@@ -36,12 +37,14 @@ const CommandSendPage = () => {
const [savedId, setSavedId] = useState(0);
const [item, setItem] = useState({});
- const handleSend = async () => {
+ const handleSend = useCatch(async () => {
let command;
if (savedId) {
const response = await fetch(`/api/commands/${savedId}`);
if (response.ok) {
command = await response.json();
+ } else {
+ throw Error(await response.text());
}
} else {
command = item;
@@ -57,8 +60,10 @@ const CommandSendPage = () => {
if (response.ok) {
history.goBack();
+ } else {
+ throw Error(await response.text());
}
- };
+ });
const validate = () => savedId || (item && item.type);
diff --git a/modern/src/settings/CommandsPage.js b/modern/src/settings/CommandsPage.js
index 20b792b8..1b09a8bd 100644
--- a/modern/src/settings/CommandsPage.js
+++ b/modern/src/settings/CommandsPage.js
@@ -28,6 +28,8 @@ const CommandsView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/commands');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/ComputedAttributesPage.js b/modern/src/settings/ComputedAttributesPage.js
index 451b47a7..86704c3b 100644
--- a/modern/src/settings/ComputedAttributesPage.js
+++ b/modern/src/settings/ComputedAttributesPage.js
@@ -28,6 +28,8 @@ const ComputedAttributeView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/attributes/computed');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/DriversPage.js b/modern/src/settings/DriversPage.js
index f5908381..26601777 100644
--- a/modern/src/settings/DriversPage.js
+++ b/modern/src/settings/DriversPage.js
@@ -26,6 +26,8 @@ const DriversView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/drivers');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js
index 5d23c2d9..257d0bca 100644
--- a/modern/src/settings/GroupsPage.js
+++ b/modern/src/settings/GroupsPage.js
@@ -26,6 +26,8 @@ const GroupsView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/groups');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/MaintenancesPage.js b/modern/src/settings/MaintenancesPage.js
index 3801f010..ea00e7e1 100644
--- a/modern/src/settings/MaintenancesPage.js
+++ b/modern/src/settings/MaintenancesPage.js
@@ -34,6 +34,8 @@ const MaintenancesView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/maintenance');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/NotificationsPage.js b/modern/src/settings/NotificationsPage.js
index 7ad5186b..de3e762f 100644
--- a/modern/src/settings/NotificationsPage.js
+++ b/modern/src/settings/NotificationsPage.js
@@ -28,6 +28,8 @@ const NotificationsView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/notifications');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/ServerPage.js b/modern/src/settings/ServerPage.js
index 5fdba47b..b745cb96 100644
--- a/modern/src/settings/ServerPage.js
+++ b/modern/src/settings/ServerPage.js
@@ -15,6 +15,7 @@ import PageLayout from '../common/components/PageLayout';
import SettingsMenu from './components/SettingsMenu';
import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttributes';
import useCommonUserAttributes from '../common/attributes/useCommonUserAttributes';
+import { useCatch } from '../reactHelper';
const useStyles = makeStyles((theme) => ({
container: {
@@ -44,7 +45,7 @@ const ServerPage = () => {
const original = useSelector((state) => state.session.server);
const [item, setItem] = useState({ ...original });
- const handleSave = async () => {
+ const handleSave = useCatch(async () => {
const response = await fetch('/api/server', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
@@ -54,8 +55,10 @@ const ServerPage = () => {
if (response.ok) {
dispatch(sessionActions.updateServer(await response.json()));
history.goBack();
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsServer']}>
diff --git a/modern/src/settings/UsersPage.js b/modern/src/settings/UsersPage.js
index 2d8d1199..235ae4c7 100644
--- a/modern/src/settings/UsersPage.js
+++ b/modern/src/settings/UsersPage.js
@@ -27,6 +27,8 @@ const UsersView = ({ updateTimestamp, onMenuClick }) => {
const response = await fetch('/api/users');
if (response.ok) {
setItems(await response.json());
+ } else {
+ throw Error(await response.text());
}
}, [updateTimestamp]);
diff --git a/modern/src/settings/components/EditItemView.js b/modern/src/settings/components/EditItemView.js
index 9b9918fe..28598e77 100644
--- a/modern/src/settings/components/EditItemView.js
+++ b/modern/src/settings/components/EditItemView.js
@@ -5,7 +5,7 @@ import Container from '@material-ui/core/Container';
import Button from '@material-ui/core/Button';
import FormControl from '@material-ui/core/FormControl';
-import { useEffectAsync } from '../../reactHelper';
+import { useCatch, useEffectAsync } from '../../reactHelper';
import { useTranslation } from '../../common/components/LocalizationProvider';
import PageLayout from '../../common/components/PageLayout';
@@ -36,13 +36,15 @@ const EditItemView = ({
const response = await fetch(`/api/${endpoint}/${id}`);
if (response.ok) {
setItem(await response.json());
+ } else {
+ throw Error(await response.text());
}
} else {
setItem(defaultItem || {});
}
}, [id]);
- const handleSave = async () => {
+ const handleSave = useCatch(async () => {
let url = `/api/${endpoint}`;
if (id) {
url += `/${id}`;
@@ -59,8 +61,10 @@ const EditItemView = ({
onItemSaved(await response.json());
}
history.goBack();
+ } else {
+ throw Error(await response.text());
}
- };
+ });
return (
<PageLayout menu={menu} breadcrumbs={breadcrumbs}>
diff --git a/modern/src/store/errors.js b/modern/src/store/errors.js
new file mode 100644
index 00000000..d1b86e5a
--- /dev/null
+++ b/modern/src/store/errors.js
@@ -0,0 +1,21 @@
+import { createSlice } from '@reduxjs/toolkit';
+
+const { reducer, actions } = createSlice({
+ name: 'errors',
+ initialState: {
+ errors: [],
+ },
+ reducers: {
+ push(state, action) {
+ state.errors.push(action.payload);
+ },
+ pop(state, _) {
+ if (state.errors.length) {
+ state.errors.shift();
+ }
+ },
+ },
+});
+
+export { actions as errorsActions };
+export { reducer as errorsReducer };
diff --git a/modern/src/store/index.js b/modern/src/store/index.js
index 6e2bb204..72867b76 100644
--- a/modern/src/store/index.js
+++ b/modern/src/store/index.js
@@ -1,5 +1,6 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
+import { errorsReducer as errors } from './errors';
import { sessionReducer as session } from './session';
import { devicesReducer as devices } from './devices';
import { positionsReducer as positions } from './positions';
@@ -9,6 +10,7 @@ import { driversReducer as drivers } from './drivers';
import { maintenancesReducer as maintenances } from './maintenances';
const reducer = combineReducers({
+ errors,
session,
devices,
positions,
@@ -18,6 +20,7 @@ const reducer = combineReducers({
maintenances,
});
+export { errorsActions } from './errors';
export { sessionActions } from './session';
export { devicesActions } from './devices';
export { positionsActions } from './positions';