diff options
Diffstat (limited to 'modern/src')
-rw-r--r-- | modern/src/App.js | 4 | ||||
-rw-r--r-- | modern/src/attributes/EditAttributesView.js | 2 | ||||
-rw-r--r-- | modern/src/attributes/useCommandAttributes.js | 11 | ||||
-rw-r--r-- | modern/src/settings/CommandPage.js | 85 | ||||
-rw-r--r-- | modern/src/settings/CommandsPage.js | 69 | ||||
-rw-r--r-- | modern/src/settings/ComputedAttributePage.js | 2 | ||||
-rw-r--r-- | modern/src/settings/OptionsLayout.js | 4 |
7 files changed, 174 insertions, 3 deletions
diff --git a/modern/src/App.js b/modern/src/App.js index 9417feab..1d7b2cae 100644 --- a/modern/src/App.js +++ b/modern/src/App.js @@ -30,6 +30,8 @@ import ComputedAttributesPage from './settings/ComputedAttributesPage'; import ComputedAttributePage from './settings/ComputedAttributePage'; import MaintenancesPage from './settings/MaintenancesPage'; import MaintenancePage from './settings/MaintenancePage'; +import CommandsPage from './settings/CommandsPage'; +import CommandPage from './settings/CommandPage'; import StatisticsPage from './admin/StatisticsPage'; import CachingController from './CachingController'; @@ -111,6 +113,8 @@ const App = () => { <Route exact path="/settings/attribute/:id?" component={ComputedAttributePage} /> <Route exact path="/settings/maintenances" component={MaintenancesPage} /> <Route exact path="/settings/maintenance/:id?" component={MaintenancePage} /> + <Route exact path="/settings/commands" component={CommandsPage} /> + <Route exact path="/settings/command/:id?" component={CommandPage} /> <Route exact path="/admin/server" component={ServerPage} /> <Route exact path="/admin/users" component={UsersPage} /> <Route exact path="/admin/statistics" component={StatisticsPage} /> diff --git a/modern/src/attributes/EditAttributesView.js b/modern/src/attributes/EditAttributesView.js index e8ab83e1..3a18a9a3 100644 --- a/modern/src/attributes/EditAttributesView.js +++ b/modern/src/attributes/EditAttributesView.js @@ -116,7 +116,7 @@ const EditAttributesView = ({ attributes, setAttributes, definitions }) => { <CloseIcon /> </IconButton> </InputAdornment> - )} + )} /> </FormControl> ); diff --git a/modern/src/attributes/useCommandAttributes.js b/modern/src/attributes/useCommandAttributes.js new file mode 100644 index 00000000..1212d283 --- /dev/null +++ b/modern/src/attributes/useCommandAttributes.js @@ -0,0 +1,11 @@ +import { useMemo } from 'react'; + +export default (t) => useMemo(() => ({ + custom: [ + { + key: 'data', + name: t('commandData'), + type: 'string', + }, + ], +}), [t]); diff --git a/modern/src/settings/CommandPage.js b/modern/src/settings/CommandPage.js new file mode 100644 index 00000000..8a7f5b94 --- /dev/null +++ b/modern/src/settings/CommandPage.js @@ -0,0 +1,85 @@ +import React, { useEffect, useState } from 'react'; +import { + Accordion, AccordionSummary, AccordionDetails, makeStyles, Typography, TextField, FormControlLabel, Checkbox, +} from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import EditItemView from '../EditItemView'; +import { useTranslation } from '../LocalizationProvider'; +import SelectField from '../form/SelectField'; +import { prefixString } from '../common/stringUtils'; +import useCommandAttributes from '../attributes/useCommandAttributes'; + +const useStyles = makeStyles(() => ({ + details: { + flexDirection: 'column', + }, +})); + +const CommandPage = () => { + const classes = useStyles(); + const t = useTranslation(); + + const availableAttributes = useCommandAttributes(t); + + const [item, setItem] = useState(); + const [attributes, setAttributes] = useState([]); + + useEffect(() => { + if (item && item.type) { + setAttributes(availableAttributes[item.type] || []); + } + }, [availableAttributes, item]); + + return ( + <EditItemView endpoint="commands" item={item} setItem={setItem}> + {item && ( + <Accordion defaultExpanded> + <AccordionSummary expandIcon={<ExpandMoreIcon />}> + <Typography variant="subtitle1"> + {t('sharedRequired')} + </Typography> + </AccordionSummary> + <AccordionDetails className={classes.details}> + <TextField + margin="normal" + value={item.description || ''} + onChange={(event) => setItem({ ...item, description: event.target.value })} + label={t('sharedDescription')} + variant="filled" + /> + <SelectField + margin="normal" + value={item.type || 'custom'} + emptyValue={null} + onChange={(e) => setItem({ ...item, type: e.target.value, attributes: {} })} + endpoint="/api/commands/types" + keyGetter={(it) => it.type} + titleGetter={(it) => t(prefixString('command', it.type))} + label={t('sharedType')} + variant="filled" + /> + {attributes.map((attribute) => ( + <TextField + margin="normal" + value={item.attributes[attribute.key]} + onChange={(e) => { + const updateItem = { ...item, attributes: { ...item.attributes } }; + updateItem.attributes[attribute.key] = e.target.value; + setItem(updateItem); + }} + label={attribute.name} + variant="filled" + /> + ))} + <FormControlLabel + control={<Checkbox checked={item.textChannel} onChange={(event) => setItem({ ...item, textChannel: event.target.checked })} />} + label={t('commandSendSms')} + /> + </AccordionDetails> + </Accordion> + )} + </EditItemView> + ); +}; + +export default CommandPage; diff --git a/modern/src/settings/CommandsPage.js b/modern/src/settings/CommandsPage.js new file mode 100644 index 00000000..e8422467 --- /dev/null +++ b/modern/src/settings/CommandsPage.js @@ -0,0 +1,69 @@ +import React, { useState } from 'react'; +import { + TableContainer, Table, TableRow, TableCell, TableHead, TableBody, makeStyles, IconButton, +} from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { useEffectAsync } from '../reactHelper'; +import EditCollectionView from '../EditCollectionView'; +import OptionsLayout from './OptionsLayout'; +import { useTranslation } from '../LocalizationProvider'; +import { formatBoolean } from '../common/formatter'; +import { prefixString } from '../common/stringUtils'; + +const useStyles = makeStyles((theme) => ({ + columnAction: { + width: theme.spacing(1), + padding: theme.spacing(0, 1), + }, +})); + +const CommandsView = ({ updateTimestamp, onMenuClick }) => { + const classes = useStyles(); + const t = useTranslation(); + + const [items, setItems] = useState([]); + + useEffectAsync(async () => { + const response = await fetch('/api/commands'); + if (response.ok) { + setItems(await response.json()); + } + }, [updateTimestamp]); + + return ( + <TableContainer> + <Table> + <TableHead> + <TableRow> + <TableCell className={classes.columnAction} /> + <TableCell>{t('sharedDescription')}</TableCell> + <TableCell>{t('sharedType')}</TableCell> + <TableCell>{t('commandSendSms')}</TableCell> + </TableRow> + </TableHead> + <TableBody> + {items.map((item) => ( + <TableRow key={item.id}> + <TableCell className={classes.columnAction} padding="none"> + <IconButton onClick={(event) => onMenuClick(event.currentTarget, item.id)}> + <MoreVertIcon /> + </IconButton> + </TableCell> + <TableCell>{item.description}</TableCell> + <TableCell>{t(prefixString('command', item.type))}</TableCell> + <TableCell>{formatBoolean(item.textChannel, t)}</TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + ); +}; + +const CommandsPage = () => ( + <OptionsLayout> + <EditCollectionView content={CommandsView} editPath="/settings/command" endpoint="commands" /> + </OptionsLayout> +); + +export default CommandsPage; diff --git a/modern/src/settings/ComputedAttributePage.js b/modern/src/settings/ComputedAttributePage.js index e8efd096..99bbfadb 100644 --- a/modern/src/settings/ComputedAttributePage.js +++ b/modern/src/settings/ComputedAttributePage.js @@ -40,7 +40,7 @@ const ComputedAttributePage = () => { }; return ( - <EditItemView endpoint="/attributes/computed" item={item} setItem={setItem}> + <EditItemView endpoint="attributes/computed" item={item} setItem={setItem}> {item && ( <Accordion defaultExpanded> diff --git a/modern/src/settings/OptionsLayout.js b/modern/src/settings/OptionsLayout.js index e588cafb..f2d8c5da 100644 --- a/modern/src/settings/OptionsLayout.js +++ b/modern/src/settings/OptionsLayout.js @@ -20,6 +20,7 @@ import BuildIcon from '@material-ui/icons/Build'; import PeopleIcon from '@material-ui/icons/People'; import BarChartIcon from '@material-ui/icons/BarChart'; import TodayIcon from '@material-ui/icons/Today'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import SideNav from '../components/SideNav'; import NavBar from '../components/NavBar'; @@ -79,14 +80,15 @@ const OptionsLayout = ({ children }) => { ], [t]); const mainRoutes = useMemo(() => [ + { name: t('sharedNotifications'), href: '/settings/notifications', icon: <NotificationsIcon /> }, { name: t('settingsUser'), href: `/user/${userId}`, icon: <PersonIcon /> }, { name: t('sharedGeofences'), href: '/geofences', icon: <CreateIcon /> }, - { name: t('sharedNotifications'), href: '/settings/notifications', icon: <NotificationsIcon /> }, { name: t('settingsGroups'), href: '/settings/groups', icon: <FolderIcon /> }, { name: t('sharedDrivers'), href: '/settings/drivers', icon: <PersonIcon /> }, { name: t('sharedCalendars'), href: '/settings/calendars', icon: <TodayIcon /> }, { name: t('sharedComputedAttributes'), href: '/settings/attributes', icon: <StorageIcon /> }, { name: t('sharedMaintenance'), href: '/settings/maintenances', icon: <BuildIcon /> }, + { name: t('sharedSavedCommands'), href: '/settings/commands', icon: <ExitToAppIcon /> }, ], [t, userId]); const routes = useMemo(() => [...mainRoutes, ...(admin ? adminRoutes : [])], [mainRoutes, admin, adminRoutes]); |