* ============LICENSE_END==========================================================================
*/
-import React from 'react';
+import React, { useState } from 'react';
import { RouteComponentProps, withRouter } from 'react-router-dom';
-import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles';
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect";
import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../framework/src/components/material-table";
import { Loader } from "../../../../framework/src/components/material-ui/loader";
-
-import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator } from "../actions/deviceActions";
-import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion } from "../models/uiModels";
-
-import Fab from '@material-ui/core/Fab';
-import AddIcon from '@material-ui/icons/Add';
-import ArrowBack from '@material-ui/icons/ArrowBack';
-import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
-import SaveIcon from '@material-ui/icons/Save';
-import EditIcon from '@material-ui/icons/Edit';
-import Tooltip from "@material-ui/core/Tooltip";
-import TextField from "@material-ui/core/TextField";
-import FormControl from "@material-ui/core/FormControl";
-import IconButton from "@material-ui/core/IconButton";
-
-import InputAdornment from "@material-ui/core/InputAdornment";
-import InputLabel from "@material-ui/core/InputLabel";
-import Select from "@material-ui/core/Select";
-import MenuItem from "@material-ui/core/MenuItem";
-import Breadcrumbs from "@material-ui/core/Breadcrumbs";
-import Link from "@material-ui/core/Link";
-import FormHelperText from '@material-ui/core/FormHelperText';
-
+import { renderObject } from '../../../../framework/src/components/objectDump';
+
+import { DisplayModeType } from '../handlers/viewDescriptionHandler';
+import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator, removeElementActionAsyncCreator, executeRpcActionAsyncCreator } from "../actions/deviceActions";
+import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion, isViewElementRpc, ViewElementRpc, isViewElementEmpty, isViewElementDate } from "../models/uiModels";
+
+import { getAccessPolicyByUrl } from "../../../../framework/src/services/restService";
+
+import Fab from '@mui/material/Fab';
+import AddIcon from '@mui/icons-material/Add';
+import PostAdd from '@mui/icons-material/PostAdd';
+import ArrowBack from '@mui/icons-material/ArrowBack';
+import RemoveIcon from '@mui/icons-material/RemoveCircleOutline';
+import SaveIcon from '@mui/icons-material/Save';
+import EditIcon from '@mui/icons-material/Edit';
+import Tooltip from "@mui/material/Tooltip";
+import FormControl from "@mui/material/FormControl";
+import IconButton from "@mui/material/IconButton";
+
+import InputLabel from "@mui/material/InputLabel";
+import Select from "@mui/material/Select";
+import MenuItem from "@mui/material/MenuItem";
+import Breadcrumbs from "@mui/material/Breadcrumbs";
+import Button from '@mui/material/Button';
+import Link from "@mui/material/Link";
+import Accordion from '@mui/material/Accordion';
+import AccordionSummary from '@mui/material/AccordionSummary';
+import AccordionDetails from '@mui/material/AccordionDetails';
+import Typography from '@mui/material/Typography';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+
+
+import { BaseProps } from '../components/baseProps';
import { UIElementReference } from '../components/uiElementReference';
import { UiElementNumber } from '../components/uiElementNumber';
import { UiElementString } from '../components/uiElementString';
import { UiElementBoolean } from '../components/uiElementBoolean';
import { UiElementSelection } from '../components/uiElementSelection';
import { UIElementUnion } from '../components/uiElementUnion';
+import { UiElementLeafList } from '../components/uiElementLeafList';
+
+import { useConfirm } from 'material-ui-confirm';
+import restService from '../services/restServices';
const styles = (theme: Theme) => createStyles({
header: {
},
outer: {
"flex": "1",
- "heigh": "100%",
+ "height": "100%",
"display": "flex",
"alignItems": "center",
"justifyContent": "center",
},
inner: {
+ },
+ container: {
+ "height": "100%",
+ "display": "flex",
+ "flexDirection": "column",
},
"icon": {
"marginRight": theme.spacing(0.5),
},
},
},
+ uiView: {
+ overflowY: "auto",
+ },
section: {
padding: "15px",
borderBottom: `2px solid ${theme.palette.divider}`,
},
verificationElements: {
width: 485, marginLeft: 20, marginRight: 20
+ },
+ heading: {
+ fontSize: theme.typography.pxToRem(15),
+ fontWeight: theme.typography.fontWeightRegular,
+ },
+ moduleCollection: {
+ marginTop: "16px",
+ overflow: "auto",
+ },
+ objectReult: {
+ overflow: "auto"
}
});
vPath: state.configuration.viewDescription.vPath,
nodeId: state.configuration.deviceDescription.nodeId,
viewData: state.configuration.viewDescription.viewData,
- viewSpecification: state.configuration.viewDescription.viewSpecification,
- displayAsList: state.configuration.viewDescription.displayAsList,
- keyProperty: state.configuration.viewDescription.keyProperty,
+ outputData: state.configuration.viewDescription.outputData,
+ displaySpecification: state.configuration.viewDescription.displaySpecification,
});
const mapDispatch = (dispatcher: IDispatcher) => ({
onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
+ removeElement: (vPath: string) => dispatcher.dispatch(removeElementActionAsyncCreator(vPath)),
+ executeRpc: (vPath: string, data: any) => dispatcher.dispatch(executeRpcActionAsyncCreator(vPath, data)),
});
const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
}
+type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any
+const AccordionSummaryExt: React.FC<GetStatelessComponentProps<typeof AccordionSummary>> = (props) => {
+ const [disabled, setDisabled] = useState(true);
+ const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
+ if (ev.button === 1) {
+ setDisabled(!disabled);
+ ev.preventDefault();
+ }
+ };
+ return (
+ <div onMouseDown={onMouseDown} >
+ <AccordionSummary {...{ ...props, disabled: props.disabled && disabled }} />
+ </div>
+ );
+};
+
const OldProps = Symbol("OldProps");
class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationComponentProps, ConfigurationApplicationComponentState> {
}
}
+ private static getChoisesFromElements = (elements: { [name: string]: ViewElement }, viewData: any) => {
+ return Object.keys(elements).reduce((acc, cur) => {
+ const elm = elements[cur];
+ if (isViewElementChoise(elm)) {
+ const caseKeys = Object.keys(elm.cases);
+
+ // find the right case for this choise, use the first one with data, at least use index 0
+ const selectedCase = caseKeys.find(key => {
+ const caseElm = elm.cases[key];
+ return Object.keys(caseElm.elements).some(caseElmKey => {
+ const caseElmElm = caseElm.elements[caseElmKey];
+ return viewData[caseElmElm.label] !== undefined || viewData[caseElmElm.id] != undefined;
+ });
+ }) || caseKeys[0];
+
+ // extract all data of the active case
+ const caseElements = elm.cases[selectedCase].elements;
+ const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
+ const dataElm = caseElements[dataCur];
+ if (isViewElementEmpty(dataElm)) {
+ dataAcc[dataElm.label] = null;
+ } else if (viewData[dataElm.label] !== undefined) {
+ dataAcc[dataElm.label] = viewData[dataElm.label];
+ } else if (viewData[dataElm.id] !== undefined) {
+ dataAcc[dataElm.id] = viewData[dataElm.id];
+ }
+ return dataAcc;
+ }, {} as { [name: string]: any });
+
+ acc[elm.id] = {
+ selectedCase,
+ data,
+ };
+ }
+ return acc;
+ }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
+ }
+
static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
- const isNew: boolean = nextProps.vPath ?.endsWith("[]") || false;
+ const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
const state = {
...prevState,
isNew: isNew,
editMode: isNew,
viewData: nextProps.viewData || null,
[OldProps]: nextProps,
- choises: nextProps.viewSpecification && Object.keys(nextProps.viewSpecification.elements).reduce((acc, cur) => {
- const elm = nextProps.viewSpecification.elements[cur];
- if (isViewElementChoise(elm)) {
- const caseKeys = Object.keys(elm.cases);
-
- // find the right case for this choise, use the first one with data, at least use index 0
- const selectedCase = caseKeys.find(key => {
- const caseElm = elm.cases[key];
- return Object.keys(caseElm.elements).some(caseElmKey => {
- const caseElmElm = caseElm.elements[caseElmKey];
- return nextProps.viewData[caseElmElm.label] != null || nextProps.viewData[caseElmElm.id] != null;
- });
- }) || caseKeys[0];
-
- // extract all data of the active case
- const caseElements = elm.cases[selectedCase].elements;
- const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
- const dataElm = caseElements[dataCur];
- if (nextProps.viewData[dataElm.label] !== undefined) {
- dataAcc[dataElm.label] = nextProps.viewData[dataElm.label];
- } else if (nextProps.viewData[dataElm.id] !== undefined) {
- dataAcc[dataElm.id] = nextProps.viewData[dataElm.id];
- }
- return dataAcc;
- }, {} as { [name: string]: any });
-
- acc[elm.id] = {
- selectedCase,
- data,
- };
- }
- return acc;
- }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
+ choises: nextProps.displaySpecification.displayMode === DisplayModeType.doNotDisplay
+ || nextProps.displaySpecification.displayMode === DisplayModeType.displayAsMessage
+ ? null
+ : nextProps.displaySpecification.displayMode === DisplayModeType.displayAsRPC
+ ? nextProps.displaySpecification.inputViewSpecification && ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.inputViewSpecification.elements, nextProps.viewData) || []
+ : ConfigurationApplicationComponent.getChoisesFromElements(nextProps.displaySpecification.viewSpecification.elements, nextProps.viewData)
}
return state;
}
});
}
- private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
- const isKey = (uiElement.label === keyProperty);
- const canEdit = editMode && (isNew || (uiElement.config && !isKey));
- if (isViewElementSelection(uiElement)) {
+ private collectData = (elements: { [name: string]: ViewElement }) => {
+ // ensure only active choises will be contained
+ const viewData: { [key: string]: any } = { ...this.state.viewData };
+ const choiseKeys = Object.keys(elements).filter(elmKey => isViewElementChoise(elements[elmKey]));
+ const elementsToRemove = choiseKeys.reduce((acc, curChoiceKey) => {
+ const currentChoice = elements[curChoiceKey] as ViewElementChoise;
+ const selectedCase = this.state.choises[curChoiceKey].selectedCase;
+ Object.keys(currentChoice.cases).forEach(caseKey => {
+ const caseElements = currentChoice.cases[caseKey].elements;
+ if (caseKey === selectedCase) {
+ Object.keys(caseElements).forEach(caseElementKey => {
+ const elm = caseElements[caseElementKey];
+ if (isViewElementEmpty(elm)) {
+ // insert null for all empty elements
+ viewData[elm.id] = null;
+ }
+ });
+ return;
+ };
+ Object.keys(caseElements).forEach(caseElementKey => {
+ acc.push(caseElements[caseElementKey]);
+ });
+ });
+ return acc;
+ }, [] as ViewElement[]);
- return <UiElementSelection
- key={uiElement.id}
- inputValue={viewData[uiElement.id] || ''}
- value={uiElement}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
- />
+ return viewData && Object.keys(viewData).reduce((acc, cur) => {
+ if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
+ acc[cur] = viewData[cur];
+ }
+ return acc;
+ }, {} as { [key: string]: any });
+ }
- } else if (isViewElementBoolean(uiElement)) {
- return <UiElementBoolean
- key={uiElement.id}
- inputValue={viewData[uiElement.id] || ''}
- value={uiElement}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+ private isPolicyViewElementForbidden = (element: ViewElement, dataPath: string): boolean => {
+ const policy = getAccessPolicyByUrl(`${dataPath}/${element.id}`);
+ return !(policy.GET && policy.POST);
+ }
- } else if (isViewElementString(uiElement)) {
- return <UiElementString
- key={uiElement.id}
- inputValue={viewData[uiElement.id] || ''}
- value={uiElement}
- isKey={isKey}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+ private isPolicyModuleForbidden = (moduleName: string, dataPath: string): boolean => {
+ const policy = getAccessPolicyByUrl(`${dataPath}/${moduleName}`);
+ return !(policy.GET && policy.POST);
+ }
+ private getEditorForViewElement = (uiElement: ViewElement): (null | React.ComponentType<BaseProps<any>>) => {
+ if (isViewElementEmpty(uiElement)) {
+ return null;
+ } else if (isViewElementSelection(uiElement)) {
+ return UiElementSelection;
+ } else if (isViewElementBoolean(uiElement)) {
+ return UiElementBoolean;
+ } else if (isViewElementString(uiElement)) {
+ return UiElementString;
+ } else if (isViewElementDate(uiElement)) {
+ return UiElementString;
} else if (isViewElementNumber(uiElement)) {
- return <UiElementNumber
- key={uiElement.id}
- value={uiElement}
- inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+ return UiElementNumber;
} else if (isViewElementUnion(uiElement)) {
- return <UIElementUnion
- key={uiElement.id}
- isKey={false}
- value={uiElement}
- inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
- readOnly={!canEdit}
- disabled={editMode && !canEdit}
- onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
- }
- else {
+ return UIElementUnion;
+ } else {
if (process.env.NODE_ENV !== "production") {
console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
}
return null;
}
+ }
+
+ private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ const isKey = (uiElement.label === keyProperty);
+ const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+
+ // do not show elements w/o any value from the backend
+ if (viewData[uiElement.id] == null && !editMode) {
+ return null;
+ } else if (isViewElementEmpty(uiElement)) {
+ return null;
+ } else if (uiElement.isList) {
+ /* element is a leaf-list */
+ return <UiElementLeafList
+ key={uiElement.id}
+ inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]}
+ value={uiElement}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+ getEditorForViewElement={this.getEditorForViewElement}
+ />;
+ } else {
+ const Element = this.getEditorForViewElement(uiElement);
+ return Element != null
+ ? (
+ <Element
+ key={uiElement.id}
+ isKey={isKey}
+ inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ value={uiElement}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+ />)
+ : null;
+ }
};
// private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
const canEdit = editMode && (isNew || (uiElement.config && !isKey));
if (isViewElementChoise(uiElement)) {
- const subElements = currentCase ?.elements;
+ const subElements = currentCase?.elements;
return (
<>
- <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <FormControl variant="standard" key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
<InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
- <Select
+ <Select variant="standard"
+ aria-label={uiElement.label + '-selection'}
required={!!uiElement.mandatory}
onChange={(e) => {
if (currentChoise.selectedCase === e.target.value) {
Object.keys(uiElement.cases).map(caseKey => {
const caseElm = uiElement.cases[caseKey];
return (
- <MenuItem key={caseElm.id} title={caseElm.description} value={caseKey}>{caseElm.label}</MenuItem>
+ <MenuItem key={caseElm.id} value={caseKey} aria-label={caseKey}><Tooltip title={caseElm.description || ''}><div style={{ width: "100%" }}>{caseElm.label}</div></Tooltip></MenuItem>
);
})
}
}
};
- private renderUIView = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ private renderUIView = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
const { classes } = this.props;
+
+
const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
if (keyProperty) {
// if (vsA.label === vsB.label) return 0;
acc.references.push(elm);
} else if (isViewElementChoise(elm)) {
acc.choises.push(elm);
+ } else if (isViewElementRpc(elm)) {
+ acc.rpcs.push(elm);
} else {
acc.elements.push(elm);
}
return acc;
- }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[] });
+ }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] });
sections.elements = sections.elements.sort(orderFunc);
return (
- <>
+ <div className={classes.uiView}>
<div className={classes.section} />
{sections.elements.length > 0
? (
? (
<div className={classes.section}>
{sections.references.map(element => (
- <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+ <UIElementReference key={element.id} element={element} disabled={editMode || this.isPolicyViewElementForbidden(element, dataPath)} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
))}
</div>
) : null
</div>
) : null
}
- </>
+ {sections.rpcs.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.rpcs.map(element => (
+ <UIElementReference key={element.id} element={element} disabled={editMode || this.isPolicyViewElementForbidden(element, dataPath)} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+ ))}
+ </div>
+ ) : null
+ }
+ </div>
);
};
- private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
+ private renderUIViewSelector = (viewSpecification: ViewSpecification, dataPath: string, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ const { classes } = this.props;
+ // group by module name
+ const modules = Object.keys(viewSpecification.elements).reduce<{ [key: string]: ViewSpecification }>((acc, cur) => {
+ const elm = viewSpecification.elements[cur];
+ const moduleView = (acc[elm.module] = acc[elm.module] || { ...viewSpecification, elements: {} });
+ moduleView.elements[cur] = elm;
+ return acc;
+ }, {});
+
+ const moduleKeys = Object.keys(modules).sort();
+
+ return (
+ <div className={classes.moduleCollection}>
+ {
+ moduleKeys.map(key => {
+ const moduleView = modules[key];
+ return (
+ <Accordion key={key} defaultExpanded={moduleKeys.length < 4} aria-label={key + '-panel'} >
+ <AccordionSummaryExt expandIcon={<ExpandMoreIcon />} aria-controls={`content-${key}`} id={`header-${key}`} disabled={this.isPolicyModuleForbidden(`${key}:`, dataPath)} >
+ <Typography className={classes.heading}>{key}</Typography>
+ </AccordionSummaryExt>
+ <AccordionDetails>
+ {this.renderUIView(moduleView, dataPath, viewData, keyProperty, editMode, isNew)}
+ </AccordionDetails>
+ </Accordion>
+ );
+ })
+ }
+ </div>
+ );
+ };
+
+ private renderUIViewList(listSpecification: ViewSpecification, dataPath: string, listKeyProperty: string, apiDocPath: string, listData: { [key: string]: any }[]) {
const listElements = listSpecification.elements;
+ const apiDocPathCreate = apiDocPath ? `${location.origin}${apiDocPath
+ .replace("$$$standard$$$", "topology-netconfnode%20resources%20-%20RestConf%20RFC%208040")
+ .replace("$$$action$$$", "put")}${listKeyProperty ? `_${listKeyProperty.replace(/[\/=\-\:]/g, '_')}_` : '' }` : undefined;
+
+ const config = listSpecification.config && listKeyProperty; // We can not configure a list with no key.
const navigate = (path: string) => {
this.props.history.push(`${this.props.match.url}${path}`);
};
const addNewElementAction = {
- icon: AddIcon, tooltip: 'Add', onClick: () => {
+ icon: AddIcon,
+ tooltip: 'Add',
+ ariaLabel:'add-element',
+ onClick: () => {
navigate("[]"); // empty key means new element
- }
+ },
+ disabled: !config,
};
- const { classes } = this.props;
+ const addWithApiDocElementAction = {
+ icon: PostAdd,
+ tooltip: 'Add',
+ ariaLabel:'add-element-via-api-doc',
+ onClick: () => {
+ window.open(apiDocPathCreate, '_blank');
+ },
+ disabled: !config,
+ };
+
+ const { classes, removeElement } = this.props;
+
+ const DeleteIconWithConfirmation: React.FC<{disabled?: boolean, rowData: { [key: string]: any }, onReload: () => void }> = (props) => {
+ const confirm = useConfirm();
+
+ return (
+ <Tooltip disableInteractive title={"Remove"} >
+ <IconButton
+ disabled={props.disabled}
+ className={classes.button}
+ aria-label="remove-element-button"
+ onClick={async (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ confirm({ title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" }, cancellationButtonProps: { color:"inherit" } })
+ .then(() => {
+ let keyId = "";
+ if (listKeyProperty && listKeyProperty.split(" ").length > 1) {
+ keyId += listKeyProperty.split(" ").map(id => props.rowData[id]).join(",");
+ } else {
+ keyId = props.rowData[listKeyProperty];
+ }
+ return removeElement(`${this.props.vPath}[${keyId}]`)
+ }).then(props.onReload);
+ }}
+ size="large">
+ <RemoveIcon />
+ </IconButton>
+ </Tooltip>
+ );
+ }
return (
- <SelectElementTable idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
+ <SelectElementTable stickyHeader idProperty={listKeyProperty} tableId={null} rows={listData} customActionButtons={apiDocPathCreate ? [addNewElementAction, addWithApiDocElementAction] : [addNewElementAction]} columns={
Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
const elm = listElements[cur];
if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
if (elm.label !== listKeyProperty) {
- acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ acc.push(elm.uiType === "boolean"
+ ? { property: elm.label, type: ColumnType.boolean }
+ : elm.uiType === "date"
+ ? { property: elm.label, type: ColumnType.date }
+ : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
} else {
- acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ acc.unshift(elm.uiType === "boolean"
+ ? { property: elm.label, type: ColumnType.boolean }
+ : elm.uiType === "date"
+ ? { property: elm.label, type: ColumnType.date }
+ : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
}
}
return acc;
}, []).concat([{
- property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (row => {
+ property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (({ rowData }) => {
return (
- <Tooltip title={"Remove"} >
- <IconButton className={classes.button} onClick={event => {
-
- }} >
- <RemoveIcon />
- </IconButton>
- </Tooltip>
- )
+ <DeleteIconWithConfirmation disabled={!config} rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath)} />
+ );
})
}])
} onHandleClick={(ev, row) => {
ev.preventDefault();
- navigate(`[${row[listKeyProperty]}]`);
+ let keyId = ""
+ if (listKeyProperty && listKeyProperty.split(" ").length > 1) {
+ keyId += listKeyProperty.split(" ").map(id => row[id]).join(",");
+ } else {
+ keyId = row[listKeyProperty];
+ }
+ listKeyProperty && navigate(`[${encodeURIComponent(keyId)}]`); // Do not navigate without key.
}} ></SelectElementTable>
);
}
+ private renderUIViewRPC(inputViewSpecification: ViewSpecification | undefined, dataPath: string, inputViewData: { [key: string]: any }, outputViewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) {
+ const { classes } = this.props;
+
+ const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
+ if (keyProperty) {
+ // if (vsA.label === vsB.label) return 0;
+ if (vsA.label === keyProperty) return -1;
+ if (vsB.label === keyProperty) return +1;
+ }
+
+ // if (vsA.uiType === vsB.uiType) return 0;
+ // if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
+ // if (vsA.uiType === "object") return +1;
+ return -1;
+ };
+
+ const sections = inputViewSpecification && Object.keys(inputViewSpecification.elements).reduce((acc, cur) => {
+ const elm = inputViewSpecification.elements[cur];
+ if (isViewElementObjectOrList(elm)) {
+ console.error("Object should not appear in RPC view !");
+ } else if (isViewElementChoise(elm)) {
+ acc.choises.push(elm);
+ } else if (isViewElementRpc(elm)) {
+ console.error("RPC should not appear in RPC view !");
+ } else {
+ acc.elements.push(elm);
+ }
+ return acc;
+ }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] })
+ || { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[], rpcs: [] as ViewElementRpc[] };
+
+ sections.elements = sections.elements.sort(orderFunc);
+
+ return (
+ <>
+ <div className={classes.section} />
+ { sections.elements.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.elements.map(element => this.renderUIElement(element, inputViewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : null
+ }
+ { sections.choises.length > 0
+ ? (
+ <div className={classes.section}>
+ {sections.choises.map(element => this.renderUIChoise(element, inputViewData, keyProperty, editMode, isNew))}
+ </div>
+ ) : null
+ }
+ <Button color="inherit" onClick={() => {
+ const resultingViewData = inputViewSpecification && this.collectData(inputViewSpecification.elements);
+ this.props.executeRpc(this.props.vPath!, resultingViewData);
+ }} >Exec</Button>
+ <div className={classes.objectReult}>
+ {outputViewData !== undefined
+ ? renderObject(outputViewData)
+ : null
+ }
+ </div>
+ </>
+ );
+ };
+
private renderBreadCrumps() {
const { editMode } = this.state;
- const { viewSpecification, displayAsList } = this.props;
- const { vPath, match: { url, path }, nodeId } = this.props;
+ const { displaySpecification, vPath, nodeId } = this.props;
const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
let lastPath = `/configuration`;
let basePath = `/configuration/${nodeId}`;
return (
<div className={this.props.classes.header}>
<div>
- <Breadcrumbs aria-label="breadcrumb">
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(lastPath);
- }}>Back</Link>
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(`/configuration/${nodeId}`);
- }}><span>{nodeId}</span></Link>
+ <Breadcrumbs aria-label="breadcrumbs">
+ <Link underline="hover" color="inherit" href="#" aria-label="back-breadcrumb"
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(lastPath);
+ }}>Back</Link>
+ <Link underline="hover" color="inherit" href="#"
+ aria-label={nodeId + '-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(`/configuration/${nodeId}`);
+ }}><span>{nodeId}</span></Link>
{
pathParts.map(([prop, key], ind) => {
const path = `${basePath}/${prop}`;
const keyPath = key && `${basePath}/${prop}[${key}]`;
+ const propTitle = prop.replace(/^[^:]+:/, "");
const ret = (
<span key={ind}>
- <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
- ev.preventDefault();
- this.props.history.push(path);
- }}><span>{prop.replace(/^[^:]+:/, "")}</span></Link>
- {
- keyPath && <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ <Link underline="hover" color="inherit" href="#"
+ aria-label={propTitle + '-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
ev.preventDefault();
- this.props.history.push(keyPath);
- }}>{`[${key}]`}</Link> || null
+ this.props.history.push(path);
+ }}><span>{propTitle}</span></Link>
+ {
+ keyPath && <Link underline="hover" color="inherit" href="#"
+ aria-label={key + '-breadcrumb'}
+ onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(keyPath);
+ }}>{`[${key && key.replace(/\%2C/g, ",")}]`}</Link> || null
}
</span>
);
</Breadcrumbs>
</div>
{this.state.editMode && (
- <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={async () => {
- this.props.vPath && await this.props.reloadView(this.props.vPath);
+ <Fab color="secondary" aria-label="back-button" className={this.props.classes.fab} onClick={async () => {
+ this.props.vPath && (await this.props.reloadView(this.props.vPath));
this.setState({ editMode: false });
}} ><ArrowBack /></Fab>
) || null}
{ /* do not show edit if this is a list or it can't be edited */
- !displayAsList && viewSpecification.canEdit && (<div>
- <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={() => {
+ displaySpecification.displayMode === DisplayModeType.displayAsObject && displaySpecification.viewSpecification.canEdit && (<div>
+ <Fab color="secondary" aria-label={editMode ? 'save-button' : 'edit-button'} className={this.props.classes.fab} onClick={() => {
if (this.state.editMode) {
-
// ensure only active choises will be contained
- const choiseKeys = Object.keys(viewSpecification.elements).filter(elmKey => isViewElementChoise(viewSpecification.elements[elmKey]));
- const elementsToRemove = choiseKeys.reduce((acc, cur) => {
- const choise = viewSpecification.elements[cur] as ViewElementChoise;
- const selectedCase = this.state.choises[cur].selectedCase;
- Object.keys(choise.cases).forEach(caseKey => {
- if (caseKey === selectedCase) return;
- const caseElements = choise.cases[caseKey].elements;
- Object.keys(caseElements).forEach(caseElementKey => {
- acc.push(caseElements[caseElementKey]);
- });
- });
- return acc;
- }, [] as ViewElement[]);
-
- const viewData = this.state.viewData;
- const resultingViewData = viewData && Object.keys(viewData).reduce((acc, cur) => {
- if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
- acc[cur] = viewData[cur];
- }
- return acc;
- }, {} as { [key: string]: any });
-
+ const resultingViewData = this.collectData(displaySpecification.viewSpecification.elements);
this.props.onUpdateData(this.props.vPath!, resultingViewData);
}
this.setState({ editMode: !editMode });
}
return (
- <div>
- <SelectElementTable idProperty={listKeyProperty} rows={listData} columns={
+ <div className={this.props.classes.container}>
+ <SelectElementTable stickyHeader idProperty={listKeyProperty} tableId={null} rows={listData} columns={
Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
const elm = listSpecification.elements[cur];
if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
}
private renderValueEditor() {
- const { keyProperty, displayAsList, viewSpecification } = this.props;
+ const { displaySpecification: ds, outputData } = this.props;
const { viewData, editMode, isNew } = this.state;
return (
- <div>
+ <div className={this.props.classes.container}>
{this.renderBreadCrumps()}
- {displayAsList && viewData instanceof Array
- ? this.renderUIViewList(viewSpecification, keyProperty!, viewData)
- : this.renderUIView(viewSpecification, viewData!, keyProperty, editMode, isNew)
+ {ds.displayMode === DisplayModeType.doNotDisplay
+ ? null
+ : ds.displayMode === DisplayModeType.displayAsList && viewData instanceof Array
+ ? this.renderUIViewList(ds.viewSpecification, ds.dataPath!, ds.keyProperty!, ds.apidocPath!, viewData)
+ : ds.displayMode === DisplayModeType.displayAsRPC
+ ? this.renderUIViewRPC(ds.inputViewSpecification, ds.dataPath!, viewData!, outputData, undefined, true, false)
+ : ds.displayMode === DisplayModeType.displayAsMessage
+ ? this.renderMessage(ds.renderMessage)
+ : this.renderUIViewSelector(ds.viewSpecification, ds.dataPath!, viewData!, ds.keyProperty, editMode, isNew)
}
</div >
);
}
+ private renderMessage(renderMessage: string) {
+ return (
+ <div className={this.props.classes.container}>
+ <h4>{renderMessage}</h4>
+ </div>
+ );
+ }
+
private renderCollectingData() {
return (
<div className={this.props.classes.outer}>
<div className={this.props.classes.inner}>
<Loader />
- <h3>Collecting Data ...</h3>
+ <h3>Processing ...</h3>
</div>
</div>
);