aboutsummaryrefslogtreecommitdiff
path: root/modern
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-06-26 19:11:48 -0700
committerAnton Tananaev <anton@traccar.org>2022-06-26 19:11:48 -0700
commitff8e5ad793352facbd6200d0b059a0c970b6da84 (patch)
tree7e93dec679593e856b33bcebafa04cb34b22c1cd /modern
parent5d9a9175f828d016d8ff28ae69c0bd4fa80f2fee (diff)
downloadtrackermap-web-ff8e5ad793352facbd6200d0b059a0c970b6da84.tar.gz
trackermap-web-ff8e5ad793352facbd6200d0b059a0c970b6da84.tar.bz2
trackermap-web-ff8e5ad793352facbd6200d0b059a0c970b6da84.zip
Computed attributes testing
Diffstat (limited to 'modern')
-rw-r--r--modern/src/settings/ComputedAttributePage.js181
1 files changed, 117 insertions, 64 deletions
diff --git a/modern/src/settings/ComputedAttributePage.js b/modern/src/settings/ComputedAttributePage.js
index c9170650..287a4340 100644
--- a/modern/src/settings/ComputedAttributePage.js
+++ b/modern/src/settings/ComputedAttributePage.js
@@ -11,6 +11,8 @@ import {
TextField,
createFilterOptions,
Autocomplete,
+ Button,
+ Snackbar,
} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
@@ -18,6 +20,9 @@ import EditItemView from './components/EditItemView';
import { useTranslation } from '../common/components/LocalizationProvider';
import usePositionAttributes from '../common/attributes/usePositionAttributes';
import SettingsMenu from './components/SettingsMenu';
+import SelectField from '../common/components/SelectField';
+import { useCatch } from '../reactHelper';
+import { snackBarDurationLongMs } from '../common/util/duration';
const useStyles = makeStyles((theme) => ({
details: {
@@ -35,6 +40,8 @@ const ComputedAttributePage = () => {
const positionAttributes = usePositionAttributes(t);
const [item, setItem] = useState();
+ const [deviceId, setDeviceId] = useState();
+ const [result, setResult] = useState();
const options = Object.entries(positionAttributes).filter(([, value]) => !value.property).map(([key, value]) => ({
key,
@@ -46,6 +53,21 @@ const ComputedAttributePage = () => {
stringify: (option) => option.name,
});
+ const testAttribute = useCatch(async () => {
+ const query = new URLSearchParams({ deviceId });
+ const url = `/api/attributes/computed/test?${query.toString()}`;
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(item),
+ });
+ if (response.ok) {
+ setResult(await response.text());
+ } else {
+ throw Error(await response.text());
+ }
+ });
+
const validate = () => item && item.description && item.expression;
return (
@@ -58,71 +80,102 @@ const ComputedAttributePage = () => {
breadcrumbs={['settingsTitle', 'sharedComputedAttribute']}
>
{item && (
- <Accordion defaultExpanded>
- <AccordionSummary expandIcon={<ExpandMoreIcon />}>
- <Typography variant="subtitle1">
- {t('sharedRequired')}
- </Typography>
- </AccordionSummary>
- <AccordionDetails className={classes.details}>
- <TextField
- value={item.description || ''}
- onChange={(event) => setItem({ ...item, description: event.target.value })}
- label={t('sharedDescription')}
- />
- <Autocomplete
- value={options.find((option) => option.key === item.attribute) || item.attribute}
- onChange={(_, option) => {
- const attribute = option ? option.key || option : null;
- if (option && option.type) {
- setItem({ ...item, attribute, type: option.type });
- } else {
- setItem({ ...item, attribute });
- }
- }}
- filterOptions={(options, params) => {
- const filtered = filter(options, params);
- if (params.inputValue) {
- filtered.push({
- key: params.inputValue,
- name: params.inputValue,
- });
- }
- return filtered;
- }}
- options={options}
- getOptionLabel={(option) => option.name || option}
- renderOption={(props, option) => (
- <li {...props}>
- {option.name}
- </li>
- )}
- renderInput={(params) => (
- <TextField {...params} label={t('sharedAttribute')} />
- )}
- freeSolo
- />
- <TextField
- value={item.expression || ''}
- onChange={(event) => setItem({ ...item, expression: event.target.value })}
- label={t('sharedExpression')}
- multiline
- rows={4}
- />
- <FormControl disabled={item.attribute in positionAttributes}>
- <InputLabel>{t('sharedType')}</InputLabel>
- <Select
- label={t('sharedType')}
- value={item.type || ''}
- onChange={(event) => setItem({ ...item, type: event.target.value })}
+ <>
+ <Accordion defaultExpanded>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedRequired')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <TextField
+ value={item.description || ''}
+ onChange={(e) => setItem({ ...item, description: e.target.value })}
+ label={t('sharedDescription')}
+ />
+ <Autocomplete
+ value={options.find((option) => option.key === item.attribute) || item.attribute}
+ onChange={(_, option) => {
+ const attribute = option ? option.key || option : null;
+ if (option && option.type) {
+ setItem({ ...item, attribute, type: option.type });
+ } else {
+ setItem({ ...item, attribute });
+ }
+ }}
+ filterOptions={(options, params) => {
+ const filtered = filter(options, params);
+ if (params.inputValue) {
+ filtered.push({
+ key: params.inputValue,
+ name: params.inputValue,
+ });
+ }
+ return filtered;
+ }}
+ options={options}
+ getOptionLabel={(option) => option.name || option}
+ renderOption={(props, option) => (
+ <li {...props}>
+ {option.name}
+ </li>
+ )}
+ renderInput={(params) => (
+ <TextField {...params} label={t('sharedAttribute')} />
+ )}
+ freeSolo
+ />
+ <TextField
+ value={item.expression || ''}
+ onChange={(e) => setItem({ ...item, expression: e.target.value })}
+ label={t('sharedExpression')}
+ multiline
+ rows={4}
+ />
+ <FormControl disabled={item.attribute in positionAttributes}>
+ <InputLabel>{t('sharedType')}</InputLabel>
+ <Select
+ label={t('sharedType')}
+ value={item.type || ''}
+ onChange={(e) => setItem({ ...item, type: e.target.value })}
+ >
+ <MenuItem value="string">{t('sharedTypeString')}</MenuItem>
+ <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem>
+ <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem>
+ </Select>
+ </FormControl>
+ </AccordionDetails>
+ </Accordion>
+ <Accordion>
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography variant="subtitle1">
+ {t('sharedTest')}
+ </Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.details}>
+ <SelectField
+ value={deviceId || 0}
+ onChange={(e) => setDeviceId(Number(e.target.value))}
+ endpoint="/api/devices"
+ label={t('sharedDevice')}
+ />
+ <Button
+ variant="outlined"
+ color="primary"
+ onClick={testAttribute}
+ disabled={!deviceId}
>
- <MenuItem value="string">{t('sharedTypeString')}</MenuItem>
- <MenuItem value="number">{t('sharedTypeNumber')}</MenuItem>
- <MenuItem value="boolean">{t('sharedTypeBoolean')}</MenuItem>
- </Select>
- </FormControl>
- </AccordionDetails>
- </Accordion>
+ {t('sharedTestExpression')}
+ </Button>
+ <Snackbar
+ open={!!result}
+ onClose={() => setResult(null)}
+ autoHideDuration={snackBarDurationLongMs}
+ message={result}
+ />
+ </AccordionDetails>
+ </Accordion>
+ </>
)}
</EditItemView>
);