aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2023-02-23 11:48:06 -0800
committerAnton Tananaev <anton@traccar.org>2023-02-23 11:48:06 -0800
commit5f65762c67aacc9a621983fcb06107b982792c3f (patch)
treeb4c29451fbfb2fc0e090671efc76188725ac4d59
parentd64115f88d84c85b149063cf8929d1f57c66935f (diff)
downloadtrackermap-web-5f65762c67aacc9a621983fcb06107b982792c3f.tar.gz
trackermap-web-5f65762c67aacc9a621983fcb06107b982792c3f.tar.bz2
trackermap-web-5f65762c67aacc9a621983fcb06107b982792c3f.zip
Separate connections from edit
-rw-r--r--modern/src/Navigation.js6
-rw-r--r--modern/src/settings/DeviceConnectionsPage.js116
-rw-r--r--modern/src/settings/DevicePage.js75
-rw-r--r--modern/src/settings/DevicesPage.js18
-rw-r--r--modern/src/settings/GroupConnectionsPage.js116
-rw-r--r--modern/src/settings/GroupPage.js73
-rw-r--r--modern/src/settings/GroupsPage.js18
-rw-r--r--modern/src/settings/UserConnectionsPage.js139
-rw-r--r--modern/src/settings/UserPage.js96
-rw-r--r--modern/src/settings/UsersPage.js17
-rw-r--r--modern/src/settings/components/CollectionActions.js20
11 files changed, 436 insertions, 258 deletions
diff --git a/modern/src/Navigation.js b/modern/src/Navigation.js
index f5b21dd9..c2aaa69f 100644
--- a/modern/src/Navigation.js
+++ b/modern/src/Navigation.js
@@ -49,6 +49,9 @@ import App from './App';
import ChangeServerPage from './other/ChangeServerPage';
import DevicesPage from './settings/DevicesPage';
import ScheduledPage from './reports/ScheduledPage';
+import DeviceConnectionsPage from './settings/DeviceConnectionsPage';
+import GroupConnectionsPage from './settings/GroupConnectionsPage';
+import UserConnectionsPage from './settings/UserConnectionsPage';
const Navigation = () => {
const navigate = useNavigate();
@@ -115,6 +118,7 @@ const Navigation = () => {
<Route path="attribute/:id" element={<ComputedAttributePage />} />
<Route path="attribute" element={<ComputedAttributePage />} />
<Route path="devices" element={<DevicesPage />} />
+ <Route path="device/:id/connections" element={<DeviceConnectionsPage />} />
<Route path="device/:id" element={<DevicePage />} />
<Route path="device" element={<DevicePage />} />
<Route path="drivers" element={<DriversPage />} />
@@ -123,6 +127,7 @@ const Navigation = () => {
<Route path="geofence/:id" element={<GeofencePage />} />
<Route path="geofence" element={<GeofencePage />} />
<Route path="groups" element={<GroupsPage />} />
+ <Route path="group/:id/connections" element={<GroupConnectionsPage />} />
<Route path="group/:id" element={<GroupPage />} />
<Route path="group" element={<GroupPage />} />
<Route path="maintenances" element={<MaintenancesPage />} />
@@ -134,6 +139,7 @@ const Navigation = () => {
<Route path="preferences" element={<PreferencesPage />} />
<Route path="server" element={<ServerPage />} />
<Route path="users" element={<UsersPage />} />
+ <Route path="user/:id/connections" element={<UserConnectionsPage />} />
<Route path="user/:id" element={<UserPage />} />
<Route path="user" element={<UserPage />} />
</Route>
diff --git a/modern/src/settings/DeviceConnectionsPage.js b/modern/src/settings/DeviceConnectionsPage.js
new file mode 100644
index 00000000..88d47872
--- /dev/null
+++ b/modern/src/settings/DeviceConnectionsPage.js
@@ -0,0 +1,116 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Typography,
+ Container,
+} from '@mui/material';
+import makeStyles from '@mui/styles/makeStyles';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import LinkField from '../common/components/LinkField';
+import { useTranslation } from '../common/components/LocalizationProvider';
+import SettingsMenu from './components/SettingsMenu';
+import { formatNotificationTitle } from '../common/util/formatter';
+import PageLayout from '../common/components/PageLayout';
+import useFeatures from '../common/util/useFeatures';
+
+const useStyles = makeStyles((theme) => ({
+ container: {
+ marginTop: theme.spacing(2),
+ },
+ details: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(2),
+ paddingBottom: theme.spacing(3),
+ },
+}));
+
+const DeviceConnectionsPage = () => {
+ const classes = useStyles();
+ const t = useTranslation();
+
+ const { id } = useParams();
+
+ const features = useFeatures();
+
+ return (
+ <PageLayout
+ menu={<SettingsMenu />}
+ breadcrumbs={['settingsTitle', 'sharedDevice', 'sharedConnections']}
+ >
+ <Container maxWidth="xs" className={classes.container}>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedConnections')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <LinkField
+ endpointAll="/api/geofences"
+ endpointLinked={`/api/geofences?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="geofenceId"
+ label={t('sharedGeofences')}
+ />
+ <LinkField
+ endpointAll="/api/notifications"
+ endpointLinked={`/api/notifications?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="notificationId"
+ titleGetter={(it) => formatNotificationTitle(t, it)}
+ label={t('sharedNotifications')}
+ />
+ {!features.disableDrivers && (
+ <LinkField
+ endpointAll="/api/drivers"
+ endpointLinked={`/api/drivers?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="driverId"
+ label={t('sharedDrivers')}
+ />
+ )}
+ {!features.disableComputedAttributes && (
+ <LinkField
+ endpointAll="/api/attributes/computed"
+ endpointLinked={`/api/attributes/computed?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="attributeId"
+ titleGetter={(it) => it.description}
+ label={t('sharedComputedAttributes')}
+ />
+ )}
+ <LinkField
+ endpointAll="/api/commands"
+ endpointLinked={`/api/commands?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="commandId"
+ titleGetter={(it) => it.description}
+ label={t('sharedSavedCommands')}
+ />
+ {!features.disableMaintenance && (
+ <LinkField
+ endpointAll="/api/maintenance"
+ endpointLinked={`/api/maintenance?deviceId=${id}`}
+ baseId={id}
+ keyBase="deviceId"
+ keyLink="maintenanceId"
+ label={t('sharedMaintenance')}
+ />
+ )}
+ </AccordionDetails>
+ </Accordion>
+ </Container>
+ </PageLayout>
+ );
+};
+
+export default DeviceConnectionsPage;
diff --git a/modern/src/settings/DevicePage.js b/modern/src/settings/DevicePage.js
index b080d8ce..a0a6c2b8 100644
--- a/modern/src/settings/DevicePage.js
+++ b/modern/src/settings/DevicePage.js
@@ -16,15 +16,12 @@ import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
import SelectField from '../common/components/SelectField';
import deviceCategories from '../common/util/deviceCategories';
-import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
import useDeviceAttributes from '../common/attributes/useDeviceAttributes';
import { useAdministrator } from '../common/util/permissions';
import SettingsMenu from './components/SettingsMenu';
import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttributes';
-import useFeatures from '../common/util/useFeatures';
import { useCatch } from '../reactHelper';
-import { formatNotificationTitle } from '../common/util/formatter';
const useStyles = makeStyles((theme) => ({
details: {
@@ -44,8 +41,6 @@ const DevicePage = () => {
const commonDeviceAttributes = useCommonDeviceAttributes(t);
const deviceAttributes = useDeviceAttributes(t);
- const features = useFeatures();
-
const [item, setItem] = useState();
const handleFiles = useCatch(async (files) => {
@@ -71,7 +66,7 @@ const DevicePage = () => {
setItem={setItem}
validate={validate}
menu={<SettingsMenu />}
- breadcrumbs={['sharedDevice']}
+ breadcrumbs={['settingsTitle', 'sharedDevice']}
>
{item && (
<>
@@ -170,74 +165,6 @@ const DevicePage = () => {
setAttributes={(attributes) => setItem({ ...item, attributes })}
definitions={{ ...commonDeviceAttributes, ...deviceAttributes }}
/>
- {item.id && (
- <Accordion>
- <AccordionSummary expandIcon={<ExpandMoreIcon />}>
- <Typography variant="subtitle1">
- {t('sharedConnections')}
- </Typography>
- </AccordionSummary>
- <AccordionDetails className={classes.details}>
- <LinkField
- endpointAll="/api/geofences"
- endpointLinked={`/api/geofences?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="geofenceId"
- label={t('sharedGeofences')}
- />
- <LinkField
- endpointAll="/api/notifications"
- endpointLinked={`/api/notifications?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="notificationId"
- titleGetter={(it) => formatNotificationTitle(t, it)}
- label={t('sharedNotifications')}
- />
- {!features.disableDrivers && (
- <LinkField
- endpointAll="/api/drivers"
- endpointLinked={`/api/drivers?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="driverId"
- label={t('sharedDrivers')}
- />
- )}
- {!features.disableComputedAttributes && (
- <LinkField
- endpointAll="/api/attributes/computed"
- endpointLinked={`/api/attributes/computed?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="attributeId"
- titleGetter={(it) => it.description}
- label={t('sharedComputedAttributes')}
- />
- )}
- <LinkField
- endpointAll="/api/commands"
- endpointLinked={`/api/commands?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="commandId"
- titleGetter={(it) => it.description}
- label={t('sharedSavedCommands')}
- />
- {!features.disableMaintenance && (
- <LinkField
- endpointAll="/api/maintenance"
- endpointLinked={`/api/maintenance?deviceId=${item.id}`}
- baseId={item.id}
- keyBase="deviceId"
- keyLink="maintenanceId"
- label={t('sharedMaintenance')}
- />
- )}
- </AccordionDetails>
- </Accordion>
- )}
</>
)}
</EditItemView>
diff --git a/modern/src/settings/DevicesPage.js b/modern/src/settings/DevicesPage.js
index 0ad76106..8f267a21 100644
--- a/modern/src/settings/DevicesPage.js
+++ b/modern/src/settings/DevicesPage.js
@@ -1,7 +1,9 @@
import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Table, TableRow, TableCell, TableHead, TableBody,
} from '@mui/material';
+import LinkIcon from '@mui/icons-material/Link';
import makeStyles from '@mui/styles/makeStyles';
import { useEffectAsync } from '../reactHelper';
import { useTranslation } from '../common/components/LocalizationProvider';
@@ -23,6 +25,7 @@ const useStyles = makeStyles((theme) => ({
const DevicesPage = () => {
const classes = useStyles();
+ const navigate = useNavigate();
const t = useTranslation();
const hours12 = usePreference('twelveHourFormat');
@@ -46,6 +49,13 @@ const DevicesPage = () => {
}
}, [timestamp]);
+ const actionConnections = {
+ key: 'connections',
+ title: t('sharedConnections'),
+ icon: <LinkIcon fontSize="small" />,
+ handler: (deviceId) => navigate(`/settings/device/${deviceId}/connections`),
+ };
+
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'sharedDrivers']}>
<SearchHeader keyword={searchKeyword} setKeyword={setSearchKeyword} />
@@ -71,7 +81,13 @@ const DevicesPage = () => {
<TableCell>{item.contact}</TableCell>
<TableCell>{formatTime(item.expirationTime, 'date', hours12)}</TableCell>
<TableCell className={classes.columnAction} padding="none">
- <CollectionActions itemId={item.id} editPath="/settings/device" endpoint="devices" setTimestamp={setTimestamp} />
+ <CollectionActions
+ itemId={item.id}
+ editPath="/settings/device"
+ endpoint="devices"
+ setTimestamp={setTimestamp}
+ customActions={[actionConnections]}
+ />
</TableCell>
</TableRow>
)) : (<TableShimmer columns={6} endAction />)}
diff --git a/modern/src/settings/GroupConnectionsPage.js b/modern/src/settings/GroupConnectionsPage.js
new file mode 100644
index 00000000..8ea3b88e
--- /dev/null
+++ b/modern/src/settings/GroupConnectionsPage.js
@@ -0,0 +1,116 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Typography,
+ Container,
+} from '@mui/material';
+import makeStyles from '@mui/styles/makeStyles';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import LinkField from '../common/components/LinkField';
+import { useTranslation } from '../common/components/LocalizationProvider';
+import SettingsMenu from './components/SettingsMenu';
+import { formatNotificationTitle } from '../common/util/formatter';
+import PageLayout from '../common/components/PageLayout';
+import useFeatures from '../common/util/useFeatures';
+
+const useStyles = makeStyles((theme) => ({
+ container: {
+ marginTop: theme.spacing(2),
+ },
+ details: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(2),
+ paddingBottom: theme.spacing(3),
+ },
+}));
+
+const GroupConnectionsPage = () => {
+ const classes = useStyles();
+ const t = useTranslation();
+
+ const { id } = useParams();
+
+ const features = useFeatures();
+
+ return (
+ <PageLayout
+ menu={<SettingsMenu />}
+ breadcrumbs={['settingsTitle', 'groupDialog', 'sharedConnections']}
+ >
+ <Container maxWidth="xs" className={classes.container}>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedConnections')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <LinkField
+ endpointAll="/api/geofences"
+ endpointLinked={`/api/geofences?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="geofenceId"
+ label={t('sharedGeofences')}
+ />
+ <LinkField
+ endpointAll="/api/notifications"
+ endpointLinked={`/api/notifications?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="notificationId"
+ titleGetter={(it) => formatNotificationTitle(t, it)}
+ label={t('sharedNotifications')}
+ />
+ {!features.disableDrivers && (
+ <LinkField
+ endpointAll="/api/drivers"
+ endpointLinked={`/api/drivers?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="driverId"
+ label={t('sharedDrivers')}
+ />
+ )}
+ {!features.disableComputedAttributes && (
+ <LinkField
+ endpointAll="/api/attributes/computed"
+ endpointLinked={`/api/attributes/computed?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="attributeId"
+ titleGetter={(it) => it.description}
+ label={t('sharedComputedAttributes')}
+ />
+ )}
+ <LinkField
+ endpointAll="/api/commands"
+ endpointLinked={`/api/commands?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="commandId"
+ titleGetter={(it) => it.description}
+ label={t('sharedSavedCommands')}
+ />
+ {!features.disableMaintenance && (
+ <LinkField
+ endpointAll="/api/maintenance"
+ endpointLinked={`/api/maintenance?groupId=${id}`}
+ baseId={id}
+ keyBase="groupId"
+ keyLink="maintenanceId"
+ label={t('sharedMaintenance')}
+ />
+ )}
+ </AccordionDetails>
+ </Accordion>
+ </Container>
+ </PageLayout>
+ );
+};
+
+export default GroupConnectionsPage;
diff --git a/modern/src/settings/GroupPage.js b/modern/src/settings/GroupPage.js
index b9ec36c9..51fbda0e 100644
--- a/modern/src/settings/GroupPage.js
+++ b/modern/src/settings/GroupPage.js
@@ -10,13 +10,10 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
import SelectField from '../common/components/SelectField';
-import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
import SettingsMenu from './components/SettingsMenu';
import useCommonDeviceAttributes from '../common/attributes/useCommonDeviceAttributes';
import useGroupAttributes from '../common/attributes/useGroupAttributes';
-import useFeatures from '../common/util/useFeatures';
-import { formatNotificationTitle } from '../common/util/formatter';
import { useCatch } from '../reactHelper';
import { groupsActions } from '../store';
@@ -37,8 +34,6 @@ const GroupPage = () => {
const commonDeviceAttributes = useCommonDeviceAttributes(t);
const groupAttributes = useGroupAttributes(t);
- const features = useFeatures();
-
const [item, setItem] = useState();
const onItemSaved = useCatch(async () => {
@@ -98,74 +93,6 @@ const GroupPage = () => {
setAttributes={(attributes) => setItem({ ...item, attributes })}
definitions={{ ...commonDeviceAttributes, ...groupAttributes }}
/>
- {item.id && (
- <Accordion>
- <AccordionSummary expandIcon={<ExpandMoreIcon />}>
- <Typography variant="subtitle1">
- {t('sharedConnections')}
- </Typography>
- </AccordionSummary>
- <AccordionDetails className={classes.details}>
- <LinkField
- endpointAll="/api/geofences"
- endpointLinked={`/api/geofences?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="geofenceId"
- label={t('sharedGeofences')}
- />
- <LinkField
- endpointAll="/api/notifications"
- endpointLinked={`/api/notifications?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="notificationId"
- titleGetter={(it) => formatNotificationTitle(t, it)}
- label={t('sharedNotifications')}
- />
- {!features.disableDrivers && (
- <LinkField
- endpointAll="/api/drivers"
- endpointLinked={`/api/drivers?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="driverId"
- label={t('sharedDrivers')}
- />
- )}
- {!features.disableComputedAttributes && (
- <LinkField
- endpointAll="/api/attributes/computed"
- endpointLinked={`/api/attributes/computed?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="attributeId"
- titleGetter={(it) => it.description}
- label={t('sharedComputedAttributes')}
- />
- )}
- <LinkField
- endpointAll="/api/commands"
- endpointLinked={`/api/commands?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="commandId"
- titleGetter={(it) => it.description}
- label={t('sharedSavedCommands')}
- />
- {!features.disableMaintenance && (
- <LinkField
- endpointAll="/api/maintenance"
- endpointLinked={`/api/maintenance?groupId=${item.id}`}
- baseId={item.id}
- keyBase="groupId"
- keyLink="maintenanceId"
- label={t('sharedMaintenance')}
- />
- )}
- </AccordionDetails>
- </Accordion>
- )}
</>
)}
</EditItemView>
diff --git a/modern/src/settings/GroupsPage.js b/modern/src/settings/GroupsPage.js
index 64ae1a1d..0624f7d1 100644
--- a/modern/src/settings/GroupsPage.js
+++ b/modern/src/settings/GroupsPage.js
@@ -1,7 +1,9 @@
import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Table, TableRow, TableCell, TableHead, TableBody,
} from '@mui/material';
+import LinkIcon from '@mui/icons-material/Link';
import makeStyles from '@mui/styles/makeStyles';
import { useEffectAsync } from '../reactHelper';
import { useTranslation } from '../common/components/LocalizationProvider';
@@ -21,6 +23,7 @@ const useStyles = makeStyles((theme) => ({
const GroupsPage = () => {
const classes = useStyles();
+ const navigate = useNavigate();
const t = useTranslation();
const [timestamp, setTimestamp] = useState(Date.now());
@@ -42,6 +45,13 @@ const GroupsPage = () => {
}
}, [timestamp]);
+ const actionConnections = {
+ key: 'connections',
+ title: t('sharedConnections'),
+ icon: <LinkIcon fontSize="small" />,
+ handler: (groupId) => navigate(`/settings/group/${groupId}/connections`),
+ };
+
return (
<PageLayout menu={<SettingsMenu />} breadcrumbs={['settingsTitle', 'settingsGroups']}>
<SearchHeader keyword={searchKeyword} setKeyword={setSearchKeyword} />
@@ -57,7 +67,13 @@ const GroupsPage = () => {
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell className={classes.columnAction} padding="none">
- <CollectionActions itemId={item.id} editPath="/settings/group" endpoint="groups" setTimestamp={setTimestamp} />
+ <CollectionActions
+ itemId={item.id}
+ editPath="/settings/group"
+ endpoint="groups"
+ setTimestamp={setTimestamp}
+ customActions={[actionConnections]}
+ />
</TableCell>
</TableRow>
)) : (<TableShimmer columns={2} endAction />)}
diff --git a/modern/src/settings/UserConnectionsPage.js b/modern/src/settings/UserConnectionsPage.js
new file mode 100644
index 00000000..80de8835
--- /dev/null
+++ b/modern/src/settings/UserConnectionsPage.js
@@ -0,0 +1,139 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Typography,
+ Container,
+} from '@mui/material';
+import makeStyles from '@mui/styles/makeStyles';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import LinkField from '../common/components/LinkField';
+import { useTranslation } from '../common/components/LocalizationProvider';
+import SettingsMenu from './components/SettingsMenu';
+import { formatNotificationTitle } from '../common/util/formatter';
+import PageLayout from '../common/components/PageLayout';
+
+const useStyles = makeStyles((theme) => ({
+ container: {
+ marginTop: theme.spacing(2),
+ },
+ details: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(2),
+ paddingBottom: theme.spacing(3),
+ },
+}));
+
+const UserConnectionsPage = () => {
+ const classes = useStyles();
+ const t = useTranslation();
+
+ const { id } = useParams();
+
+ return (
+ <PageLayout
+ menu={<SettingsMenu />}
+ breadcrumbs={['settingsTitle', 'settingsUser', 'sharedConnections']}
+ >
+ <Container maxWidth="xs" className={classes.container}>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedConnections')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <LinkField
+ endpointAll="/api/devices?all=true"
+ endpointLinked={`/api/devices?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="deviceId"
+ label={t('deviceTitle')}
+ />
+ <LinkField
+ endpointAll="/api/groups?all=true"
+ endpointLinked={`/api/groups?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="groupId"
+ label={t('settingsGroups')}
+ />
+ <LinkField
+ endpointAll="/api/geofences?all=true"
+ endpointLinked={`/api/geofences?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="geofenceId"
+ label={t('sharedGeofences')}
+ />
+ <LinkField
+ endpointAll="/api/notifications?all=true"
+ endpointLinked={`/api/notifications?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="notificationId"
+ titleGetter={(it) => formatNotificationTitle(t, it, true)}
+ label={t('sharedNotifications')}
+ />
+ <LinkField
+ endpointAll="/api/calendars?all=true"
+ endpointLinked={`/api/calendars?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="calendarId"
+ label={t('sharedCalendars')}
+ />
+ <LinkField
+ endpointAll="/api/users?all=true"
+ endpointLinked={`/api/users?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="managedUserId"
+ label={t('settingsUsers')}
+ />
+ <LinkField
+ endpointAll="/api/attributes/computed?all=true"
+ endpointLinked={`/api/attributes/computed?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="attributeId"
+ titleGetter={(it) => it.description}
+ label={t('sharedComputedAttributes')}
+ />
+ <LinkField
+ endpointAll="/api/drivers?all=true"
+ endpointLinked={`/api/drivers?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="driverId"
+ label={t('sharedDrivers')}
+ />
+ <LinkField
+ endpointAll="/api/commands?all=true"
+ endpointLinked={`/api/commands?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="commandId"
+ titleGetter={(it) => it.description}
+ label={t('sharedSavedCommands')}
+ />
+ <LinkField
+ endpointAll="/api/maintenance?all=true"
+ endpointLinked={`/api/maintenance?userId=${id}`}
+ baseId={id}
+ keyBase="userId"
+ keyLink="maintenanceId"
+ label={t('sharedMaintenance')}
+ />
+ </AccordionDetails>
+ </Accordion>
+ </Container>
+ </PageLayout>
+ );
+};
+
+export default UserConnectionsPage;
diff --git a/modern/src/settings/UserPage.js b/modern/src/settings/UserPage.js
index 582e63eb..91500eb4 100644
--- a/modern/src/settings/UserPage.js
+++ b/modern/src/settings/UserPage.js
@@ -22,7 +22,6 @@ import { useDispatch, useSelector } from 'react-redux';
import moment from 'moment';
import EditItemView from './components/EditItemView';
import EditAttributesAccordion from './components/EditAttributesAccordion';
-import LinkField from '../common/components/LinkField';
import { useTranslation } from '../common/components/LocalizationProvider';
import useUserAttributes from '../common/attributes/useUserAttributes';
import { sessionActions } from '../store';
@@ -32,7 +31,6 @@ import useCommonUserAttributes from '../common/attributes/useCommonUserAttribute
import { useAdministrator, useRestriction, useManager } from '../common/util/permissions';
import useQuery from '../common/util/useQuery';
import { useCatch } from '../reactHelper';
-import { formatNotificationTitle } from '../common/util/formatter';
import useMapStyles from '../map/core/useMapStyles';
import { map } from '../map/core/MapView';
@@ -392,100 +390,6 @@ const UserPage = () => {
</AccordionDetails>
</Accordion>
)}
- {item.id && manager && (
- <Accordion>
- <AccordionSummary expandIcon={<ExpandMoreIcon />}>
- <Typography variant="subtitle1">
- {t('sharedConnections')}
- </Typography>
- </AccordionSummary>
- <AccordionDetails className={classes.details}>
- <LinkField
- endpointAll="/api/devices?all=true"
- endpointLinked={`/api/devices?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="deviceId"
- label={t('deviceTitle')}
- />
- <LinkField
- endpointAll="/api/groups?all=true"
- endpointLinked={`/api/groups?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="groupId"
- label={t('settingsGroups')}
- />
- <LinkField
- endpointAll="/api/geofences?all=true"
- endpointLinked={`/api/geofences?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="geofenceId"
- label={t('sharedGeofences')}
- />
- <LinkField
- endpointAll="/api/notifications?all=true"
- endpointLinked={`/api/notifications?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="notificationId"
- titleGetter={(it) => formatNotificationTitle(t, it, true)}
- label={t('sharedNotifications')}
- />
- <LinkField
- endpointAll="/api/calendars?all=true"
- endpointLinked={`/api/calendars?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="calendarId"
- label={t('sharedCalendars')}
- />
- <LinkField
- endpointAll="/api/users?all=true"
- endpointLinked={`/api/users?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="managedUserId"
- label={t('settingsUsers')}
- />
- <LinkField
- endpointAll="/api/attributes/computed?all=true"
- endpointLinked={`/api/attributes/computed?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="attributeId"
- titleGetter={(it) => it.description}
- label={t('sharedComputedAttributes')}
- />
- <LinkField
- endpointAll="/api/drivers?all=true"
- endpointLinked={`/api/drivers?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="driverId"
- label={t('sharedDrivers')}
- />
- <LinkField
- endpointAll="/api/commands?all=true"
- endpointLinked={`/api/commands?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="commandId"
- titleGetter={(it) => it.description}
- label={t('sharedSavedCommands')}
- />
- <LinkField
- endpointAll="/api/maintenance?all=true"
- endpointLinked={`/api/maintenance?userId=${item.id}`}
- baseId={item.id}
- keyBase="userId"
- keyLink="maintenanceId"
- label={t('sharedMaintenance')}
- />
- </AccordionDetails>
- </Accordion>
- )}
</>
)}
</EditItemView>
diff --git a/modern/src/settings/UsersPage.js b/modern/src/settings/UsersPage.js
index 6cb10d25..98674c9e 100644
--- a/modern/src/settings/UsersPage.js
+++ b/modern/src/settings/UsersPage.js
@@ -1,8 +1,10 @@
import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
import {
Table, TableRow, TableCell, TableHead, TableBody,
} from '@mui/material';
import LoginIcon from '@mui/icons-material/Login';
+import LinkIcon from '@mui/icons-material/Link';
import makeStyles from '@mui/styles/makeStyles';
import { useCatch, useEffectAsync } from '../reactHelper';
import { formatBoolean, formatTime } from '../common/util/formatter';
@@ -25,6 +27,7 @@ const useStyles = makeStyles((theme) => ({
const UsersPage = () => {
const classes = useStyles();
+ const navigate = useNavigate();
const t = useTranslation();
const admin = useAdministrator();
@@ -45,12 +48,20 @@ const UsersPage = () => {
}
});
- const loginAction = {
+ const actionLogin = {
+ key: 'login',
title: t('loginLogin'),
- icon: (<LoginIcon fontSize="small" />),
+ icon: <LoginIcon fontSize="small" />,
handler: handleLogin,
};
+ const actionConnections = {
+ key: 'connections',
+ title: t('sharedConnections'),
+ icon: <LinkIcon fontSize="small" />,
+ handler: (userId) => navigate(`/settings/user/${userId}/connections`),
+ };
+
useEffectAsync(async () => {
setLoading(true);
try {
@@ -93,7 +104,7 @@ const UsersPage = () => {
editPath="/settings/user"
endpoint="users"
setTimestamp={setTimestamp}
- customAction={admin ? loginAction : null}
+ customActions={admin ? [actionLogin, actionConnections] : [actionConnections]}
/>
</TableCell>
</TableRow>
diff --git a/modern/src/settings/components/CollectionActions.js b/modern/src/settings/components/CollectionActions.js
index e40b3eaa..b84d8f0f 100644
--- a/modern/src/settings/components/CollectionActions.js
+++ b/modern/src/settings/components/CollectionActions.js
@@ -17,7 +17,7 @@ const useStyles = makeStyles(() => ({
}));
const CollectionActions = ({
- itemId, editPath, endpoint, setTimestamp, customAction,
+ itemId, editPath, endpoint, setTimestamp, customActions,
}) => {
const theme = useTheme();
const classes = useStyles();
@@ -39,8 +39,8 @@ const CollectionActions = ({
setMenuAnchorEl(null);
};
- const handleCustom = () => {
- customAction.handler(itemId);
+ const handleCustom = (action) => {
+ action.handler(itemId);
setMenuAnchorEl(null);
};
@@ -59,20 +59,20 @@ const CollectionActions = ({
<MoreVertIcon fontSize="small" />
</IconButton>
<Menu open={!!menuAnchorEl} anchorEl={menuAnchorEl} onClose={() => setMenuAnchorEl(null)}>
- {customAction && (
- <MenuItem onClick={handleCustom}>{customAction.title}</MenuItem>
- )}
+ {customActions && customActions.map((action) => (
+ <MenuItem onClick={() => handleCustom(action)} key={action.key}>{action.title}</MenuItem>
+ ))}
<MenuItem onClick={handleEdit}>{t('sharedEdit')}</MenuItem>
<MenuItem onClick={handleRemove}>{t('sharedRemove')}</MenuItem>
</Menu>
</>
) : (
<div className={classes.row}>
- {customAction && (
- <IconButton size="small" onClick={handleCustom}>
- {customAction.icon}
+ {customActions && customActions.map((action) => (
+ <IconButton size="small" onClick={() => handleCustom(action)} key={action.key}>
+ {action.icon}
</IconButton>
- )}
+ ))}
<IconButton size="small" onClick={handleEdit}>
<EditIcon fontSize="small" />
</IconButton>