diff options
Diffstat (limited to 'sdnr/wt/odlux/framework/src')
35 files changed, 1152 insertions, 595 deletions
diff --git a/sdnr/wt/odlux/framework/src/actions/menuAction.ts b/sdnr/wt/odlux/framework/src/actions/menuAction.ts new file mode 100644 index 000000000..ec0796587 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/actions/menuAction.ts @@ -0,0 +1,31 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { Action } from '../flux/action'; + +export class MenuAction extends Action { + constructor(public isOpen: boolean) { + super(); + } +} + +export class MenuClosedByUser extends Action { + constructor(public isClosed: boolean) { + super(); + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/actions/websocketAction.ts b/sdnr/wt/odlux/framework/src/actions/websocketAction.ts new file mode 100644 index 000000000..8512d59d5 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/actions/websocketAction.ts @@ -0,0 +1,8 @@ +import { Action } from "../flux/action"; + + +export class SetWebsocketAction extends Action { + constructor(public isConnected: boolean) { + super(); + } +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/app.css b/sdnr/wt/odlux/framework/src/app.css index f3d3b7aab..356f36dd0 100644 --- a/sdnr/wt/odlux/framework/src/app.css +++ b/sdnr/wt/odlux/framework/src/app.css @@ -2,4 +2,5 @@ html, body, #app { height: 100%; padding: 0px; margin: 0px; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/app.tsx b/sdnr/wt/odlux/framework/src/app.tsx index 09c7a76e2..85b0dfb34 100644 --- a/sdnr/wt/odlux/framework/src/app.tsx +++ b/sdnr/wt/odlux/framework/src/app.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
/******************************************************************************
* Copyright 2018 highstreet technologies GmbH
*
@@ -50,6 +50,8 @@ import { startRestService } from './services/restService'; import theme from './design/default';
import '!style-loader!css-loader!./app.css';
import { ReplaceAction } from './actions/navigationActions';
+import { startForceLogoutService } from './services/forceLogoutService';
+import { startNotificationService } from './services/notificationService';
declare module '@material-ui/core/styles/createMuiTheme' {
@@ -90,6 +92,8 @@ export const runApplication = () => { startRestService(applicationStore);
startHistoryListener(applicationStore);
+ startForceLogoutService(applicationStore);
+ startNotificationService(applicationStore);
const App = (): JSX.Element => (
<ApplicationStoreProvider applicationStore={applicationStore} >
diff --git a/sdnr/wt/odlux/framework/src/common/event.ts b/sdnr/wt/odlux/framework/src/common/event.ts index f71b0164a..913487911 100644 --- a/sdnr/wt/odlux/framework/src/common/event.ts +++ b/sdnr/wt/odlux/framework/src/common/event.ts @@ -1,4 +1,23 @@ /** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + + +/** * Represents an event. * Events enable a class or object to notify other classes or objects when something of interest occurs. * The class that sends (or invokes) the event is called the publisher and the classes that receive (or handle) the event are called subscribers. diff --git a/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx b/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx index a20c592c5..4cf63e927 100644 --- a/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx +++ b/sdnr/wt/odlux/framework/src/components/errorDisplay.tsx @@ -36,10 +36,10 @@ const styles = (theme: Theme) => createStyles({ justifyContent: "center", }, paper: { - width: theme.spacing.unit * 50, + width: theme.spacing(50), backgroundColor: theme.palette.background.paper, boxShadow: theme.shadows[5], - padding: theme.spacing.unit * 4, + padding: theme.spacing(4), }, card: { minWidth: 275, @@ -75,43 +75,47 @@ type ErrorDisplayProps = WithStyles<typeof styles> & Connect; * Represents a compnent for formaing and displaying errors. */ class ErrorDisplayComponent extends React.Component<ErrorDisplayProps> { + constructor(props: ErrorDisplayProps) { + super(props); + } + render(): JSX.Element { const { classes, state } = this.props; const errorInfo = state.framework.applicationState.errors.length && state.framework.applicationState.errors[state.framework.applicationState.errors.length - 1]; return ( - <Modal className={ classes.modal } + <Modal className={classes.modal} aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description" - open={ state.framework.applicationState.errors && state.framework.applicationState.errors.length > 0 } - onClose={ () => this.props.dispatch(new ClearErrorInfoAction()) } + open={state.framework.applicationState.errors && state.framework.applicationState.errors.length > 0} + onClose={() => this.props.dispatch(new ClearErrorInfoAction())} > - { errorInfo && - <div className={ classes.paper }> - <Card className={ classes.card }> + {errorInfo && + <div className={classes.paper}> + <Card className={classes.card}> <CardContent> - <Typography className={ classes.title } color="textSecondary"> - Something went wrong. + <Typography className={classes.title} color="textSecondary"> + {errorInfo.title != null ? errorInfo.title : "Something went wrong."} </Typography> - <Typography variant="headline" component="h2"> - { errorInfo.error && errorInfo.error.toString() } + <Typography variant="h5" component="h2"> + {errorInfo.error && errorInfo.error.toString()} </Typography> - <Typography className={ classes.pos } color="textSecondary"> - { errorInfo.message && errorInfo.message .toString() } + <Typography className={classes.pos} color="textSecondary"> + {errorInfo.message && errorInfo.message.toString()} </Typography> <Typography component="p"> - { errorInfo.info && errorInfo.info.componentStack && errorInfo.info.componentStack.split('\n').map(line => { - return [line, <br />]; - }) } - { errorInfo.info && errorInfo.info.extra && errorInfo.info.extra.split('\n').map(line => { - return [line, <br />]; - }) } + {errorInfo.info && errorInfo.info.componentStack && errorInfo.info.componentStack.split('\n').map(line => { + return [line, <br />]; + })} + {errorInfo.info && errorInfo.info.extra && errorInfo.info.extra.split('\n').map(line => { + return [line, <br />]; + })} </Typography> </CardContent> <CardActions> - <Button size="small" onClick={ () => this.props.dispatch(new RemoveErrorInfoAction(errorInfo)) } >Close</Button> - </CardActions> + <Button size="small" onClick={() => this.props.dispatch(new RemoveErrorInfoAction(errorInfo))} >Close</Button> + </CardActions> </Card> - </div> || null + </div> || <div></div> } </Modal> ); diff --git a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx index d89ced23a..3e31c5e03 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx @@ -33,19 +33,21 @@ import { EnhancedTableFilter } from './tableFilter'; import { ColumnModel, ColumnType } from './columnModel'; import { Omit } from '@material-ui/core'; import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon'; +import { replaceHyphen } from '../../utilities/yangHelper'; +import { string } from 'prop-types'; export { ColumnModel, ColumnType } from './columnModel'; -type propType = string | number | null | undefined | (string|number)[]; +type propType = string | number | null | undefined | (string | number)[]; type dataType = { [prop: string]: propType }; -type resultType<TData = dataType> = { page: number, rowCount: number, rows: TData[] }; +type resultType<TData = dataType> = { page: number, total: number, rows: TData[] }; -export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) =>resultType<TData> | Promise<resultType<TData>>; +export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) => resultType<TData> | Promise<resultType<TData>>; function desc(a: dataType, b: dataType, orderBy: string) { - if ((b[orderBy] || "") < (a[orderBy] || "") ) { + if ((b[orderBy] || "") < (a[orderBy] || "")) { return -1; } - if ((b[orderBy] || "") > (a[orderBy] || "") ) { + if ((b[orderBy] || "") > (a[orderBy] || "")) { return 1; } return 0; @@ -68,7 +70,7 @@ function getSorting(order: 'asc' | 'desc' | null, orderBy: string) { const styles = (theme: Theme) => createStyles({ root: { width: '100%', - marginTop: theme.spacing.unit * 3, + marginTop: theme.spacing(3), }, table: { minWidth: 1020, @@ -83,7 +85,7 @@ export type MaterialTableComponentState<TData = {}> = { orderBy: string | null; selected: any[] | null; rows: TData[]; - rowCount: number; + total: number; page: number; rowsPerPage: number; loading: boolean; @@ -95,18 +97,19 @@ export type TableApi = { forceRefresh?: () => Promise<void> }; type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & { columns: ColumnModel<TData>[]; - idProperty: keyof TData | ((data: TData) => React.Key ); + idProperty: keyof TData | ((data: TData) => React.Key); + tableId?: string; title?: string; enableSelection?: boolean; disableSorting?: boolean; disableFilter?: boolean; - customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; + customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void; }; -type MaterialTableComponentPropsWithRows<TData={}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; -type MaterialTableComponentPropsWithRequestData<TData={}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; -type MaterialTableComponentPropsWithExternalState<TData={}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { +type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; }; +type MaterialTableComponentPropsWithRequestData<TData = {}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; }; +type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & { onToggleFilter: () => void; onFilterChanged: (property: string, filterTerm: string) => void; onHandleChangePage: (page: number) => void; @@ -152,7 +155,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : null, selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null, rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [], - rowCount: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, + total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, page, rowsPerPage, }; @@ -167,58 +170,58 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } render(): JSX.Element { const { classes, columns } = this.props; - const { rows, rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; + const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state; const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage); - const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as {[key:string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty; + const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as { [key: string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty; const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) } return ( - <Paper className={ classes.root }> - <TableToolbar numSelected={ selected && selected.length } title={ this.props.title } customActionButtons={ this.props.customActionButtons } onExportToCsv={ this.exportToCsv } - onToggleFilter={ toggleFilter } /> - <div className={ classes.tableWrapper }> - <Table className={ classes.table } aria-labelledby="tableTitle"> + <Paper className={classes.root}> + <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv} + onToggleFilter={toggleFilter} /> + <div className={classes.tableWrapper}> + <Table className={classes.table} aria-labelledby="tableTitle"> <EnhancedTableHead - columns={ columns } - numSelected={ selected && selected.length } - order={ order } - orderBy={ orderBy } - onSelectAllClick={ this.handleSelectAllClick } - onRequestSort={ this.onHandleRequestSort } - rowCount={ rows.length } - enableSelection={ this.props.enableSelection } + columns={columns} + numSelected={selected && selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={this.handleSelectAllClick} + onRequestSort={this.onHandleRequestSort} + rowCount={rows.length} + enableSelection={this.props.enableSelection} /> <TableBody> - { showFilter && <EnhancedTableFilter columns={ columns } filter={ filter } onFilterChanged={ this.onFilterChanged } enableSelection={this.props.enableSelection} /> || null } - { rows // may need ordering here + {showFilter && <EnhancedTableFilter columns={columns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null} + {rows // may need ordering here .map((entry: TData & { [key: string]: any }) => { const entryId = getId(entry); const isSelected = this.isSelected(entryId); return ( <TableRow hover - onClick={ event => this.handleClick(event, entry, entryId) } + onClick={event => this.handleClick(event, entry, entryId)} role="checkbox" - aria-checked={ isSelected } - tabIndex={ -1 } - key={ entryId } - selected={ isSelected } + aria-checked={isSelected} + tabIndex={-1} + key={entryId} + selected={isSelected} > - { this.props.enableSelection - ? <TableCell padding="checkbox" style={ { width: "50px" } }> - <Checkbox checked={ isSelected } /> + {this.props.enableSelection + ? <TableCell padding="checkbox" style={{ width: "50px" }}> + <Checkbox checked={isSelected} /> </TableCell> - : null + : null } { this.props.columns.map( col => { - const style = col.width ? { width: col.width } : { }; + const style = col.width ? { width: col.width } : {}; return ( - <TableCell key={ col.property } align={ col.type === ColumnType.numeric && !col.align ? "right": col.align } style={ style }> - { col.type === ColumnType.custom && col.customControl - ? <col.customControl className={col.className} style={col.style} rowData={ entry } /> + <TableCell key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} style={style}> + {col.type === ColumnType.custom && col.customControl + ? <col.customControl className={col.className} style={col.style} rowData={entry} /> : col.type === ColumnType.boolean - ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true": "false"] : String(entry[col.property]) }</span> + ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])}</span> : <span className={col.className} style={col.style}>{String(entry[col.property])}</span> } </TableCell> @@ -228,29 +231,29 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } </TableRow> ); - }) } - { emptyRows > 0 && ( - <TableRow style={ { height: 49 * emptyRows } }> - <TableCell colSpan={ this.props.columns.length } /> + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 49 * emptyRows }}> + <TableCell colSpan={this.props.columns.length} /> </TableRow> - ) } + )} </TableBody> </Table> </div> <TablePagination - rowsPerPageOptions={[5, 10, 20, 50] } + rowsPerPageOptions={[5, 10, 20, 50]} component="div" - count={ rowCount } - rowsPerPage={ rowsPerPage } - page={ page } - backIconButtonProps={ { + count={rowCount} + rowsPerPage={rowsPerPage} + page={page} + backIconButtonProps={{ 'aria-label': 'Previous Page', - } } - nextIconButtonProps={ { + }} + nextIconButtonProps={{ 'aria-label': 'Next Page', - } } - onChangePage={ this.onHandleChangePage } - onChangeRowsPerPage={ this.onHandleChangeRowsPerPage } + }} + onChangePage={this.onHandleChangePage} + onChangeRowsPerPage={this.onHandleChangeRowsPerPage} /> </Paper> ); @@ -261,7 +264,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return { ...state, rows: props.rows, - rowCount: props.rowCount, + total: props.total, orderBy: props.orderBy, order: props.order, filter: props.filter, @@ -281,9 +284,11 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return state; } - private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], rowCount: number } { + private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } { + + const { page, rowsPerPage, order, orderBy, filter } = state; + try { - const { page, rowsPerPage, order, orderBy, filter } = state; let data: dataType[] = props.rows || []; let filtered = false; if (state.showFilter) { @@ -292,25 +297,84 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate filtered = filtered || exp !== undefined; data = exp !== undefined ? data.filter((val) => { const value = val[prop]; - return (value == exp) || (value && value.toString().indexOf(String(exp)) > -1); + + if (value) { + + if (typeof exp === 'boolean') { + return value == exp; + + } else if (typeof exp === 'string') { + + const valueAsString = value.toString(); + if (exp.length === 0) return value; + + const regex = new RegExp("\\*", "g"); + const regex2 = new RegExp("\\?", "g"); + + const countStar = (exp.match(regex) || []).length; + const countQuestionmarks = (exp.match(regex2) || []).length; + + if (countStar > 0 || countQuestionmarks > 0) { + let editableExpression = exp; + + if (!exp.startsWith('*')) { + editableExpression = '^' + exp; + } + + if (!exp.endsWith('*')) { + editableExpression = editableExpression + '$'; + } + + const expressionAsRegex = editableExpression.replace(/\*/g, ".*").replace(/\?/g, "."); + + return valueAsString.match(new RegExp(expressionAsRegex, "g")); + } + else if (exp.includes('>=')) { + return Number(valueAsString) >= Number(exp.replace('>=', '')); + } else if (exp.includes('<=')) { + return Number(valueAsString) <= Number(exp.replace('<=', '')); + } else + if (exp.includes('>')) { + return Number(valueAsString) > Number(exp.replace('>', '')); + } else if (exp.includes('<')) { + return Number(valueAsString) < Number(exp.replace('<', '')); + } + } + } + + return (value == exp) }) : data; }); } const rowCount = data.length; - data = (orderBy && order - ? stableSort(data, getSorting(order, orderBy)) - : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate + let newPage = Math.floor(rowCount / rowsPerPage); + return { + rows: data, + total: rowCount, + page: newPage + }; + } else { + data = (orderBy && order + ? stableSort(data, getSorting(order, orderBy)) + : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage); + + return { + rows: data, + total: rowCount, + page: page + }; + } - return { - rows: data, - rowCount - }; - } catch{ + + } catch (e) { + console.error(e); return { rows: [], - rowCount: 0 + total: 0, + page: page } } } @@ -323,7 +387,8 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate ); this.setState(response); } else { - this.setState(MaterialTableComponent.updateRows(this.props, this.state)); + let updateResult = MaterialTableComponent.updateRows(this.props, this.state); + this.setState(updateResult); } } @@ -361,7 +426,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate handleSelectAllClick: () => {}; - private onHandleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, page: number) => { + private onHandleChangePage = (event: any | null, page: number) => { if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { this.props.onHandleChangePage(page); return; @@ -390,12 +455,12 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate return (selectedIndex > -1); } - private handleClick(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData, id: string | number): void { + private handleClick(event: any, rowData: TData, id: string | number): void { if (this.props.onHandleClick instanceof Function) { this.props.onHandleClick(event, rowData); return; } - if (!this.props.enableSelection){ + if (!this.props.enableSelection) { return; } let selected = this.state.selected || []; @@ -416,20 +481,26 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate }); } + private exportToCsv = async () => { let file; let data: dataType[] | null = null; let csv: string[] = []; - if (isMaterialTableComponentPropsWithRequestData(this.props)) { + // table with extra request handler this.setState({ loading: true }); const result = await Promise.resolve( - this.props.onRequestData( 0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) + this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {}) ); data = result.rows; this.setState({ loading: true }); - } else { + } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) { + // table with generated handlers note: exports data shown on current page + data = this.props.rows; + } + else { + // table with local data data = MaterialTableComponent.updateRows(this.props, this.state).rows; } @@ -438,7 +509,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate this.state.rows && this.state.rows.forEach((row: any) => { csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n"); }); - const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. + const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type. try { // Specify the filename using the File constructor, but ... file = new File(csv, "export.csv", properties); diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx index 0356aa225..737ea85f9 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx @@ -33,7 +33,7 @@ const styles = (theme: Theme) => createStyles({ flexWrap: 'wrap', }, input: { - margin: theme.spacing.unit, + margin: theme.spacing(1), }, }); @@ -45,7 +45,7 @@ interface IEnhancedTableFilterComponentProps extends WithStyles<typeof styles> { } class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterComponentProps> { - createFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLInputElement|HTMLSelectElement|HTMLTextAreaElement>) => { + createFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => { this.props.onFilterChanged && this.props.onFilterChanged(property, event.target.value); }; @@ -53,33 +53,33 @@ class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterC const { columns, filter, classes } = this.props; return ( <TableRow> - { this.props.enableSelection - ? <TableCell padding="checkbox" style={ { width: "50px" } }> - </TableCell> - : null - } - { columns.map(col => { + {this.props.enableSelection + ? <TableCell padding="checkbox" style={{ width: "50px" }}> + </TableCell> + : null + } + {columns.map(col => { const style = col.width ? { width: col.width } : {}; return ( <TableCell - key={ col.property } - padding={ col.disablePadding ? 'none' : 'default' } - style={ style } + key={col.property} + padding={col.disablePadding ? 'none' : 'default'} + style={style} > - { col.disableFilter || (col.type === ColumnType.custom) + {col.disableFilter || (col.type === ColumnType.custom) ? null : (col.type === ColumnType.boolean) ? <Select className={classes.input} value={filter[col.property] !== undefined ? filter[col.property] : ''} onChange={this.createFilterHandler(col.property)} inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} > <MenuItem value={undefined}> <em>None</em> </MenuItem> - <MenuItem value={true as any as string}>{ col.labels ? col.labels["true"]:"true"}</MenuItem> - <MenuItem value={false as any as string}>{ col.labels ? col.labels["false"] : "false"}</MenuItem> + <MenuItem value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem> + <MenuItem value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem> </Select> : <Input className={classes.input} inputProps={{ 'aria-label': 'Filter' }} value={filter[col.property] || ''} onChange={this.createFilterHandler(col.property)} />} </TableCell> ); - }, this) } + }, this)} </TableRow> ); } diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx index 80d38ab52..a4080b51b 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx @@ -32,7 +32,7 @@ import { SvgIconProps } from '@material-ui/core/SvgIcon/SvgIcon'; const styles = (theme: Theme) => createStyles({ root: { - paddingRight: theme.spacing.unit, + paddingRight: theme.spacing(1), }, highlight: theme.palette.type === 'light' @@ -65,6 +65,7 @@ const styles = (theme: Theme) => createStyles({ interface ITableToolbarComponentProps extends WithStyles<typeof styles> { numSelected: number | null; title?: string; + tableId?: string; customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, onClick: () => void }[]; onToggleFilter: () => void; onExportToCsv: () => void; @@ -79,7 +80,7 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, }; } - private handleMenu = (event: React.MouseEvent<HTMLElement>) => { + private handleMenu = (event: React.MouseEvent<HTMLElement>) => { this.setState({ anchorEl: event.currentTarget }); }; @@ -89,55 +90,55 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, render() { const { numSelected, classes } = this.props; const open = !!this.state.anchorEl; - + const buttonPrefix = this.props.tableId !== undefined ? this.props.tableId + '-' : ''; return ( - <Toolbar className={ `${ classes.root } ${ numSelected && numSelected > 0 ? classes.highlight : '' } ` } > - <div className={ classes.title }> - { numSelected && numSelected > 0 ? ( - <Typography color="inherit" variant="subheading"> - { numSelected } selected + <Toolbar className={`${classes.root} ${numSelected && numSelected > 0 ? classes.highlight : ''} `} > + <div className={classes.title}> + {numSelected && numSelected > 0 ? ( + <Typography color="inherit" variant="subtitle1"> + {numSelected} selected </Typography> ) : ( - <Typography variant="headline" id="tableTitle"> - { this.props.title || null } + <Typography variant="h5" id="tableTitle"> + {this.props.title || null} </Typography> - ) } + )} </div> - <div className={ classes.spacer } /> - <div className={ classes.actions }> - { this.props.customActionButtons - ? this.props.customActionButtons.map((action, ind) =>( - <Tooltip key={ `custom-action-${ ind }`} title={action.tooltip}> - <IconButton aria-label={ `custom-action-${ind}` } onClick={() => action.onClick() }> - <action.icon /> - </IconButton> - </Tooltip> + <div className={classes.spacer} /> + <div className={classes.actions}> + {this.props.customActionButtons + ? this.props.customActionButtons.map((action, ind) => ( + <Tooltip key={`custom-action-${ind}`} title={action.tooltip}> + <IconButton aria-label={buttonPrefix + `custom-action-${ind}`} onClick={() => action.onClick()}> + <action.icon /> + </IconButton> + </Tooltip> )) - : null } - { numSelected && numSelected > 0 ? ( + : null} + {numSelected && numSelected > 0 ? ( <Tooltip title="Delete"> - <IconButton aria-label="Delete"> + <IconButton aria-label={buttonPrefix + "delete"}> <DeleteIcon /> </IconButton> </Tooltip> ) : ( <Tooltip title="Filter list"> - <IconButton aria-label="Filter list" onClick={ () => { this.props.onToggleFilter && this.props.onToggleFilter() } }> + <IconButton aria-label={buttonPrefix + "filter-list"} onClick={() => { this.props.onToggleFilter && this.props.onToggleFilter() }}> <FilterListIcon /> </IconButton> </Tooltip> - ) } + )} <Tooltip title="Actions"> <IconButton color="inherit" - aria-owns={ open ? 'menu-appbar' : undefined } + aria-owns={open ? 'menu-appbar' : undefined} aria-haspopup="true" - onClick={ this.handleMenu } > + onClick={this.handleMenu} > <MoreIcon /> </IconButton> </Tooltip> - <Menu id="menu-appbar" anchorEl={ this.state.anchorEl } anchorOrigin={ { vertical: 'top', horizontal: 'right' } } - transformOrigin={ { vertical: 'top', horizontal: 'right' } } open={ open } onClose={ this.handleClose } > - <MenuItem onClick={ this.props.onExportToCsv }>Export as CSV</MenuItem> + <Menu id="menu-appbar" anchorEl={this.state.anchorEl} anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + transformOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} onClose={this.handleClose} > + <MenuItem onClick={this.props.onExportToCsv}>Export as CSV</MenuItem> </Menu> </div> </Toolbar> diff --git a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts index 43c3b5e35..6e8902c07 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts +++ b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts @@ -27,7 +27,7 @@ export interface IExternalTableState<TData> { orderBy: string | null; selected: any[] | null; rows: TData[]; - rowCount: number; + total: number; page: number; rowsPerPage: number; loading: boolean; @@ -68,13 +68,13 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState } class SetPreFilterChangedAction extends TableAction { - constructor(public preFilter: {[key: string]: string}) { + constructor(public preFilter: { [key: string]: string }) { super(); } } class SetFilterChangedAction extends TableAction { - constructor (public filter: { [key: string]: string }) { + constructor(public filter: { [key: string]: string }) { super(); } } @@ -92,7 +92,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState } class SetResultAction extends TableAction { - constructor(public result: { page: number, rowCount: number, rows: TData[] }) { + constructor(public result: { page: number, total: number, rows: TData[] }) { super(); } } @@ -105,7 +105,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState orderBy: null, selected: null, rows: [], - rowCount: 0, + total: 0, page: 0, rowsPerPage: 10, loading: false, @@ -126,14 +126,14 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState ...state, loading: false, rows: action.result.rows, - rowCount: action.result.rowCount, + total: action.result.total, page: action.result.page, } } else if (action instanceof RequestSortAction) { state = { ...state, loading: true, - orderBy : state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy , + orderBy: state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy, order: state.orderBy === action.orderBy && state.order === 'asc' ? 'desc' : 'asc', } } else if (action instanceof SetShowFilterAction) { @@ -176,9 +176,23 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState const reloadAction = (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => { dispatch(new RefreshAction()); const ownState = selectState(getAppState()); - const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {})}; - Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter )).then(result => { - dispatch(new SetResultAction(result)); + const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) }; + Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => { + + if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate + + let newPage = Math.floor(result.total / ownState.rowsPerPage); + + Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result1 => { + dispatch(new SetResultAction(result1)); + }); + + + } else { + dispatch(new SetResultAction(result)); + } + + }).catch(error => new AddErrorInfoAction(error)); }; @@ -186,8 +200,8 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState return { onPreFilterChanged: (preFilter: { [key: string]: string }) => { dispatch(new SetPreFilterChangedAction(preFilter)); - (!skipRefresh) && dispatch(reloadAction); - } + (!skipRefresh) && dispatch(reloadAction); + } }; } @@ -236,7 +250,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState const createProperties = (state: IApplicationStoreState) => { return { ...selectState(state) - } + } } return { diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/index.ts b/sdnr/wt/odlux/framework/src/components/material-ui/index.ts index c756f7f07..e0e3fc943 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/index.ts +++ b/sdnr/wt/odlux/framework/src/components/material-ui/index.ts @@ -15,8 +15,8 @@ * the License. * ============LICENSE_END========================================================================== */ -export { ListItemLink } from './listItemLink';
-export { Panel } from './panel';
-export { ToggleButton, ToggleButtonClassKey } from './toggleButton';
-export { TreeView, ITreeItem, TreeViewCtorType} from './treeView';
-
+export { ListItemLink } from './listItemLink'; +export { Panel } from './panel'; +export { ToggleButton, ToggleButtonClassKey } from './toggleButton'; +export { TreeView, ITreeItem, TreeViewCtorType} from './treeView'; +export { Loader } from './loader'; diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/loader.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/loader.tsx new file mode 100644 index 000000000..5ab2fd415 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/components/material-ui/loader.tsx @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + + +import * as React from "react"; + +import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles'; + +const styles = (theme: Theme) => createStyles({ + "@keyframes spin": { + "0%": { transform: "rotate(0deg)" }, + "100%": { transform: "rotate(360deg)" }, + }, + loader: { + border: `16px solid ${theme.palette.grey.A200}`, + borderTop: `16px solid ${theme.palette.secondary.main}`, + borderRadius: "50%", + width: "120px", + height: "120px", + animation: "$spin 2s linear infinite", + } +}); + +const LoaderComponent: React.FC<WithStyles<typeof styles>> = (props) => { + return ( + <div className={props.classes.loader} /> + ); +}; + +export const Loader = withStyles(styles)(LoaderComponent); diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/panel.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/panel.tsx index 9627a749b..378d48592 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/panel.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/panel.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as React from 'react';
import { withStyles, Theme, WithStyles, createStyles } from '@material-ui/core/styles';
@@ -32,8 +32,8 @@ const styles = (theme: Theme) => createStyles({ detail: {
// background: theme.palette.background.paper,
// color: theme.palette.text.primary,
- position: "relative",
- display: 'flex',
+ position: "relative",
+ display: 'flex',
flexDirection: 'column'
},
text: {
@@ -53,18 +53,18 @@ type PanalProps = WithStyles<typeof styles> & { const PanelComponent: React.SFC<PanalProps> = (props) => {
const { classes, activePanel, onToggle } = props;
return (
- <ExpansionPanel className={ classes.accordion } expanded={ activePanel === props.panelId } onChange={ () => onToggle(props.panelId) } >
- <ExpansionPanelSummary expandIcon={ <ExpandMoreIcon /> }>
- <Typography className={ classes.text } >{ props.title }</Typography>
+ <ExpansionPanel className={classes.accordion} expanded={activePanel === props.panelId} onChange={() => onToggle(props.panelId)} >
+ <ExpansionPanelSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography className={classes.text} >{props.title}</Typography>
</ExpansionPanelSummary>
- <ExpansionPanelDetails className={ classes.detail }>
- { props.children }
+ <ExpansionPanelDetails className={classes.detail}>
+ {props.children}
</ExpansionPanelDetails>
- { props.customActionButtons
+ {props.customActionButtons
? <ExpansionPanelActions>
- { props.customActionButtons }
- </ExpansionPanelActions>
- : null }
+ {props.customActionButtons}
+ </ExpansionPanelActions>
+ : null}
</ExpansionPanel>
);
};
diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/snackDisplay.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/snackDisplay.tsx index 610376107..437784ce5 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/snackDisplay.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/snackDisplay.tsx @@ -21,7 +21,7 @@ import { IApplicationStoreState } from '../../store/applicationStore'; import { Connect, connect, IDispatcher } from '../../flux/connect'; import { RemoveSnackbarNotification } from '../../actions/snackbarActions'; -import { InjectedNotistackProps, withSnackbar } from 'notistack'; +import { WithSnackbarProps, withSnackbar } from 'notistack'; const mapProps = (state: IApplicationStoreState) => ({ notifications: state.framework.applicationState.snackBars @@ -33,7 +33,7 @@ const mapDispatch = (dispatcher: IDispatcher) => ({ } }); -type DisplaySnackbarsComponentProps = Connect<typeof mapProps, typeof mapDispatch> & InjectedNotistackProps; +type DisplaySnackbarsComponentProps = Connect<typeof mapProps, typeof mapDispatch> & WithSnackbarProps; class DisplaySnackbarsComponent extends React.Component<DisplaySnackbarsComponentProps> { private displayed: number[] = []; diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/toggleButton.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/toggleButton.tsx index fb10ca941..1a29d6970 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/toggleButton.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/toggleButton.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as React from 'react';
import classNames from 'classnames';
@@ -30,7 +30,7 @@ export const styles = (theme: Theme) => createStyles({ height: 32,
minWidth: 48,
margin: 0,
- padding: `${theme.spacing.unit - 4}px ${theme.spacing.unit * 1.5}px`,
+ padding: `${theme.spacing(1 - 4)}px ${theme.spacing(1.5)}px`,
borderRadius: 2,
willChange: 'opacity',
color: fade(theme.palette.action.active, 0.38),
@@ -119,6 +119,7 @@ class ToggleButtonComponent extends React.Component<IToggleButtonProps> { handleChange = (event: React.FormEvent<HTMLElement>) => {
const { onChange, onClick, value } = this.props;
+ event.stopPropagation();
if (onClick) {
onClick(event, value);
if (event.isDefaultPrevented()) {
@@ -129,6 +130,7 @@ class ToggleButtonComponent extends React.Component<IToggleButtonProps> { if (onChange) {
onChange(event, value);
}
+ event.preventDefault();
};
render() {
@@ -157,6 +159,7 @@ class ToggleButtonComponent extends React.Component<IToggleButtonProps> { disabled={disabled}
focusRipple={!disableFocusRipple}
onClick={this.handleChange}
+ href="#"
{...other}
>
<span className={classes.label}>{children}</span>
diff --git a/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx b/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx index 7f2f73926..e4eb3a794 100644 --- a/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-ui/treeView.tsx @@ -103,21 +103,21 @@ class TreeViewComponent<TData extends ITreeItem> extends React.Component<TreeVie ...this.props.style }, search: { - padding: `0px ${ this.props.theme.spacing.unit }px` + padding: `0px ${this.props.theme.spacing(1)}px` } }; return ( - <div style={ styles.root }> - { children } - { enableSearchBar && <TextField label={ "Search" } fullWidth={ true } style={ styles.search } value={ searchTerm } onChange={ this.onChangeSearchText } /> || null } + <div style={styles.root}> + {children} + {enableSearchBar && <TextField label={"Search"} fullWidth={true} style={styles.search} value={searchTerm} onChange={this.onChangeSearchText} /> || null} <List> - { this.renderItems(items, searchTerm && searchTerm.toLowerCase()) } + {this.renderItems(items, searchTerm && searchTerm.toLowerCase())} </List> </div> ); } - private itemIndex: number = 0; + private itemIndex: number = 0; private renderItems = (items: TData[], searchTerm: string | undefined, depth: number = 1) => { return items.reduce((acc, item) => { @@ -144,7 +144,7 @@ class TreeViewComponent<TData extends ITreeItem> extends React.Component<TreeVie private renderItem = (item: TData, searchTerm: string | undefined, depth: number, isFolder: boolean, expanded: boolean): JSX.Element | null => { const styles = { item: { - paddingLeft: (((this.props.depthOffset || 0) + depth) * this.props.theme.spacing.unit * 3), + paddingLeft: (((this.props.depthOffset || 0) + depth) * this.props.theme.spacing(3)), backgroundColor: this.state.activeItem === item ? this.props.theme.palette.action.selected : undefined, height: this.props.itemHeight || undefined, cursor: item.disabled ? 'not-allowed' : 'pointer', @@ -159,47 +159,47 @@ class TreeViewComponent<TData extends ITreeItem> extends React.Component<TreeVie const searchTermLength = searchTerm && searchTerm.length || 0; const handleClickCreator = (isIcon: boolean) => (event: React.SyntheticEvent) => { - if (item.disabled) return; - event.preventDefault(); - event.stopPropagation(); - if (isFolder && (this.props.autoExpandFolder || isIcon)) { - this.props.onFolderClick ? this.props.onFolderClick(item) : this.onFolderClick(item); - } else { - this.props.onItemClick ? this.props.onItemClick(item) : this.onItemClick(item); - } - }; + if (item.disabled) return; + event.preventDefault(); + event.stopPropagation(); + if (isFolder && (this.props.autoExpandFolder || isIcon)) { + this.props.onFolderClick ? this.props.onFolderClick(item) : this.onFolderClick(item); + } else { + this.props.onItemClick ? this.props.onItemClick(item) : this.onItemClick(item); + } + }; return ((searchTerm && (matchIndex > -1 || expanded) || !searchTerm) ? ( - <ListItem key={ `tree-list-${ this.itemIndex++ }` } style={ styles.item } onClick={ handleClickCreator(false) } button > + <ListItem key={`tree-list-${this.itemIndex++}`} style={styles.item} onClick={handleClickCreator(false)} button > { // display the left icon - (this.props.useFolderIcons && <ListItemIcon>{ isFolder ? <FolderIcon /> : <FileIcon /> }</ListItemIcon>) || - (item.icon && (<ListItemIcon><item.icon /></ListItemIcon>)) } + (this.props.useFolderIcons && <ListItemIcon>{isFolder ? <FolderIcon /> : <FileIcon />}</ListItemIcon>) || + (item.icon && (<ListItemIcon><item.icon /></ListItemIcon>))} + - { // highlight search result matchIndex > -1 - ? (<span> - { text.substring(0, matchIndex) } - <span - style={ { - display: 'inline-block', - backgroundColor: 'rgba(255,235,59,0.5)', - padding: '3px', - } } - > - { text.substring(matchIndex, matchIndex + searchTermLength) } - </span> - { text.substring(matchIndex + searchTermLength) } - </span>) - : (<ListItemText primary={ text } />) + ? (<span> + {text.substring(0, matchIndex)} + <span + style={{ + display: 'inline-block', + backgroundColor: 'rgba(255,235,59,0.5)', + padding: '3px', + }} + > + {text.substring(matchIndex, matchIndex + searchTermLength)} + </span> + {text.substring(matchIndex + searchTermLength)} + </span>) + : (<ListItemText primary={text} />) } { // display the right icon, depending on the state - !isFolder ? null : expanded ? (<OpenIcon onClick={ handleClickCreator(true) } />) : (<CloseIcon onClick={ handleClickCreator(true) } />) } + !isFolder ? null : expanded ? (<OpenIcon onClick={handleClickCreator(true)} />) : (<CloseIcon onClick={handleClickCreator(true)} />)} </ListItem> - ) + ) : null ); } @@ -264,5 +264,5 @@ class TreeViewComponent<TData extends ITreeItem> extends React.Component<TreeVie export type TreeViewCtorType<TData extends ITreeItem = ITreeItem> = new () => React.Component<Omit<TreeViewComponentProps<TData>, 'theme'>>; -export const TreeView = withTheme()(TreeViewComponent); +export const TreeView = withTheme(TreeViewComponent); export default TreeView;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx index e3adaeed7..00d43d99c 100644 --- a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx +++ b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as React from 'react';
import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';
@@ -29,7 +29,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import ListItemLink from '../components/material-ui/listItemLink';
-import connect, { Connect } from '../flux/connect';
+import connect, { Connect, IDispatcher } from '../flux/connect';
+import { MenuAction } from '../actions/menuAction';
+import * as classNames from 'classnames';
const drawerWidth = 240;
@@ -38,41 +40,101 @@ const styles = (theme: Theme) => createStyles({ position: 'relative',
width: drawerWidth,
},
- toolbar: theme.mixins.toolbar
+ toolbar: theme.mixins.toolbar,
+ drawerOpen: {
+ width: drawerWidth,
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ },
+ drawerClose: {
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.leavingScreen,
+ }),
+ overflowX: 'hidden',
+ width: theme.spacing(7) + 1,
+ [theme.breakpoints.up('sm')]: {
+ width: theme.spacing(9) + 1,
+ },
+ }
});
-export const NavigationMenu = withStyles(styles)(connect()(({ classes, state }: WithStyles<typeof styles> & Connect) => {
+const tabletWidthBreakpoint = 768;
+
+export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, dispatch }: WithStyles<typeof styles> & Connect & Connect) => {
const { user } = state.framework.authenticationState
+ const isOpen = state.framework.applicationState.isMenuOpen
+ const closedByUser = state.framework.applicationState.isMenuClosedByUser
+
+ const [responsive, setResponsive] = React.useState(false);
+
+ React.useEffect(() => {
+
+ function handleResize() {
+ if (user && user.isValid) {
+ if (window.innerWidth < tabletWidthBreakpoint && !responsive) {
+ setResponsive(true);
+ if (!closedByUser) {
+ console.log("responsive menu collapsed")
+ dispatch(new MenuAction(false));
+ }
+
+ } else if (window.innerWidth > tabletWidthBreakpoint && responsive) {
+ setResponsive(false);
+ if (!closedByUser) {
+ console.log("responsive menu restored")
+ dispatch(new MenuAction(true));
+ }
+
+ }
+ }
+ }
+ window.addEventListener("resize", handleResize);
+
+
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ }
+ })
+
return (
<Drawer
variant="permanent"
+ className={
+ classNames({
+ [classes.drawerOpen]: isOpen,
+ [classes.drawerClose]: !isOpen
+ })
+ }
classes={{
paper: classes.drawerPaper,
}}
>
{user && user.isValid && <>
<div className={classes.toolbar} />
- { /* https://fiffty.github.io/react-treeview-mui/ */}
- <List component="nav">
+ { /* https://fiffty.github.io/react-treeview-mui/ */}
+ <List component="nav">
<ListItemLink exact to="/" primary="Home" icon={<FontAwesomeIcon icon={faHome} />} />
<Divider />
- {
- state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => {
- const reg = state.framework.applicationRegistraion[key];
- return reg && (
- <ListItemLink
- key={reg.name}
- to={reg.path || `/${reg.name}`}
- primary={reg.menuEntry || reg.name}
- secondary={reg.subMenuEntry}
- icon={reg.icon && <FontAwesomeIcon icon={reg.icon} /> || null} />
- ) || null;
- }) || null
- }
- <Divider />
- <ListItemLink to="/about" primary="About" icon={<FontAwesomeIcon icon={faAddressBook} />} />
+ {
+ state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => {
+ const reg = state.framework.applicationRegistraion[key];
+ return reg && (
+ <ListItemLink
+ key={reg.name}
+ to={reg.path || `/${reg.name}`}
+ primary={reg.menuEntry || reg.name}
+ secondary={reg.subMenuEntry}
+ icon={reg.icon && <FontAwesomeIcon icon={reg.icon} /> || null} />
+ ) || null;
+ }) || null
+ }
+ <Divider />
+ <ListItemLink to="/about" primary="About" icon={<FontAwesomeIcon icon={faAddressBook} />} />
</List>
- </> || null
+ </> || null
}
</Drawer>)
}));
diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx index 81759d628..7168ff496 100644 --- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx +++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as React from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
@@ -24,18 +24,23 @@ import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
+import Block from '@material-ui/icons/Block';
+import Adjust from '@material-ui/icons/Adjust';
import MenuIcon from '@material-ui/icons/Menu';
import AccountCircle from '@material-ui/icons/AccountCircle';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faBan } from '@fortawesome/free-solid-svg-icons';
+import { faDotCircle } from '@fortawesome/free-solid-svg-icons';
import { UpdateAuthentication } from '../actions/authentication';
import { ReplaceAction } from '../actions/navigationActions';
import connect, { Connect, IDispatcher } from '../flux/connect';
import Logo from './logo';
+import { MenuAction, MenuClosedByUser } from '../actions/menuAction';
const styles = (theme: Theme) => createStyles({
appBar: {
@@ -51,6 +56,15 @@ const styles = (theme: Theme) => createStyles({ icon: {
marginLeft: 16,
marginRight: 8
+ },
+ connected: {
+ color: "green"
+ },
+ notConnected: {
+ color: "red"
+ },
+ notificationInfo: {
+ marginLeft: 5
}
});
@@ -59,6 +73,10 @@ const mapDispatch = (dispatcher: IDispatcher) => { logout: () => {
dispatcher.dispatch(new UpdateAuthentication(null));
dispatcher.dispatch(new ReplaceAction("/login"));
+ },
+ toggleMainMenu: (value: boolean, value2: boolean) => {
+ dispatcher.dispatch(new MenuAction(value));
+ dispatcher.dispatch(new MenuClosedByUser(value2))
}
}
};
@@ -69,7 +87,6 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE constructor(props: TitleBarProps) {
super(props);
-
this.state = {
anchorEl: null
}
@@ -78,64 +95,103 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE render(): JSX.Element {
const { classes, state, history, location } = this.props;
const open = !!this.state.anchorEl;
+ let toolbarElements: Array<JSX.Element>;
+ toolbarElements = [];
+
+ // create notificationInfo element
+ const notificationInfo = state.framework.applicationState.isWebsocketAvailable != undefined ?
+ (state.framework.applicationState.isWebsocketAvailable ?
+ <Typography variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.connected} icon={faDotCircle} /> |</Typography> : <Typography variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.notConnected} icon={faBan} /> |</Typography>)
+ : <Typography variant="body1" className={classes.notificationInfo}>Notifications N/A |</Typography>;
+
+
+ // add notificationInfo element before help
+ if (state.framework.applicationRegistraion) {
+ let isNotificationInfoAdded = false;
+ Object.keys(state.framework.applicationRegistraion).map(key => {
+ const reg = state.framework.applicationRegistraion[key];
+ if (reg && reg.statusBarElement) {
+ if (key === "help") {
+ isNotificationInfoAdded = true;
+ toolbarElements.push(notificationInfo);
+ }
+ toolbarElements.push(<reg.statusBarElement key={key} />);
+ }
+ });
+
+ // add notificationInfo in case help wasn't found
+ if (!isNotificationInfoAdded) {
+ toolbarElements.push(notificationInfo);
+ }
+ }
return (
- <AppBar position="absolute" className={ classes.appBar }>
+ <AppBar position="absolute" className={classes.appBar}>
<Toolbar>
- <IconButton className={ classes.menuButton } color="inherit" aria-label="Menu">
+ <IconButton className={classes.menuButton} color="inherit" aria-label="Menu" onClick={this.toggleMainMenu}>
<MenuIcon />
</IconButton>
<Logo />
- <Typography variant="title" color="inherit" >
- { state.framework.applicationState.icon
- ? (<FontAwesomeIcon className={ classes.icon } icon={ state.framework.applicationState.icon } />)
- : null }
- { state.framework.applicationState.title }
+ <Typography variant="h6" color="inherit" >
+ {state.framework.applicationState.icon
+ ? (<FontAwesomeIcon className={classes.icon} icon={state.framework.applicationState.icon} />)
+ : null}
+ {state.framework.applicationState.title}
</Typography>
<div className={classes.grow}></div>
- { state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => {
- const reg = state.framework.applicationRegistraion[key];
- return reg && reg.statusBarElement && <reg.statusBarElement key={key} /> || null
- })}
-
- { state.framework.authenticationState.user
+ {
+ // render toolbar
+ toolbarElements.map((item) => {
+ return item
+ })
+ }
+
+ {state.framework.authenticationState.user
? (<div>
- <Button
- aria-owns={ open ? 'menu-appbar' : undefined }
+ <Button aria-label="current user menu button"
+ aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
- onClick={ this.openMenu }
+ onClick={this.openMenu}
color="inherit"
>
<AccountCircle />
- { state.framework.authenticationState.user.user }
+ {state.framework.authenticationState.user.user}
</Button>
<Menu
id="menu-appbar"
- anchorEl={ this.state.anchorEl }
- anchorOrigin={ {
+ anchorEl={this.state.anchorEl}
+ anchorOrigin={{
vertical: 'top',
horizontal: 'right',
- } }
- transformOrigin={ {
+ }}
+ transformOrigin={{
vertical: 'top',
horizontal: 'right',
- } }
- open={ open }
- onClose={ this.closeMenu }
+ }}
+ open={open}
+ onClose={this.closeMenu}
>
- <MenuItem onClick={ this.closeMenu }>Profile</MenuItem>
- <MenuItem onClick={ () => {
+ {/* <MenuItem onClick={ this.closeMenu }>Profile</MenuItem> */}
+ <MenuItem onClick={() => {
this.props.logout();
this.closeMenu();
- } }>Logout</MenuItem>
+ }}>Logout</MenuItem>
</Menu>
</div>)
- : (<Button onClick={ () => { history.push('/login') } } color="inherit" disabled={ location.pathname == "/login" }>Login</Button>) }
+ : (<Button onClick={() => { history.push('/login') }} color="inherit" disabled={location.pathname == "/login"}>Login</Button>)}
</Toolbar>
</AppBar>
);
};
+ private toggleMainMenu = (event: React.MouseEvent<HTMLElement>) => {
+ console.log(this.props);
+ if (this.props.state.framework.authenticationState.user && this.props.state.framework.authenticationState.user.isValid) {
+ const isMainMenuOpen = this.props.state.framework.applicationState.isMenuOpen
+ const isClosedByUser = this.props.state.framework.applicationState.isMenuClosedByUser
+ this.props.toggleMainMenu(!isMainMenuOpen, !isClosedByUser);
+ }
+ }
private openMenu = (event: React.MouseEvent<HTMLElement>) => {
this.setState({ anchorEl: event.currentTarget });
diff --git a/sdnr/wt/odlux/framework/src/design/default.ts b/sdnr/wt/odlux/framework/src/design/default.ts index 62e28e5c3..542c436f6 100644 --- a/sdnr/wt/odlux/framework/src/design/default.ts +++ b/sdnr/wt/odlux/framework/src/design/default.ts @@ -55,7 +55,26 @@ const theme = createMuiTheme({ dark: "#07819B", contrastText: "#ffffff" }, - } + }, + overrides: { //temp fix for labels turning white after material new version (palette primary color) + MuiFormLabel: { + root: { + "&$focused": { + color: "rgba(143,143,143,1)" + } + }, + + focused: {} + }, + MuiInput: { + underline: { + + "&:after": { + borderBottom: "2px solid #444444" + } + } + } + }, }); export default theme;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/flux/action.ts b/sdnr/wt/odlux/framework/src/flux/action.ts index 8a90f24b2..76890257e 100644 --- a/sdnr/wt/odlux/framework/src/flux/action.ts +++ b/sdnr/wt/odlux/framework/src/flux/action.ts @@ -1,4 +1,22 @@ /** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +/** * Represents an action in the odlux flux architecture. */ export abstract class Action { } diff --git a/sdnr/wt/odlux/framework/src/flux/connect.ts b/sdnr/wt/odlux/framework/src/flux/connect.ts index fca7df6fa..f54e4e0f0 100644 --- a/sdnr/wt/odlux/framework/src/flux/connect.ts +++ b/sdnr/wt/odlux/framework/src/flux/connect.ts @@ -38,19 +38,15 @@ interface IDispatchProps { dispatch: Dispatch; } -type FuncInfer<T> = { - ([...args]: any): T; -}; - -type FunctionResult<T> = T extends FuncInfer<infer U> ? U : never; +type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> type ComponentDecoratorInfer<TMergedProps> = { - <TProps>(wrappedComponent: React.ComponentType<TProps & TMergedProps>): React.ComponentClass<TProps>; + <TProps>(wrappedComponent: React.ComponentType<TProps & TMergedProps>): React.ComponentClass<Omit<TProps & TMergedProps, keyof TMergedProps>>; }; -export type Connect<TMapProps = undefined, TMapDispatch = undefined> = - (TMapProps extends undefined ? IApplicationStoreProps : FunctionResult<TMapProps>) & - (TMapDispatch extends undefined ? IDispatchProps : FunctionResult<TMapDispatch>); +export type Connect<TMapProps extends ((...args: any) => any) | undefined = undefined, TMapDispatch extends ((...args: any) => any) | undefined = undefined> = + (TMapProps extends ((...args: any) => any) ? ReturnType<TMapProps> : IApplicationStoreProps) & + (TMapDispatch extends ((...args: any) => any) ? ReturnType<TMapDispatch> : IDispatchProps); export function connect(): ComponentDecoratorInfer<IApplicationStoreProps & IDispatchProps>; @@ -73,9 +69,9 @@ export function connect<TDispatchProps>( export function connect<TProps, TStateProps, TDispatchProps>( mapStateToProps?: ((state: IApplicationStoreState) => TStateProps), mapDispatchToProps?: ((dispatcher: IDispatcher) => TDispatchProps) -) : +): ((WrappedComponent: React.ComponentType<TProps & (IApplicationStoreProps | TStateProps) & IDispatchProps>) => React.ComponentType<TProps>) { - + const injectApplicationStore = (WrappedComponent: React.ComponentType<TProps & (IApplicationStoreProps | TStateProps) & IDispatchProps>): React.ComponentType<TProps> => { class StoreAdapter extends React.Component<TProps, {}> { @@ -83,7 +79,7 @@ export function connect<TProps, TStateProps, TDispatchProps>( context: IApplicationStoreContext; render(): JSX.Element { - + if (isWrappedComponentIsVersion1(WrappedComponent)) { const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, dispatch: this.store.dispatch.bind(this.store) }); return element; @@ -91,10 +87,10 @@ export function connect<TProps, TStateProps, TDispatchProps>( const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), dispatch: this.store.dispatch.bind(this.store) }); return element; } else if (mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion3(WrappedComponent)) { - const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store)}) as any) }); + const element = React.createElement(WrappedComponent, { ...(this.props as any), ...(mapStateToProps(this.store.state) as any), ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) }); return element; } else if (!mapStateToProps && mapDispatchToProps && isWrappedComponentIsVersion4(WrappedComponent)) { - const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store)}) as any) }); + const element = React.createElement(WrappedComponent, { ...(this.props as any), state: this.store.state, ...(mapDispatchToProps({ dispatch: this.store.dispatch.bind(this.store) }) as any) }); return element; } throw new Error("Invalid arguments in connect."); @@ -158,7 +154,7 @@ export class ApplicationStoreProvider extends React.Component<ApplicationStorePr } render(): JSX.Element { - return React.Children.only(this.props.children); + return React.Children.only(this.props.children) as any; //type error, fix when possible } } diff --git a/sdnr/wt/odlux/framework/src/flux/middleware.ts b/sdnr/wt/odlux/framework/src/flux/middleware.ts index 6179f3bcf..de6505c4f 100644 --- a/sdnr/wt/odlux/framework/src/flux/middleware.ts +++ b/sdnr/wt/odlux/framework/src/flux/middleware.ts @@ -35,7 +35,7 @@ export type ActionHandlerMapObject<S extends { [key: string]: any }, A extends A } export const combineActionHandler = <TState extends { [key: string]: any }, TAction extends Action = Action>(actionHandlers: ActionHandlerMapObject<TState, TAction>) : IActionHandler<TState, TAction> => { - const finalActionHandlers: ActionHandlerMapObject<TState> = {} as ActionHandlerMapObject<TState>; + const finalActionHandlers = {} as { [key: string]: any }; // https://github.com/microsoft/TypeScript/issues/31808 Object.keys(actionHandlers).forEach(actionHandlerKey => { const handler = actionHandlers[actionHandlerKey]; if (typeof handler === 'function') { @@ -55,7 +55,7 @@ export const combineActionHandler = <TState extends { [key: string]: any }, TAct return function combination<TAction extends Action>(state: TState = ({} as TState), action: TAction) { let hasChanged = false; - const nextState : TState = {} as TState; + const nextState = {} as { [key: string]: any }; // https://github.com/microsoft/TypeScript/issues/31808 Object.keys(finalActionHandlers).forEach(key => { const actionHandler = finalActionHandlers[key]; const previousState = state[key]; @@ -76,7 +76,7 @@ export const chainMiddleware = <TStoreState>(...middlewares: Middleware<TStoreSt const middlewareAPI = { getState() { return store.state }, dispatch: <TAction extends Action>(action: TAction) => store.dispatch(action) // we want to use the combinded dispatch - // we should NOT use the flux dispatcher here, since the action would affect ALL stores + // we should NOT use the flux dispatcher here, since the action would affect ALL stores }; const chain = middlewares.map(middleware => middleware(middlewareAPI)); return compose(...chain)(store.dispatch) as Dispatch; diff --git a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts index 75ea92e43..a93f96a82 100644 --- a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts @@ -20,22 +20,25 @@ import { SetTitleAction } from '../actions/titleActions'; import { AddSnackbarNotification, RemoveSnackbarNotification } from '../actions/snackbarActions'; import { AddErrorInfoAction, RemoveErrorInfoAction, ClearErrorInfoAction } from '../actions/errorActions'; - +import { MenuAction, MenuClosedByUser } from '../actions/menuAction' import { IconType } from '../models/iconDefinition'; import { ErrorInfo } from '../models/errorInfo'; import { SnackbarItem } from '../models/snackbarItem'; +import { SetWebsocketAction } from '../actions/websocketAction'; export interface IApplicationState { title: string; appId?: string; icon?: IconType; - + isMenuOpen: boolean; + isMenuClosedByUser: boolean; errors: ErrorInfo[]; snackBars: SnackbarItem[]; + isWebsocketAvailable: boolean | undefined; } -const applicationStateInit: IApplicationState = { title: "Loading ...", errors: [], snackBars:[] }; +const applicationStateInit: IApplicationState = { title: "Loading ...", errors: [], snackBars: [], isMenuOpen: true, isMenuClosedByUser: false, isWebsocketAvailable: undefined }; export const applicationStateHandler: IActionHandler<IApplicationState> = (state = applicationStateInit, action) => { if (action instanceof SetTitleAction) { @@ -84,6 +87,22 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state ...state, snackBars: state.snackBars.filter(s => s.key !== action.key) }; + } else if (action instanceof MenuAction) { + state = { + ...state, + isMenuOpen: action.isOpen + } + } else if (action instanceof MenuClosedByUser) { + state = { + ...state, + isMenuClosedByUser: action.isClosed + } + } + else if (action instanceof SetWebsocketAction) { + state = { + ...state, + isWebsocketAvailable: action.isConnected + } } return state; }; diff --git a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts index c0f2b4215..82b228dc0 100644 --- a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts @@ -21,6 +21,7 @@ import { UpdateAuthentication } from '../actions/authentication'; import { User } from '../models/authentication'; import { onLogin, onLogout } from '../services/applicationApi'; +import { startWebsocketSession, endWebsocketSession } from '../services/notificationService'; export interface IAuthenticationState { user?: User; @@ -28,6 +29,10 @@ export interface IAuthenticationState { const initialToken = localStorage.getItem("userToken"); +if (initialToken !== null) { + startWebsocketSession(); +} + const authenticationStateInit: IAuthenticationState = { user: initialToken && User.fromString(initialToken) || undefined }; @@ -38,9 +43,11 @@ export const authenticationStateHandler: IActionHandler<IAuthenticationState> = const user = action.bearerToken && new User(action.bearerToken) || undefined; if (user) { localStorage.setItem("userToken", user.toString()); + startWebsocketSession(); onLogin(); } else { localStorage.removeItem("userToken"); + endWebsocketSession(); onLogout(); } diff --git a/sdnr/wt/odlux/framework/src/models/elasticSearch.ts b/sdnr/wt/odlux/framework/src/models/elasticSearch.ts index 62a138524..12cfd7d28 100644 --- a/sdnr/wt/odlux/framework/src/models/elasticSearch.ts +++ b/sdnr/wt/odlux/framework/src/models/elasticSearch.ts @@ -1,34 +1,29 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
export type Result<TSource extends {}> = {
- aggregations: TSource;
- took: number;
- timed_out: boolean;
- _shards: {
- total: number;
- successful: number;
- failed: number;
- };
- hits: {
- total: number;
- max_score: number;
- hits?: (HitEntry<TSource>)[] | null;
- };
+ output: {
+ pagination?: {
+ size: number,
+ page: number,
+ total: number
+ },
+ data: TSource[];
+ }
}
export type HitEntry<TSource extends {}> = {
diff --git a/sdnr/wt/odlux/framework/src/models/errorInfo.ts b/sdnr/wt/odlux/framework/src/models/errorInfo.ts index 9081f20f1..21217a108 100644 --- a/sdnr/wt/odlux/framework/src/models/errorInfo.ts +++ b/sdnr/wt/odlux/framework/src/models/errorInfo.ts @@ -16,6 +16,7 @@ * ============LICENSE_END========================================================================== */ export type ErrorInfo = { + title?: string, error?: Error | null, url?: string, line?: number, diff --git a/sdnr/wt/odlux/framework/src/models/restService.ts b/sdnr/wt/odlux/framework/src/models/restService.ts index 053c29b8e..03f580b01 100644 --- a/sdnr/wt/odlux/framework/src/models/restService.ts +++ b/sdnr/wt/odlux/framework/src/models/restService.ts @@ -1,4 +1,22 @@ /** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +/** * The PlainObject type is a JavaScript object containing zero or more key-value pairs. */ export interface PlainObject<T = any> { diff --git a/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts b/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts new file mode 100644 index 000000000..ce4faab6b --- /dev/null +++ b/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts @@ -0,0 +1,52 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + +import { ApplicationStore } from "../store/applicationStore"; +import { UpdateAuthentication } from "../actions/authentication"; +import { ReplaceAction } from "../actions/navigationActions"; + +const maxMinutesTillLogout = 15; +let applicationStore: ApplicationStore | null; +let tickTimer = 15; + + +export const startForceLogoutService = (store: ApplicationStore) => { + applicationStore = store; + createForceLogoutInterval(); +}; + +const createForceLogoutInterval = () => { + console.log("logout timer running...") + + return setInterval(function () { + if (applicationStore && applicationStore.state.framework.authenticationState.user) { + tickTimer--; + + if (tickTimer === 0) { + console.log("got logged out by timer") + if (applicationStore) { + applicationStore.dispatch(new UpdateAuthentication(null)); + applicationStore.dispatch(new ReplaceAction("/login")); + } + } + } + + }, 1 * 60000) +} + +document.addEventListener("mousemove", function () { tickTimer = maxMinutesTillLogout; }, false)
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/services/notificationService.ts b/sdnr/wt/odlux/framework/src/services/notificationService.ts index b12c19985..85d3f716b 100644 --- a/sdnr/wt/odlux/framework/src/services/notificationService.ts +++ b/sdnr/wt/odlux/framework/src/services/notificationService.ts @@ -1,24 +1,31 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as X2JS from 'x2js';
+import { ApplicationStore } from '../store/applicationStore';
+import { SetWebsocketAction } from '../actions/websocketAction';
const socketUrl = [location.protocol === 'https:' ? 'wss://' : 'ws://', 'admin', ':', 'admin', '@', location.hostname, ':', location.port, '/websocket'].join('');
-const subscriptions: { [scope: string]: SubscriptionCallback[] } = { };
+const subscriptions: { [scope: string]: SubscriptionCallback[] } = {};
+let socketReady: Promise<WebSocket>;
+let userLoggedOut = false;
+let wasWebsocketConnectionEstablished: undefined | boolean;
+let applicationStore: ApplicationStore | null;
+
export interface IFormatedMessage {
notifType: string | null;
@@ -27,7 +34,7 @@ export interface IFormatedMessage { export type SubscriptionCallback<TMessage extends IFormatedMessage = IFormatedMessage> = (msg: TMessage) => void;
-function formatData(event: MessageEvent) : IFormatedMessage | undefined {
+function formatData(event: MessageEvent): IFormatedMessage | undefined {
var x2js = new X2JS();
var jsonObj: { [key: string]: IFormatedMessage } = x2js.xml2js(event.data);
@@ -35,7 +42,7 @@ function formatData(event: MessageEvent) : IFormatedMessage | undefined { const notifType = Object.keys(jsonObj)[0];
const formated = jsonObj[notifType];
- formated.notifType = notifType ;
+ formated.notifType = notifType;
formated.time = new Date().toISOString();
return formated;
}
@@ -43,42 +50,31 @@ function formatData(event: MessageEvent) : IFormatedMessage | undefined { }
-export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {
- return socketReady.then((notificationSocket) => {
- const scopes = scope instanceof Array ? scope : [scope];
+export function subscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): boolean {
+ const scopes = scope instanceof Array ? scope : [scope];
- // send all new scopes to subscribe
- const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => {
- const currentCallbacks = subscriptions[cur];
- if (currentCallbacks) {
- if (!currentCallbacks.some(c => c === callback)) {
- currentCallbacks.push(callback);
- }
- } else {
- subscriptions[cur] = [callback];
- acc.push(cur);
+ // send all new scopes to subscribe
+ const newScopesToSubscribe: string[] = scopes.reduce((acc: string[], cur: string) => {
+ const currentCallbacks = subscriptions[cur];
+ if (currentCallbacks) {
+ if (!currentCallbacks.some(c => c === callback)) {
+ currentCallbacks.push(callback);
}
- return acc;
- }, []);
-
- if (newScopesToSubscribe.length === 0) {
- return true;
+ } else {
+ subscriptions[cur] = [callback];
+ acc.push(cur);
}
+ return acc;
+ }, []);
- // send a subscription to all active scopes
- const scopesToSubscribe = Object.keys(subscriptions);
- if (notificationSocket.readyState === notificationSocket.OPEN) {
- const data = {
- 'data': 'scopes',
- 'scopes': scopesToSubscribe
- };
- notificationSocket.send(JSON.stringify(data));
- return true;
- }
- return false;
- });
+ if (newScopesToSubscribe.length === 0) {
+ return true;
+ }
+
+ return true;
}
+
export function unsubscribe<TMessage extends IFormatedMessage = IFormatedMessage>(scope: string | string[], callback: SubscriptionCallback<TMessage>): Promise<boolean> {
return socketReady.then((notificationSocket) => {
const scopes = scope instanceof Array ? scope : [scope];
@@ -107,6 +103,10 @@ export function unsubscribe<TMessage extends IFormatedMessage = IFormatedMessage });
}
+export const startNotificationService = (store: ApplicationStore) => {
+ applicationStore = store;
+}
+
const connect = (): Promise<WebSocket> => {
return new Promise((resolve, reject) => {
const notificationSocket = new WebSocket(socketUrl);
@@ -132,22 +132,58 @@ const connect = (): Promise<WebSocket> => { };
notificationSocket.onerror = function (error) {
- console.log("Socket error: " + error);
+ console.log("Socket error:");
+ console.log(error);
reject("Socket error: " + error);
+ if (applicationStore) {
+ applicationStore.dispatch(new SetWebsocketAction(false));
+ }
};
notificationSocket.onopen = function (event) {
+ if (applicationStore) {
+ applicationStore.dispatch(new SetWebsocketAction(true));
+ }
console.log("Socket connection opened.");
resolve(notificationSocket);
+
+ // send a subscription to all active scopes
+ const scopesToSubscribe = Object.keys(subscriptions);
+ if (notificationSocket.readyState === notificationSocket.OPEN) {
+ const data = {
+ 'data': 'scopes',
+ 'scopes': scopesToSubscribe
+ };
+ notificationSocket.send(JSON.stringify(data));
+ };
};
notificationSocket.onclose = function (event) {
- socketReady = connect();
+ console.log("socket connection closed");
+ if (applicationStore) {
+ applicationStore.dispatch(new SetWebsocketAction(false));
+ }
+ if (!userLoggedOut) {
+ socketReady = connect();
+ }
};
});
}
-let socketReady = connect();
+
+
+
+export const startWebsocketSession = () => {
+ socketReady = connect();
+ userLoggedOut = false;
+}
+
+export const endWebsocketSession = () => {
+ socketReady.then(websocket => {
+ websocket.close();
+ userLoggedOut = true;
+ })
+}
diff --git a/sdnr/wt/odlux/framework/src/services/restService.ts b/sdnr/wt/odlux/framework/src/services/restService.ts index c25deda84..b02d7d19f 100644 --- a/sdnr/wt/odlux/framework/src/services/restService.ts +++ b/sdnr/wt/odlux/framework/src/services/restService.ts @@ -30,38 +30,76 @@ export const formEncode = (params: { [key: string]: string | number }) => Object return encodeURIComponent(key) + '=' + encodeURIComponent(params[key].toString()); }).join('&'); -export async function requestRest<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<TData | false | null> { +/** Sends a rest request to the given path. + * @returns The data, or null it there was any error + */ +export async function requestRest<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<TData | null> { + const res = await requestRestExt<TData>(path, init, authenticate, isResource); + if (res && res.status >= 200 && res.status < 300) { + return res.data; + } + return null; +} + +/** Sends a rest request to the given path and reports the server state. + * @returns An object with the server state, a message and the data. + */ +export async function requestRestExt<TData>(path: string = '', init: RequestInit = {}, authenticate: boolean = true, isResource: boolean = false): Promise<{ status: number, message?: string, data: TData | null }> { + const result: { status: number, message?: string, data: TData | null } = { + status: -1, + data: null, + }; const isAbsUrl = absUrlPattern.test(path); const uri = isAbsUrl ? path : isResource ? path.replace(/\/{2,}/i, '/') : (baseUri) + ('/' + path).replace(/\/{2,}/i, '/'); - init.headers = { + init = { 'method': 'GET', - 'Content-Type': 'application/json', - 'Accept': 'application/json', - ...init.headers + ...init, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...init.headers + } }; if (!isAbsUrl && authenticate && applicationStore) { const { state: { framework: { authenticationState: { user } } } } = applicationStore; // do not request if the user is not valid if (!user || !user.isValid) { - return null; + return { + ...result, + message: "User is not valid or not logged in." + }; } (init.headers = { ...init.headers, - 'Authorization': `${user.tokenType} ${user.token}` + 'Authorization': `${user.tokenType} ${user.token}` //'Authorization': 'Basic YWRtaW46YWRtaW4=' }); } - const result = await fetch(uri, init); - if (result.status === 401 || result.status === 403) { + const fetchResult = await fetch(uri, init); + if (fetchResult.status === 401 || fetchResult.status === 403) { applicationStore && applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${applicationStore.state.framework.navigationState.pathname}`)); - return null; + return { + ...result, + status: 403, + message: "Authentication requested by server." + }; } - const contentType = result.headers.get("Content-Type") || result.headers.get("content-type"); + const contentType = fetchResult.headers.get("Content-Type") || fetchResult.headers.get("content-type"); const isJson = contentType && contentType.toLowerCase().startsWith("application/json"); try { - const data = result.ok && (isJson ? await result.json() : await result.text()) as TData ; - return data; - } catch { - return null; + const data = (isJson ? await fetchResult.json() : await fetchResult.text()) as TData; + return { + ...result, + status: fetchResult.status, + message: fetchResult.statusText, + data: data + }; + } catch (error) { + return { + ...result, + status: fetchResult.status, + message: error && error.message || String(error), + data: null + }; } }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts index 54713f1b1..c18a40b0b 100644 --- a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts +++ b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts @@ -16,70 +16,61 @@ * ============LICENSE_END========================================================================== */ +import { Result } from '../models'; import { DataCallback } from '../components/material-table'; -import { Result, HitEntry } from '../models'; import { requestRest } from '../services/restService'; +import { convertPropertyNames, convertPropertyValues, replaceUpperCase, replaceHyphen } from './yangHelper'; + type propType = string | number | null | undefined | (string | number)[]; type dataType = { [prop: string]: propType }; -export function createSearchDataHandler<TResult extends {} = dataType>(uri: (() => string) | string, additionalParameters?: {}): DataCallback<(TResult & { _id: string })>; -export function createSearchDataHandler<TResult extends {} = dataType, TData = dataType>(uri: (() => string) | string, additionalParameters: {} | null | undefined, mapResult: (res: HitEntry<TResult>, index: number, arr: HitEntry<TResult>[]) => (TData & { _id: string }), mapRequest?: (name?: string | null) => string): DataCallback<(TData & { _id: string })> -export function createSearchDataHandler<TResult, TData>(uri: (() => string) | string, additionalParameters?: {} | null | undefined, mapResult?: (res: HitEntry<TResult>, index: number, arr: HitEntry<TResult>[]) => (TData & { _id: string }), mapRequest?: (name?: string | null) => string): DataCallback<(TData & { _id: string })> { - const fetchData: DataCallback<(TData & { _id: string })> = async (page, rowsPerPage, orderBy, order, filter) => { - const url = `${ window.location.origin }/database/${typeof uri === "function" ? uri(): uri}/_search`; - const from = rowsPerPage && page != null && !isNaN(+page) - ? (+page) * rowsPerPage - : null; +/** Represents a fabric for the searchDataHandler used by the internal data api. + * @param typeName The name of the entry type to create a searchDataHandler for. + * @param additionalFilters Filterproperties and their values to add permanently. + * @returns The searchDataHandler callback to be used with the material table. +*/ +export function createSearchDataHandler<TResult>(typeName: (() => string) | string, additionalFilters?: {} | null | undefined): DataCallback<(TResult)> { + const fetchData: DataCallback<(TResult)> = async (pageIndex, rowsPerPage, orderBy, order, filter) => { + const url = `/restconf/operations/data-provider:read-${typeof typeName === "function" ? typeName(): typeName}-list`; + + filter = { ...filter, ...additionalFilters }; const filterKeys = filter && Object.keys(filter) || []; const query = { - ...filterKeys.length > 0 ? { - query: { - bool: { - must: filterKeys.reduce((acc, cur) => { - if (acc && filter && filter[cur]) { - acc.push({ [filter[cur].indexOf("*") > -1 || filter[cur].indexOf("?") > -1 ? "wildcard" : "term"]: { [mapRequest ? mapRequest(cur) : cur]: filter[cur] } }); - } - return acc; - }, [] as any[]) - } - } - } : { "query": { "match_all": {} } }, - ...rowsPerPage ? { "size": rowsPerPage } : {}, - ...from ? { "from": from } : {}, - ...orderBy && order ? { "sort": [{ [mapRequest ? mapRequest(orderBy) : orderBy]: order }] } : {}, - ...additionalParameters ? additionalParameters : {} + input: { + filter: filterKeys.filter(f => filter![f] != null && filter![f] !== "").map(property => ({ property, filtervalue: filter![property]})), + sortorder: orderBy ? [{ property: orderBy, sortorder: order === "desc" ? "descending" : "ascending" }] : [], + pagination: { size: rowsPerPage, page: (pageIndex != null && pageIndex > 0 && pageIndex || 0) +1 } + } }; - const result = await requestRest<Result<TResult & { _id: string }>>(url, { + const result = await requestRest<Result<TResult>>(url, { method: "POST", // *GET, POST, PUT, DELETE, etc. - mode: "no-cors", // no-cors, cors, *same-origin + mode: "same-origin", // no-cors, cors, *same-origin cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached headers: { - "Content-Type": "application/json; charset=utf-8", + "Content-Type": "application/json", // "Content-Type": "application/x-www-form-urlencoded", }, - body: JSON.stringify(query), // body data type must match "Content-Type" header + body: JSON.stringify(convertPropertyValues(query, replaceUpperCase)), // body data type must match "Content-Type" header }); if (result) { - let rows: (TData & { _id: string })[] = []; + let rows: TResult[] = []; - if (result && result.hits && result.hits.hits) { - rows = result.hits.hits.map( mapResult ? mapResult : h => ( - { ...(h._source as any as TData), _id: h._id } - )) || [] + if (result && result.output && result.output.data) { + rows = result.output.data.map(obj => convertPropertyNames(obj, replaceHyphen)) || [] } const data = { - page: Math.min(page || 0, result.hits.total || 0 / (rowsPerPage || 1)), rowCount: result.hits.total, rows: rows + page: result.output.pagination && result.output.pagination.page != null && result.output.pagination.page - 1 || 0 , total: result.output.pagination && result.output.pagination.total || 0, rows: rows }; return data; } - return { page: 0, rowCount: 0, rows: [] }; + return { page: 1, total: 0, rows: [] }; }; return fetchData; diff --git a/sdnr/wt/odlux/framework/src/utilities/yangHelper.ts b/sdnr/wt/odlux/framework/src/utilities/yangHelper.ts new file mode 100644 index 000000000..127f3e07d --- /dev/null +++ b/sdnr/wt/odlux/framework/src/utilities/yangHelper.ts @@ -0,0 +1,39 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + */ + + +export const replaceHyphen = (name: string) => name.replace(/-([a-z])/g, (g) => (g[1].toUpperCase())); +export const replaceUpperCase = (name: string) => name.replace(/([a-z][A-Z])/g, (g) => g[0] + '-' + g[1].toLowerCase()); + +export const convertPropertyNames = <T extends { [prop: string]: any }>(obj: T, conv: (name: string) => string): T => { + return Object.keys(obj).reduce<{ [prop: string]: any }>((acc, cur) => { + acc[conv(cur)] = typeof obj[cur] === "object" ? convertPropertyNames(obj[cur], conv) : obj[cur]; + return acc; + }, obj instanceof Array ? [] : {}) as T; +} + +export const convertPropertyValues = <T extends { [prop: string]: any }>(obj: T, conv: (name: string) => string): T => { + return Object.keys(obj).reduce<{ [prop: string]: any }>((acc, cur) => { + acc[cur] = typeof obj[cur] === "object" + ? convertPropertyValues(obj[cur], conv) + : cur === "property" + ? conv(obj[cur]) + : obj[cur]; + return acc; + }, obj instanceof Array ? [] : {}) as T; +}
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/views/frame.tsx b/sdnr/wt/odlux/framework/src/views/frame.tsx index a39516de3..b93b7ee01 100644 --- a/sdnr/wt/odlux/framework/src/views/frame.tsx +++ b/sdnr/wt/odlux/framework/src/views/frame.tsx @@ -1,20 +1,20 @@ -/** - * ============LICENSE_START======================================================================== - * ONAP : ccsdk feature sdnr wt odlux - * ================================================================================================= - * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. - * ================================================================================================= - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * ============LICENSE_END========================================================================== - */ +/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
import * as React from 'react';
import { HashRouter as Router, Route, Redirect, Switch } from 'react-router-dom';
@@ -49,60 +49,70 @@ const styles = (theme: Theme) => createStyles({ display: "flex",
flexDirection: "column",
backgroundColor: theme.palette.background.default,
- padding: theme.spacing.unit * 3,
+ padding: theme.spacing(3),
minWidth: 0, // So the Typography noWrap works
},
toolbar: theme.mixins.toolbar
});
-export const Frame = withStyles(styles)(({ classes }: WithStyles<typeof styles>) => {
- const registrations = applicationService.applications;
- return (
- <SnackbarProvider maxSnack={3}>
- <Router>
- <div className={ classes.root }>
- <SnackDisplay />
- <ErrorDisplay />
- <TitleBar />
- <Menu />
- <main className={ classes.content }>
- <div className={ classes.toolbar } />
- <Switch>
- <Route exact path="/" component={ () => (
- <AppFrame title={ "Home" } icon={ faHome } >
- <Home />
- </AppFrame>
- ) } />
- <Route path="/about" component={ () => (
- <AppFrame title={ "About" } icon={ faAddressBook } >
- <About />
- </AppFrame>
- )} />
- { process.env.NODE_ENV === "development" ? <Route path="/test" component={() => (
- <AppFrame title={"Test"} icon={faAddressBook} >
- <Test />
- </AppFrame>
- )} /> : null}
- <Route path="/login" component={ () => (
- <AppFrame title={ "Login" } icon={ faSignInAlt } >
- <Login />
- </AppFrame>
- ) } />
- { Object.keys(registrations).map(p => {
- const application = registrations[p];
- return (<Route key={ application.name } path={ application.path || `/${ application.name }` } component={ () => (
- <AppFrame title={ application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name } icon={ application.icon } appId={application.name} >
- <application.rootComponent />
+type FrameProps = WithStyles<typeof styles>;
+
+class FrameComponent extends React.Component<FrameProps>{
+
+ render() {
+ const registrations = applicationService.applications;
+ const { classes } = this.props;
+ return (
+ <SnackbarProvider maxSnack={3}>
+ <Router>
+ <div className={classes.root}>
+ <SnackDisplay />
+ <ErrorDisplay />
+ <TitleBar />
+ <Menu />
+ <main className={classes.content}>
+ {
+ <div className={classes.toolbar} /> //needed for margins, don't remove!
+ }
+ <Switch>
+ <Route exact path="/" component={() => (
+ <AppFrame title={"Home"} icon={faHome} >
+ <Home />
</AppFrame>
- ) } />)
- }) }
- <Redirect to="/" />
- </Switch>
- </main>
- </div>
- </Router>
- </SnackbarProvider>
- );
-});
+ )} />
+ <Route path="/about" component={() => (
+ <AppFrame title={"About"} icon={faAddressBook} >
+ <About />
+ </AppFrame>
+ )} />
+ {process.env.NODE_ENV === "development" ? <Route path="/test" component={() => (
+ <AppFrame title={"Test"} icon={faAddressBook} >
+ <Test />
+ </AppFrame>
+ )} /> : null}
+ <Route path="/login" component={() => (
+ <AppFrame title={"Login"} icon={faSignInAlt} >
+ <Login />
+ </AppFrame>
+ )} />
+ {Object.keys(registrations).map(p => {
+ const application = registrations[p];
+ return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => (
+ <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} >
+ <application.rootComponent />
+ </AppFrame>
+ )} />)
+ })}
+ <Redirect to="/" />
+ </Switch>
+ </main>
+ </div>
+ </Router>
+ </SnackbarProvider>
+ );
+ }
+}
+
+export const Frame = withStyles(styles)(FrameComponent);
export default Frame;
diff --git a/sdnr/wt/odlux/framework/src/views/home.tsx b/sdnr/wt/odlux/framework/src/views/home.tsx index e20c46aa4..4a4084e88 100644 --- a/sdnr/wt/odlux/framework/src/views/home.tsx +++ b/sdnr/wt/odlux/framework/src/views/home.tsx @@ -15,14 +15,13 @@ * the License. * ============LICENSE_END========================================================================== */ -import * as React from 'react';
-
-export const Home = (props: React.Props<any>) => {
- return (
- <div>
- <h1>Welcome to ODLUX.</h1>
- </div>
- )
-}
-
+import * as React from 'react'; + +export const Home = (props: React.Props<any>) => { + return ( + <div> + <h1>Welcome to ODLUX.</h1> + </div> + ) +} export default Home;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx index 9b69ecd7e..3f6ef6134 100644 --- a/sdnr/wt/odlux/framework/src/views/login.tsx +++ b/sdnr/wt/odlux/framework/src/views/login.tsx @@ -45,31 +45,31 @@ const styles = (theme: Theme) => createStyles({ layout: { width: 'auto', display: 'block', // Fix IE11 issue. - marginLeft: theme.spacing.unit * 3, - marginRight: theme.spacing.unit * 3, - [theme.breakpoints.up(400 + theme.spacing.unit * 3 * 2)]: { + marginLeft: theme.spacing(3), + marginRight: theme.spacing(3), + [theme.breakpoints.up(400 + theme.spacing(3) * 2)]: { width: 400, marginLeft: 'auto', marginRight: 'auto', }, }, paper: { - marginTop: theme.spacing.unit * 8, + marginTop: theme.spacing(8), display: 'flex', flexDirection: 'column', alignItems: 'center', - padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px`, + padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(3)}px`, }, avatar: { - margin: theme.spacing.unit, + margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main, }, form: { width: '100%', // Fix IE11 issue. - marginTop: theme.spacing.unit, + marginTop: theme.spacing(1), }, submit: { - marginTop: theme.spacing.unit * 3, + marginTop: theme.spacing(3), }, }); @@ -87,7 +87,7 @@ interface ILoginState { // todo: ggf. redirect to einbauen class LoginComponent extends React.Component<LoginProps, ILoginState> { - constructor (props: LoginProps) { + constructor(props: LoginProps) { super(props); this.state = { @@ -142,13 +142,13 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { /> </FormControl> <FormControlLabel - control={<Checkbox value="remember" color="primary" />} + control={<Checkbox value="remember" color="secondary" />} label="Remember me" /> <Button type="submit" fullWidth - variant="raised" + variant="contained" color="primary" disabled={this.state.busy} className={classes.submit} |