diff options
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/views')
-rw-r--r-- | sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx new file mode 100644 index 000000000..5865c10e5 --- /dev/null +++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx @@ -0,0 +1,297 @@ +import * as React from 'react'; + +import { MaterialTable, ColumnType, MaterialTableCtorType } from '../../../../framework/src/components/material-table'; +import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect'; +import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore'; +import { IConnectAppStoreState } from '../../../connectApp/src/handlers/connectAppRootHandler'; +import { MountedNetworkElementType } from '../../../connectApp/src/models/mountedNetworkElements'; +import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions'; +import { Dispatch } from '../../../../framework/src/flux/store'; + +import TextField from '@material-ui/core/TextField'; +import { Tooltip, Button, FormControl, InputLabel, Select, MenuItem, InputAdornment } from '@material-ui/core'; +import Link from '@material-ui/core/Link'; + +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; + +import { ViewSpecification } from '../models/uiModels'; + +const NetworkElementTable = MaterialTable as MaterialTableCtorType<MountedNetworkElementType>; + +const mapProps = (state: IApplicationStoreState) => ({ + ...state.configuration, + avaliableDevices: state.connect.mountedNetworkElements.elements.filter(el => el.connectionStatus === "connected") +}); +const mapDisp = (dispatcher: IDispatcher) => ({ + navigateTo: (viewId: string, index?: string | number) => dispatcher.dispatch((dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { configuration: { nodeId, lpId, indexValues } } = getState(); + const newIndexValues = typeof index === 'number' && indexValues + ? indexValues.split('/').slice(0, index).join("/") + : indexValues + ? `${indexValues}${index ? `/${index}` : ''}` + : index; + dispatch(new NavigateToApplication("configuration", `${nodeId}/${lpId}/${viewId}${newIndexValues ? `/${newIndexValues}` : ''}`)); + + }), + changeNode: (ndoeId: string) => dispatcher.dispatch((dispatch: Dispatch) => { + dispatch(new NavigateToApplication("configuration", ndoeId)); + }), + changeLp: (lpId: string) => dispatcher.dispatch((dispatch: Dispatch, getState: () => IApplicationStoreState) => { + const { configuration: { nodeId } } = getState(); + dispatch(new NavigateToApplication("configuration", `${nodeId}/${lpId}`)); + }) +}); + +type ConfigurationApplicationProps = Connect<typeof mapProps, typeof mapDisp>; + +type ConfigurationApplicationState = { + +} + +class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationProps, ConfigurationApplicationState> { + + render() { + if (this.props.loading) { + return ( + <h2>Collecting data from network element. Please wait ...</h2> + ); + } else if (!this.props.nodeId) { + return ( + <> + <h2>Please select an network element to configure !</h2> + <NetworkElementTable idProperty={"mountId"} rows={this.props.avaliableDevices} asynchronus + onHandleClick={(evetm, rowData) => { this.props.changeNode(rowData.mountId) }} columns={ + [{ property:"mountId" }] + } /> + </> + ); + } else if (!this.props.lpId) { + return ( + <> + <h2>Please select an existing LP first !</h2> + <ul> + { this.props.coreModel && this.props.coreModel.ltp.map(ltp => { + return <li key={ltp.uuid}> + <Link component="a" variant="body2" color="secondary" onClick={() => { + this.props.changeLp(ltp.lp[0].uuid); + }}>{ltp.lp[0].label[0].value}</Link> + </li> + }) || null} + </ul> + </> + ); + } else if (!this.props.capability && !this.props.viewId) { + return ( + <h2>Please select a capability or viewId first !</h2> + ); + } + const viewData = this.props.viewData; + const viewSpecification = this.props.viewId + ? this.props.viewSpecifications.find(d => d.id === this.props.viewId) + : this.props.viewSpecifications.find(d => d.name === this.props.conditionalPackage); + + return viewSpecification + ? ( + <> + <hgroup style={{ marginBottom: 15 }}> + <h2>{`${this.props.nodeId} - ${this.props.lpId}`}</h2> + {this.createBreadCrump(viewSpecification.id, this.props.viewSpecifications)} + </hgroup> + <div style={{ display: "flex", flexWrap: "wrap", overflow: "auto" }}> + { + + (this.props.viewData && this.props.viewData instanceof Array) + ? this.renderUIList(viewSpecification, viewData as { [key: string]: string | number }[]) + : this.renderUIElement(viewSpecification, viewData as { [key: string]: string | number }) + } + {/* { <pre>{JSON.stringify(this.props.viewData, null, 2)} </pre> } */} + + </div> + </> + ) + : <h2>View Not Found</h2>; + } + + private static keyPropertyParser = /\$\$INDEX:(\d+):?([a-z\-]+)?\$\$$/; + private renderUIList = (viewSpecification: ViewSpecification, viewData: { [key: string]: string | number }[]) => { + const keyMatch = ConfigurationApplicationComponent.keyPropertyParser.exec(viewSpecification.dataPath); + const keyProperty = keyMatch && keyMatch[2]; + return ( + <Table> + <TableHead> + <TableRow> + {viewSpecification.elements.map(uiElement => { + switch (uiElement.uiType) { + case "number": + return ( + <TableCell key={uiElement.id} align={"right"} >{uiElement.label}</TableCell> + ); + case "selection": + case "object": + case "list": + case "string": + case "boolean": + return ( + <TableCell key={uiElement.id} align={"left"} >{uiElement.label}</TableCell> + ); + default: + if (process.env.NODE_ENV !== "production") { + console.error(`Unknown column type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + } + return null; + } + }) + } + <TableCell align={"right"} >Actions</TableCell> + </TableRow> + </TableHead> + <TableBody> + {viewData.map((row, ind) => ( + <TableRow key={keyProperty && row[keyProperty] || ind}> + {viewSpecification.elements.map(uiElement => { + switch (uiElement.uiType) { + case "string": + case "number": + return ( + <TableCell key={uiElement.id} component="td" scope="row" align={uiElement.uiType === "number" ? "right" : "left"}>{row[uiElement.id] == null ? "---" : row[uiElement.id] } </TableCell> + ); + case "boolean": + return ( + <TableCell key={uiElement.id} component="td" scope="row" align={"left"} >{row[uiElement.id] == null ? "---" : row[uiElement.id] ? uiElement.trueValue || 'True' : uiElement.falseValue || 'False'} </TableCell> + ); + case "list": + case "object": + return ( + <TableCell key={uiElement.id} component="td" scope="row" align={"left"} > + <Tooltip title={uiElement.description || ''}> + <Link component="a" variant="body2" color="secondary" onClick={() => { + this.props.navigateTo(uiElement.viewId, String(ind)); + }}>{uiElement.label}</Link> + </Tooltip></TableCell> + ); + case "selection": + const option = row[uiElement.id] ? uiElement.options.find(opt => opt.key === row[uiElement.id]) : null; + return ( + <TableCell key={uiElement.id} component="td" scope="row" align={"left"} >{option ? option.value : row[uiElement.id] == null ? "---" : row[uiElement.id] } </TableCell> + ); + default: + if (process.env.NODE_ENV !== "production") { + console.error(`Unknown column type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + } + return null; + } + })} + <TableCell align={"right"} ><Button onClick={() => { + this.props.navigateTo(this.props.viewId || '', String(/*keyProperty && row[keyProperty] || */ ind)); + }} >Details</Button> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + ); + } + + private renderUIElement = (viewSpecification: ViewSpecification, viewData: { [key: string]: string | number }) => ( + viewSpecification.elements.map(uiElement => { + if (uiElement.leafrefPath) { + return null; + } + switch (uiElement.uiType) { + case "selection": + return (viewData[uiElement.id] != null + ? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}> + <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel> + <Select + readOnly={ true } + value={(viewData[uiElement.id] || '').toString().toLowerCase()} + inputProps={{ + name: uiElement.id, + id: `select-${uiElement.id}`, + }} + > + {uiElement.options.map(option => (<MenuItem title={option.description} value={option.value}>{option.key}</MenuItem>))} + </Select> + </FormControl>) + : null + ); + case "boolean": + return (viewData[uiElement.id] != null + ? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}> + <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel> + <Select + readOnly={ true } + + value={String(viewData[uiElement.id]).toLowerCase()} + inputProps={{ + name: uiElement.id, + id: `select-${uiElement.id}`, + }} + > + <MenuItem value={'true'}>{uiElement.trueValue || 'True'}</MenuItem> + <MenuItem value={'false'}>{uiElement.falseValue || 'False'}</MenuItem> + + </Select> + </FormControl>) + : null + ); + case "string": + return ( + <Tooltip key={uiElement.id} title={uiElement.description || ''}> + <TextField InputProps={{ readOnly: true }} spellCheck={false} autoFocus margin="dense" + id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] || ''} style={{ width: 485, marginLeft: 20, marginRight: 20 }} /> + </Tooltip> + ); + case "number": + return ( + <Tooltip key={uiElement.id} title={uiElement.description || ''}> + <TextField InputProps={{ readOnly: true, startAdornment: uiElement.unit != null ? <InputAdornment position="start">{uiElement.unit}</InputAdornment> : undefined }} spellCheck={false} autoFocus margin="dense" + id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] || ''} style={{ width: 485, marginLeft: 20, marginRight: 20 }} /> + </Tooltip> + ); + case "list": + case "object": + return ( + <Tooltip key={uiElement.id} title={uiElement.description || ''}> + <Link component="a" variant="body2" color="secondary" style={{ width: 485, marginLeft: 20, marginRight: 20 }} onClick={() => { + this.props.navigateTo(uiElement.viewId); + }}>{uiElement.label}</Link> + </Tooltip> + ); + default: + if (process.env.NODE_ENV !== "production") { + console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`) + } + return null; + } + }) + ) + + private createBreadCrump = (viewId: string, viewSpecifications: ViewSpecification[]) => { + const result: JSX.Element[] = []; + const hasIndex = /\/\$\$INDEX:(\d+):?([a-z\-]+)?\$\$/i; + let currentViewSpecification = viewSpecifications.find(s => s.id === viewId); + let indexCounter = 0; + while (currentViewSpecification != null) { + const currentViewId = currentViewSpecification.id; + const currentDataPathHasIndex = hasIndex.test(currentViewSpecification.dataPath); + result.unshift(( + <span> + <Link component="a" variant="body2" color="secondary" onClick={() => { + this.props.navigateTo(currentViewId, currentDataPathHasIndex ? ++indexCounter : indexCounter); + }}>{currentViewSpecification.name}</Link> + {viewId === currentViewId ? null : " | "} + </span> + )); + currentViewSpecification = viewSpecifications.find(s => s.id === (currentViewSpecification && currentViewSpecification.parentView || '')); + } + return result; + } +} + +export const ConfigurationApplication = connect(mapProps, mapDisp)(ConfigurationApplicationComponent); +export default ConfigurationApplication;
\ No newline at end of file |