diff options
Diffstat (limited to 'sdnr/wt/odlux/framework/src')
30 files changed, 647 insertions, 164 deletions
diff --git a/sdnr/wt/odlux/framework/src/actions/authentication.ts b/sdnr/wt/odlux/framework/src/actions/authentication.ts index 20a248dc1..f1d11de37 100644 --- a/sdnr/wt/odlux/framework/src/actions/authentication.ts +++ b/sdnr/wt/odlux/framework/src/actions/authentication.ts @@ -18,9 +18,11 @@ import { Dispatch } from '../flux/store'; import { Action } from '../flux/action'; import { AuthPolicy, User } from '../models/authentication'; -import { GeneralSettings } from '../models/settings'; -import { SetGeneralSettingsAction, setGeneralSettingsAction } from './settingsAction'; +import { GeneralSettings, Settings } from '../models/settings'; +import { saveInitialSettings, SetGeneralSettingsAction, setGeneralSettingsAction } from './settingsAction'; import { endWebsocketSession } from '../services/notificationService'; +import { endUserSession, startUserSession } from '../services/userSessionService'; +import { IApplicationStoreState } from '../store/applicationStore'; export class UpdateUser extends Action { @@ -40,16 +42,30 @@ export class UpdatePolicies extends Action { export const loginUserAction = (user?: User) => (dispatcher: Dispatch) =>{ dispatcher(new UpdateUser(user)); - loadUserSettings(user, dispatcher); - - + if(user){ + startUserSession(user); + loadUserSettings(user, dispatcher); + localStorage.setItem("userToken", user.toString()); + } } -export const logoutUser = () => (dispatcher: Dispatch) =>{ +export const logoutUser = () => (dispatcher: Dispatch, getState: () => IApplicationStoreState) =>{ + + const {framework:{applicationState:{ authentication }, authenticationState: {user}}} = getState(); dispatcher(new UpdateUser(undefined)); dispatcher(new SetGeneralSettingsAction(null)); endWebsocketSession(); + endUserSession(); + localStorage.removeItem("userToken"); + + + //only call if a user is currently logged in + if (authentication === "oauth" && user) { + + const url = window.location.origin; + window.location.href=`${url}/oauth/logout`; + } } const loadUserSettings = (user: User | undefined, dispatcher: Dispatch) =>{ @@ -74,14 +90,8 @@ const loadUserSettings = (user: User | undefined, dispatcher: Dispatch) =>{ }else{ return null; } - }).then((result:GeneralSettings)=>{ - if(result?.general){ - //will start websocket session if applicable - dispatcher(setGeneralSettingsAction(result.general.areNotificationsEnabled!)); - - }else{ - dispatcher(setGeneralSettingsAction(false)); - } + }).then((result:Settings)=>{ + dispatcher(saveInitialSettings(result)); }) } }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/actions/settingsAction.ts b/sdnr/wt/odlux/framework/src/actions/settingsAction.ts index ffcdfc26a..1c18ddc8e 100644 --- a/sdnr/wt/odlux/framework/src/actions/settingsAction.ts +++ b/sdnr/wt/odlux/framework/src/actions/settingsAction.ts @@ -18,47 +18,113 @@ import { Dispatch } from "../flux/store"; import { Action } from "../flux/action"; -import { GeneralSettings } from "../models/settings"; +import { GeneralSettings, Settings, TableSettings, TableSettingsColumn } from "../models/settings"; import { getSettings, putSettings } from "../services/settingsService"; import { startWebsocketSession, suspendWebsocketSession } from "../services/notificationService"; +import { IApplicationStoreState } from "../store/applicationStore"; -export class SetGeneralSettingsAction extends Action{ +export class SetGeneralSettingsAction extends Action { /** * */ - constructor(public areNoticationsActive: boolean|null) { + constructor(public areNoticationsActive: boolean | null) { super(); - } } -export const setGeneralSettingsAction = (value: boolean) => (dispatcher: Dispatch) =>{ +export class SetTableSettings extends Action { + + constructor(public tableName: string, public updatedColumns: TableSettingsColumn[]) { + super(); + } +} + +export class LoadSettingsAction extends Action { + + constructor(public settings: Settings & { isInitialLoadDone: true }) { + super(); + } + +} + +export class SettingsDoneLoadingAction extends Action { + +} + +export const setGeneralSettingsAction = (value: boolean) => (dispatcher: Dispatch) => { dispatcher(new SetGeneralSettingsAction(value)); - if(value){ + if (value) { startWebsocketSession(); - }else{ + } else { suspendWebsocketSession(); } } -export const updateGeneralSettingsAction = (activateNotifications: boolean) => async (dispatcher: Dispatch) =>{ +export const updateGeneralSettingsAction = (activateNotifications: boolean) => async (dispatcher: Dispatch) => { - const value: GeneralSettings = {general:{areNotificationsEnabled: activateNotifications}}; + const value: GeneralSettings = { general: { areNotificationsEnabled: activateNotifications } }; const result = await putSettings("/general", JSON.stringify(value.general)); dispatcher(setGeneralSettingsAction(activateNotifications)); } +export const updateTableSettings = (tableName: string, columns: TableSettingsColumn[]) => async (dispatcher: Dispatch, getState: () => IApplicationStoreState) => { + + + //TODO: ask micha how to handle object with variable properties! + //fix for now: just safe everything! + + let {framework:{applicationState:{settings:{tables}}}} = getState(); + + tables[tableName] = { columns: columns }; + const json=JSON.stringify(tables); + + // would only save latest entry + //const json = JSON.stringify({ [tableName]: { columns: columns } }); + + const result = await putSettings("/tables", json); + + dispatcher(new SetTableSettings(tableName, columns)); +} + export const getGeneralSettingsAction = () => async (dispatcher: Dispatch) => { const result = await getSettings<GeneralSettings>(); - if(result && result.general){ + if (result && result.general) { dispatcher(new SetGeneralSettingsAction(result.general.areNotificationsEnabled!)) } +} + +export const saveInitialSettings = (settings: any) => async (dispatcher: Dispatch) => { + + const defaultSettings = {general:{ areNotificationsEnabled: false }, tables:{}}; + + const initialSettings = {...defaultSettings, ...settings}; + + if (initialSettings) { + if (initialSettings.general) { + const settingsActive = initialSettings.general.areNotificationsEnabled; + + if (settingsActive) { + startWebsocketSession(); + } else { + suspendWebsocketSession(); + } + } + + dispatcher(new LoadSettingsAction(initialSettings)); + } + else { + dispatcher(new SettingsDoneLoadingAction()); + + } + + + }
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/assets/images/home.svg b/sdnr/wt/odlux/framework/src/assets/images/home.svg new file mode 100644 index 000000000..080d0502e --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/images/home.svg @@ -0,0 +1,20 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="460.298px" height="460.297px" viewBox="0 0 460.298 460.297" style="enable-background:new 0 0 460.298 460.297;" + xml:space="preserve"> +<g> + <g> + <path fill="#565656" d="M230.149,120.939L65.986,256.274c0,0.191-0.048,0.472-0.144,0.855c-0.094,0.38-0.144,0.656-0.144,0.852v137.041 + c0,4.948,1.809,9.236,5.426,12.847c3.616,3.613,7.898,5.431,12.847,5.431h109.63V303.664h73.097v109.64h109.629 + c4.948,0,9.236-1.814,12.847-5.435c3.617-3.607,5.432-7.898,5.432-12.847V257.981c0-0.76-0.104-1.334-0.288-1.707L230.149,120.939 + z"/> + <path fill="#565656" d="M457.122,225.438L394.6,173.476V56.989c0-2.663-0.856-4.853-2.574-6.567c-1.704-1.712-3.894-2.568-6.563-2.568h-54.816 + c-2.666,0-4.855,0.856-6.57,2.568c-1.711,1.714-2.566,3.905-2.566,6.567v55.673l-69.662-58.245 + c-6.084-4.949-13.318-7.423-21.694-7.423c-8.375,0-15.608,2.474-21.698,7.423L3.172,225.438c-1.903,1.52-2.946,3.566-3.14,6.136 + c-0.193,2.568,0.472,4.811,1.997,6.713l17.701,21.128c1.525,1.712,3.521,2.759,5.996,3.142c2.285,0.192,4.57-0.476,6.855-1.998 + L230.149,95.817l197.57,164.741c1.526,1.328,3.521,1.991,5.996,1.991h0.858c2.471-0.376,4.463-1.43,5.996-3.138l17.703-21.125 + c1.522-1.906,2.189-4.145,1.991-6.716C460.068,229.007,459.021,226.961,457.122,225.438z"/> + +<path fill="#D81036" d="M 457.122 225.438 L 251.849 54.417 L 251.849 54.417 C 245.765 49.468 238.531 46.994 230.155 46.994 C 221.78 46.994 214.547 49.468 208.457 54.417 L 3.172 225.438 C 1.269 226.958 0.226 229.004 0.032 231.574 C -0.161 234.142 0.504 236.385 2.029 238.287 L 19.73 259.415 C 21.255 261.127 23.251 262.174 25.726 262.557 C 28.011 262.749 30.296 262.081 32.581 260.559 L 230.149 95.817 L 427.719 260.558 C 429.245 261.886 431.24 262.549 433.715 262.549 H 434.573 C 437.044 262.173 439.036 261.119 440.569 259.411 L 458.272 238.286 C 459.794 236.38 460.461 234.141 460.263 231.57 C 460.068 229.007 459.021 226.961 457.122 225.438 Z"/> + </g> +</g> +</svg> diff --git a/sdnr/wt/odlux/framework/src/assets/images/home.svg.d.ts b/sdnr/wt/odlux/framework/src/assets/images/home.svg.d.ts new file mode 100644 index 000000000..7098d79a2 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/assets/images/home.svg.d.ts @@ -0,0 +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========================================================================== + */ + + declare const home: string; + export default home;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts b/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts index ce5b2cd1e..3ed313497 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts +++ b/sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts @@ -37,6 +37,7 @@ export type ColumnModel<TData> = { disablePadding?: boolean; width?: string | number ; className?: string; + hide?: boolean; style?: React.CSSProperties; align?: 'inherit' | 'left' | 'center' | 'right' | 'justify'; disableSorting?: boolean; 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 aac2a1252..8541cfe56 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/index.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/index.tsx @@ -36,7 +36,7 @@ import { EnhancedTableHead } from './tableHead'; import { EnhancedTableFilter } from './tableFilter'; import { ColumnModel, ColumnType } from './columnModel'; -import { Menu } from '@mui/material'; +import { Menu, Typography } from '@mui/material'; import { DistributiveOmit } from '@mui/types'; import makeStyles from '@mui/styles/makeStyles'; @@ -151,19 +151,23 @@ export type MaterialTableComponentState<TData = {}> = { rowsPerPage: number; loading: boolean; showFilter: boolean; + hiddenColumns: string[]; filter: { [property: string]: string }; }; export type TableApi = { forceRefresh?: () => Promise<void> }; -type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & { +type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & { className?: string; columns: ColumnModel<TData>[]; idProperty: keyof TData | ((data: TData) => React.Key); - tableId?: string; + + //Note: used to save settings as well. Must be unique across apps. Null tableIds will not get saved to the settings + tableId: string | null; isPopup?: boolean; title?: string; stickyHeader?: boolean; + allowHtmlHeader?: boolean; defaultSortOrder?: 'asc' | 'desc'; defaultSortColumn?: keyof TData; enableSelection?: boolean; @@ -182,6 +186,8 @@ type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableCom onHandleChangePage: (page: number) => void; onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void; onHandleRequestSort: (property: string) => void; + onHideColumns : (columnNames: string[]) => void + onShowColumns: (columnNames: string[]) => void }; type MaterialTableComponentProps<TData = {}> = @@ -203,13 +209,18 @@ function isMaterialTableComponentPropsWithRowsAndRequestData(props: MaterialTabl propsWithExternalState.onHandleChangePage instanceof Function || propsWithExternalState.onHandleChangeRowsPerPage instanceof Function || propsWithExternalState.onToggleFilter instanceof Function || + propsWithExternalState.onHideColumns instanceof Function || propsWithExternalState.onHandleRequestSort instanceof Function } +// get settings in here! + + class MaterialTableComponent<TData extends {} = {}> extends React.Component<MaterialTableComponentProps, MaterialTableComponentState & { contextMenuInfo: { index: number; mouseX?: number; mouseY?: number }; }> { constructor(props: MaterialTableComponentProps) { super(props); + const page = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.page : 0; const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10; @@ -224,6 +235,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null, rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [], total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0, + hiddenColumns: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) && this.props.hiddenColumns || [], page, rowsPerPage, }; @@ -237,18 +249,27 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } } render(): JSX.Element { - const { classes, columns } = this.props; + const { classes, columns, allowHtmlHeader } = this.props; 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 toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) } + + const hideColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onHideColumns : (data: string[]) => { const newArray = [...new Set([...this.state.hiddenColumns, ...data])]; this.setState({hiddenColumns:newArray}); } + const showColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onShowColumns : (data: string[]) => { const newArray = this.state.hiddenColumns.filter(el=> !data.includes(el)); this.setState({hiddenColumns:newArray}); } + + const allColumnsHidden = this.props.columns.length === this.state.hiddenColumns.length; return ( <Paper className={this.props.className ? `${classes.root} ${this.props.className}` : classes.root}> <TableContainer className={classes.container}> <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv} - onToggleFilter={toggleFilter} /> + onToggleFilter={toggleFilter} + columns={columns} + onHideColumns={hideColumns} + onShowColumns={showColumns} /> <Table padding="normal" aria-label={this.props.tableId ? this.props.tableId : 'tableTitle'} stickyHeader={this.props.stickyHeader || false} > <EnhancedTableHead + allowHtmlHeader={allowHtmlHeader || false} columns={columns} numSelected={selected && selected.length} order={order} @@ -257,10 +278,14 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate onRequestSort={this.onHandleRequestSort} rowCount={rows.length} enableSelection={this.props.enableSelection} + hiddenColumns={this.state.hiddenColumns} /> <TableBody> - {showFilter && <EnhancedTableFilter columns={columns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null} - {rows // may need ordering here + {showFilter && <EnhancedTableFilter columns={columns} hiddenColumns={this.state.hiddenColumns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null} + + {allColumnsHidden ? <Typography variant="body1" textAlign="center">All columns of this table are hidden.</Typography> : + + rows // may need ordering here .map((entry: TData & { [RowDisabled]?: boolean, [kex: string]: any }, index) => { const entryId = getId(entry); const contextMenu = (this.props.createContextMenu && this.state.contextMenuInfo.index === index && this.props.createContextMenu(entry)) || null; @@ -295,15 +320,17 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate > {this.props.enableSelection ? <TableCell padding="checkbox" style={{ width: "50px", color: entry[RowDisabled] || false ? "inherit" : undefined } }> - <Checkbox checked={isSelected} /> + <Checkbox color='secondary' checked={isSelected} /> </TableCell> : null } { + this.props.columns.map( col => { const style = col.width ? { width: col.width } : {}; - return ( + const tableCell = ( + <TableCell style={ entry[RowDisabled] || false ? { ...style, color: "inherit" } : style } aria-label={col.title? toAriaLabel(col.title) : toAriaLabel(col.property)} key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} > {col.type === ColumnType.custom && col.customControl ? <col.customControl className={col.className} style={col.style} rowData={entry} /> @@ -313,6 +340,10 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } </TableCell> ); + + //show column if... + const showColumn = !this.state.hiddenColumns.includes(col.property); + return showColumn && tableCell } ) } @@ -352,6 +383,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate } static getDerivedStateFromProps(props: MaterialTableComponentProps, state: MaterialTableComponentState & { _rawRows: {}[] }): MaterialTableComponentState & { _rawRows: {}[] } { + if (isMaterialTableComponentPropsWithRowsAndRequestData(props)) { return { ...state, @@ -363,6 +395,7 @@ class MaterialTableComponent<TData extends {} = {}> extends React.Component<Mate loading: props.loading, showFilter: props.showFilter, page: props.page, + hiddenColumns: props.hiddenColumns, rowsPerPage: props.rowsPerPage } } else if (isMaterialTableComponentPropsWithRows(props) && props.asynchronus && state._rawRows !== props.rows) { diff --git a/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx b/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx new file mode 100644 index 000000000..f8ae6ea97 --- /dev/null +++ b/sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx @@ -0,0 +1,188 @@ +/** + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt odlux + * ================================================================================================= + * Copyright (C) 2022 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 { Button, Checkbox, FormControlLabel, MenuItem, Popover, Switch, Typography } from '@mui/material'; +import connect, { Connect, IDispatcher } from '../../flux/connect'; +import * as React from 'react'; + +import { ColumnModel } from './columnModel'; +import { IApplicationStoreState } from '../../store/applicationStore'; +import { TableSettingsColumn } from '../../models/settings'; +import { updateTableSettings } from '../../actions/settingsAction'; + +const mapStateToProps = (state: IApplicationStoreState) => ({ + settings: state.framework.applicationState.settings, + settingsDoneLoading: state.framework.applicationState.settings.isInitialLoadDone +}); + +const mapDispatchToProps = (dispatcher: IDispatcher) => ({ + saveToSettings: (tableName: string, columns: TableSettingsColumn[]) => dispatcher.dispatch(updateTableSettings(tableName, columns)) +}) + +type DialogProps = { + columns: ColumnModel<{}>[], + settingsName: string | null, + anchorEl: HTMLElement | null; + hideColumns: (columnNames: string[]) => void + showColumns: (columnNames: string[]) => void + onClose(): void + +} & Connect<typeof mapStateToProps, typeof mapDispatchToProps>; + + //TODO: figure out why everything gets triggered twice... + +const ShowColumnsDialog: React.FunctionComponent<DialogProps> = (props) => { + + const savedSettings = props.settingsName && props.settings.tables[props.settingsName]; + + const [checkedColumns, setCheckedColumns] = React.useState<{ property: string, display: boolean, title: string | undefined }[]>([]); + + const open = Boolean(props.anchorEl); + const allColumnNames = props.columns.map(e => e.property); + + React.useEffect(() => { + + createHideShowSelection(); + + }, []); + + React.useEffect(() => { + + createHideShowSelection(); + + }, [props.settings.isInitialLoadDone]); + + + const createHideShowSelection = () => { + let columns = props.columns.map(e => { return { property: e.property, display: !Boolean(e.hide), title: e.title } }); + + + if (savedSettings) { + + if (columns.length !== savedSettings.columns.length) { + console.error("saved column length does not match current column length. Maybe a settings entry got wrongly overridden?") + } + + //overwrite column data with settings + savedSettings?.columns.forEach(el => { + let foundIndex = columns.findIndex(e => e.property == el.property); + if (columns[foundIndex] !== undefined) + columns[foundIndex].display = el.displayed; + }); + + } else { + console.warn("No settingsName set, changes will not be saved.") + } + + setCheckedColumns(columns); + + const hideColumns = columns.filter(el => !el.display).map(e => e.property); + props.hideColumns(hideColumns); + } + + + const handleChange = (propertyName: string, checked: boolean) => { + if (!checked) { + props.hideColumns([propertyName]); + } else { + props.showColumns([propertyName]) + + } + + let updatedList = checkedColumns.map(item => { + if (item.property == propertyName) { + return { ...item, display: checked }; + } + return item; + }); + + setCheckedColumns(updatedList); + }; + + const onHideAll = () => { + + switchCheckedColumns(false); + props.hideColumns(allColumnNames); + } + + const onShowAll = () => { + + switchCheckedColumns(true); + props.showColumns(allColumnNames); + } + + const onClose = () => { + + const tableColumns: TableSettingsColumn[] = checkedColumns.map(el => { + return { + property: el.property, + displayed: el.display + } + }); + + if (props.settingsName) { + props.saveToSettings(props.settingsName, tableColumns); + } + props.onClose(); + + } + + const switchCheckedColumns = (changeToValue: boolean) => { + let updatedList = checkedColumns.map(item => { + return { ...item, display: changeToValue }; + }); + + setCheckedColumns(updatedList); + + } + + return (<Popover open={open} onClose={onClose} + anchorEl={props.anchorEl} + anchorOrigin={{ + vertical: 'top', + horizontal: 'left', + }} > + <div> + <Typography fontWeight={600} style={{ margin: 10 }} >Hide / Show Columns</Typography> + </div> + <div style={{ display: "flex", flexDirection: "column", margin: 10 }}> + { + checkedColumns?.map((el, i) => { + + return <> + + <FormControlLabel + value="end" + key={"hide-show-column-"+i} + aria-label={"hide-or-show-column-button"} + control={<Switch color="secondary" checked={el.display} onChange={e => handleChange(el.property, e.target.checked)} />} + label={el.title || el.property} + labelPlacement="end" + /> + </> + }) + } + <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between" }}> + <Button color="secondary" aria-label="hide-all-columns-button" onClick={(e) => onHideAll()}>Hide all</Button> + <Button color="secondary" aria-label="show-all-columns-button" onClick={(e) => onShowAll()}>Show all</Button> + </div> + </div> + </Popover>) +} + +export default connect(mapStateToProps, mapDispatchToProps)(ShowColumnsDialog); 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 a46dd18d8..1b9136844 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx @@ -50,6 +50,7 @@ interface IEnhancedTableFilterComponentProps extends WithStyles<typeof styles> { onFilterChanged: (property: string, filterTerm: string) => void; filter: { [property: string]: string }; columns: ColumnModel<{}>[]; + hiddenColumns: string[]; enableSelection?: boolean; } @@ -73,7 +74,7 @@ class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterC } {columns.map((col, ind) => { const style = col.width ? { width: col.width } : {}; - return ( + const tableCell = ( <TableCell className={col.type === ColumnType.numeric ? classes.numberInput : ''} key={col.property} @@ -99,6 +100,10 @@ class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterC onChange={this.createInputFilterHandler(col.property)} />} </TableCell> ); + + const showColumn = !this.props.hiddenColumns.includes(col.property); + + return showColumn && tableCell; }, this)} </TableRow> ); diff --git a/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx b/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx index c500f44ce..d6f7b7def 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx @@ -50,7 +50,9 @@ interface IEnhancedTableHeadComponentProps extends styles_header { orderBy: string | null; rowCount: number; columns: ColumnModel<{}>[]; + hiddenColumns: string[]; enableSelection?: boolean; + allowHtmlHeader?: boolean; } class EnhancedTableHeadComponent extends React.Component<IEnhancedTableHeadComponentProps> { @@ -77,7 +79,7 @@ class EnhancedTableHeadComponent extends React.Component<IEnhancedTableHeadCompo } { columns.map(col => { const style = col.width ? { width: col.width } : {}; - return ( + const tableCell = ( <TableCell className= {classes.header} key={ col.property } align={ col.type === ColumnType.numeric ? 'right' : 'left' } @@ -102,11 +104,19 @@ class EnhancedTableHeadComponent extends React.Component<IEnhancedTableHeadCompo direction={ order || undefined } onClick={ this.createSortHandler(col.property) } > - { col.title || col.property } + { + this.props.allowHtmlHeader ? <div className="content" dangerouslySetInnerHTML={{__html: col.title || col.property}}></div> + : (col.title || col.property ) + } </TableSortLabel> </Tooltip> } </TableCell> ); + + //show column if... + const showColumn = !this.props.hiddenColumns.includes(col.property); + + return showColumn && tableCell; }, this) } </TableRow> </TableHead> 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 426436d44..143b802a4 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx +++ b/sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx @@ -33,6 +33,8 @@ import MenuItem from '@mui/material/MenuItem'; import Menu from '@mui/material/Menu'; import { SvgIconProps } from '@mui/material/SvgIcon'; import { Button } from '@mui/material'; +import { ColumnModel } from './columnModel'; +import ShowColumnsDialog from './showColumnDialog' const styles = (theme: Theme) => createStyles({ root: { @@ -69,18 +71,24 @@ const styles = (theme: Theme) => createStyles({ interface ITableToolbarComponentProps extends WithStyles<typeof styles> { numSelected: number | null; title?: string; - tableId?: string; + tableId: string | null; customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[]; + columns: ColumnModel<{}>[]; + onHideColumns: (columnNames: string[]) => void + onShowColumns: (columnNames: string[]) => void onToggleFilter: () => void; onExportToCsv: () => void; } -class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, { anchorEl: EventTarget & HTMLElement | null }> { +class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, { anchorEl: EventTarget & HTMLElement | null, anchorElDialog: HTMLElement | null }> { + + constructor(props: ITableToolbarComponentProps) { super(props); this.state = { - anchorEl: null + anchorEl: null, + anchorElDialog: null }; } @@ -91,11 +99,22 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, private handleClose = () => { this.setState({ anchorEl: null }); }; + + private showColumnsDialog = (event: React.MouseEvent<HTMLElement>) =>{ + this.setState({ anchorElDialog: this.state.anchorEl }); + } + + private onCloseDialog = () =>{ + this.setState({ anchorElDialog: null }); + + } + render() { const { numSelected, classes } = this.props; const open = !!this.state.anchorEl; - const buttonPrefix = this.props.tableId !== undefined ? this.props.tableId : 'table'; + const buttonPrefix = this.props.tableId !== null ? this.props.tableId : 'table'; return ( + <> <Toolbar className={`${classes.root} ${numSelected && numSelected > 0 ? classes.highlight : ''} `} > <div className={classes.title}> {numSelected && numSelected > 0 ? ( @@ -153,9 +172,18 @@ class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, <Menu id="menu-appbar" anchorEl={this.state.anchorEl} anchorOrigin={{ vertical: 'top', horizontal: 'right' }} transformOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} onClose={this.handleClose} > <MenuItem aria-label="export-table-as-csv" onClick={(e) =>{ this.props.onExportToCsv(); this.handleClose()}}>Export as CSV</MenuItem> + <MenuItem aria-label="hide-show-table-columns" onClick={(e) =>{ this.showColumnsDialog(e); this.handleClose()}}>Hide/show columns</MenuItem> </Menu> </div> </Toolbar> + <ShowColumnsDialog + anchorEl={this.state.anchorElDialog} + onClose={this.onCloseDialog} + settingsName={this.props.tableId} + columns={this.props.columns} + hideColumns={this.props.onHideColumns} + showColumns={this.props.onShowColumns} /> + </> ); } } 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 544e14e01..f9015493f 100644 --- a/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts +++ b/sdnr/wt/odlux/framework/src/components/material-table/utilities.ts @@ -28,6 +28,7 @@ export interface IExternalTableState<TData> { order: 'asc' | 'desc'; orderBy: string | null; selected: any[] | null; + hiddenColumns: string[] rows: (TData & { [RowDisabled]?: boolean })[]; total: number; page: number; @@ -48,7 +49,9 @@ export type ExternalMethodes<TData> = { onFilterChanged: (property: string, filterTerm: string) => void; onHandleChangePage: (page: number) => void; onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void; - }; + onHideColumns: (columnName: string[]) => void; + onShowColumns: (columnName: string[]) => void; + }, createPreActions: (dispatch: Dispatch, skipRefresh?: boolean) => { onPreFilterChanged: (preFilter: { [key: string]: string; @@ -128,6 +131,18 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState } } + class HideColumnsAction extends TableAction{ + constructor(public property: string[]){ + super(); + } + } + + class ShowColumnsAction extends TableAction{ + constructor(public property: string[]){ + super(); + } + } + // #endregion //#region Action Handler @@ -135,6 +150,7 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState order: 'asc', orderBy: null, selected: null, + hiddenColumns:[], rows: [], total: 0, page: 0, @@ -208,6 +224,18 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState rowsPerPage: action.rowsPerPage } } + else if (action instanceof HideColumnsAction){ + + //merge arrays, remove duplicates + const newArray = [...new Set([...state.hiddenColumns, ...action.property])] + state = {...state, hiddenColumns: newArray}; + } + else if(action instanceof ShowColumnsAction){ + + const newArray = state.hiddenColumns.filter(el=> !action.property.includes(el)); + state = {...state, hiddenColumns: newArray}; + } + return state; } @@ -290,6 +318,16 @@ export function createExternal<TData>(callback: DataCallback<TData>, selectState dispatch(new SetRowsPerPageAction(rowsPerPage || 10)); (!skipRefresh) && dispatch(reloadAction); }); + }, + onHideColumns: (columnName: string[]) =>{ + dispatch((dispatch: Dispatch) => { + dispatch(new HideColumnsAction(columnName)); + }) + }, + onShowColumns: (columnName: string[]) =>{ + dispatch((dispatch: Dispatch) => { + dispatch(new ShowColumnsAction(columnName)); + }) } // selected: }; diff --git a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx index 1134e230b..195706d28 100644 --- a/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx +++ b/sdnr/wt/odlux/framework/src/components/navigationMenu.tsx @@ -64,9 +64,9 @@ const styles = (theme: Theme) => createStyles({ duration: theme.transitions.duration.leavingScreen, }), overflowX: 'hidden', - width: theme.spacing(7) + 1, + width: theme.spacing(7), [theme.breakpoints.up('sm')]: { - width: theme.spacing(9) + 1, + width: theme.spacing(9), }, }, drawer: { @@ -101,7 +101,7 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di //collapse menu on mount if necessary React.useEffect(()=>{ - if(isOpen && window.innerWidth < tabletWidthBreakpoint){ + if(isOpen && window.innerWidth <= tabletWidthBreakpoint){ setResponsive(true); dispatch(new MenuAction(false)); @@ -116,14 +116,12 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di 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)); } @@ -145,13 +143,14 @@ export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, di let menuItems = state.framework.applicationRegistraion && Object.keys(state.framework.applicationRegistraion).map(key => { const reg = state.framework.applicationRegistraion[key]; + const icon = !reg.icon ? null :( typeof reg.icon === 'string' ? <img height={22} src={reg.icon} /> : <FontAwesomeIcon icon={reg.icon} /> ) 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} /> + icon={icon} /> ) || null; }) || null; diff --git a/sdnr/wt/odlux/framework/src/components/titleBar.tsx b/sdnr/wt/odlux/framework/src/components/titleBar.tsx index 7872e51da..19d3bdf74 100644 --- a/sdnr/wt/odlux/framework/src/components/titleBar.tsx +++ b/sdnr/wt/odlux/framework/src/components/titleBar.tsx @@ -131,6 +131,10 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE } } + const stateIcon = state.framework.applicationState.icon; + const icon = !stateIcon ? null :( typeof stateIcon === 'string' ? <img className={classes.icon} height={22} src={stateIcon} /> : <FontAwesomeIcon className={classes.icon} icon={stateIcon} /> ) + + return ( <AppBar enableColorOnDark position="absolute" className={classes.appBar}> <Toolbar> @@ -144,9 +148,7 @@ class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLE </IconButton> <Logo /> <Typography variant="h6" color="inherit" > - {state.framework.applicationState.icon - ? (<FontAwesomeIcon className={classes.icon} icon={state.framework.applicationState.icon} />) - : null} + {icon} {state.framework.applicationState.title} </Typography> <div className={classes.grow}></div> diff --git a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts index 16c6ed5d3..d0a07248d 100644 --- a/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts @@ -31,8 +31,8 @@ import { ExternalLoginProvider } from '../models/externalLoginProvider'; import { ApplicationConfig } from '../models/applicationConfig'; import { IConnectAppStoreState } from '../../../apps/connectApp/src/handlers/connectAppRootHandler'; import { IFaultAppStoreState } from '../../../apps/faultApp/src/handlers/faultAppRootHandler'; -import { GeneralSettings } from '../models/settings'; -import { SetGeneralSettingsAction } from '../actions/settingsAction'; +import { GeneralSettings, Settings } from '../models/settings'; +import { LoadSettingsAction, SetGeneralSettingsAction, SetTableSettings, SettingsDoneLoadingAction } from '../actions/settingsAction'; import { startWebsocketSession, suspendWebsocketSession } from '../services/notificationService'; @@ -55,7 +55,7 @@ export interface IApplicationState { authentication: "basic"|"oauth", // basic enablePolicy: boolean, // false transportpceUrl : string, - settings: GeneralSettings + settings: Settings & { isInitialLoadDone: boolean } } const applicationStateInit: IApplicationState = { @@ -69,7 +69,11 @@ const applicationStateInit: IApplicationState = { authentication: "basic", enablePolicy: false, transportpceUrl: "", - settings:{ general: { areNotificationsEnabled: null }} + settings:{ + general: { areNotificationsEnabled: null }, + tables: {}, + isInitialLoadDone: false + } }; export const configureApplication = (config: ApplicationConfig) => { @@ -150,8 +154,23 @@ export const applicationStateHandler: IActionHandler<IApplicationState> = (state state = { ...state, - settings:{general:{areNotificationsEnabled: action.areNoticationsActive}} + settings:{tables: state.settings.tables, isInitialLoadDone:state.settings.isInitialLoadDone, general:{areNotificationsEnabled: action.areNoticationsActive}} } } + else if(action instanceof SetTableSettings){ + + let tableUpdate = state.settings.tables; + tableUpdate[action.tableName] = {columns: action.updatedColumns}; + + state = {...state, settings:{general: state.settings.general, isInitialLoadDone:state.settings.isInitialLoadDone, tables: tableUpdate}} + + }else if(action instanceof LoadSettingsAction){ + + state = {...state, settings:action.settings} + } + else if(action instanceof SettingsDoneLoadingAction){ + state= {...state, settings: {...state.settings, isInitialLoadDone: true}} + } + return state; }; diff --git a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts index 1bcb43528..f98d77487 100644 --- a/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts +++ b/sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts @@ -39,12 +39,8 @@ export const authenticationStateHandler: IActionHandler<IAuthenticationState> = const {user} = action; if (user) { - localStorage.setItem("userToken", user.toString()); - startUserSession(user); onLogin(); } else { - localStorage.removeItem("userToken"); - endUserSession(); onLogout(); } diff --git a/sdnr/wt/odlux/framework/src/index.dev.html b/sdnr/wt/odlux/framework/src/index.dev.html index 4dc353c44..69c5f0607 100644 --- a/sdnr/wt/odlux/framework/src/index.dev.html +++ b/sdnr/wt/odlux/framework/src/index.dev.html @@ -26,7 +26,7 @@ faultApp.register(); // inventoryApp.register(); // helpApp.register(); - app("./app.tsx").configureApplication({ authentication:"oauth", enablePolicy: false, transportpceUrl:"http://test.de"}); + app("./app.tsx").configureApplication({ authentication:"basic", enablePolicy: false, transportpceUrl:"http://test.de"}); app("./app.tsx").runApplication(); }); </script> diff --git a/sdnr/wt/odlux/framework/src/index.html b/sdnr/wt/odlux/framework/src/index.html index 5cd2805c1..7196b5cfe 100644 --- a/sdnr/wt/odlux/framework/src/index.html +++ b/sdnr/wt/odlux/framework/src/index.html @@ -16,7 +16,7 @@ <script> // run the application require(["run"], function (run) { - run.configureApplication({ authentication:"oauth", enablePolicy: true,}); + run.configureApplication({ authentication:"basic", enablePolicy: false,}); run.runApplication(); }); diff --git a/sdnr/wt/odlux/framework/src/models/elasticSearch.ts b/sdnr/wt/odlux/framework/src/models/elasticSearch.ts index 41d29fb0f..fc4383612 100644 --- a/sdnr/wt/odlux/framework/src/models/elasticSearch.ts +++ b/sdnr/wt/odlux/framework/src/models/elasticSearch.ts @@ -31,6 +31,17 @@ export type SingeResult<TSource extends {}> = { } +export type ResultTopology<TSource extends {}> = { + "output": { + pagination?: { + size: number; + page: number; + total: number; + }, + data: TSource[]; + } +} + export type HitEntry<TSource extends {}> = { _index: string; _type: string; diff --git a/sdnr/wt/odlux/framework/src/models/iconDefinition.ts b/sdnr/wt/odlux/framework/src/models/iconDefinition.ts index e93d20ee3..ff50aa73c 100644 --- a/sdnr/wt/odlux/framework/src/models/iconDefinition.ts +++ b/sdnr/wt/odlux/framework/src/models/iconDefinition.ts @@ -18,4 +18,4 @@ import { IconDefinition } from '@fortawesome/free-solid-svg-icons'; -export type IconType = IconDefinition;
\ No newline at end of file +export type IconType = IconDefinition | string;
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/models/settings.ts b/sdnr/wt/odlux/framework/src/models/settings.ts index 6d01a34e5..11ba2f901 100644 --- a/sdnr/wt/odlux/framework/src/models/settings.ts +++ b/sdnr/wt/odlux/framework/src/models/settings.ts @@ -16,12 +16,36 @@ * ============LICENSE_END========================================================================== */ +export type TableSettingsColumn = { + property: string, + displayed: boolean +} + +export type TableSettings = { + tables:{ + [key: string]: { + columns: TableSettingsColumn[] + + //match prop names, hide them + //via property name! -> only those which are hidden! + //all others default false, oh yeah + //or maybe the other way around, gotta think about that + + } + } + + + +} + export type GeneralSettings = { general:{ areNotificationsEnabled: boolean | null } }; +export type Settings= TableSettings & GeneralSettings; + export type SettingsComponentProps = { onClose(): void };
\ No newline at end of file diff --git a/sdnr/wt/odlux/framework/src/services/authenticationService.ts b/sdnr/wt/odlux/framework/src/services/authenticationService.ts index a7691bf6f..39f407e40 100644 --- a/sdnr/wt/odlux/framework/src/services/authenticationService.ts +++ b/sdnr/wt/odlux/framework/src/services/authenticationService.ts @@ -18,7 +18,7 @@ import { AuthPolicy, AuthToken } from "../models/authentication"; import { ExternalLoginProvider } from "../models/externalLoginProvider"; -import { requestRest, formEncode } from "./restService"; +import { requestRest, formEncode, requestRestExt } from "./restService"; type AuthTokenResponse = { access_token: string; @@ -85,6 +85,12 @@ class AuthenticationService { public async getAccessPolicies(){ return await requestRest<AuthPolicy[]>(`oauth/policies`, { method: "GET" }, true); } + + public async getServerReadyState(){ + + const result = await fetch("/ready", {method: "GET"}); + return result.status == (200 || 304) ? true : false; + } } export const authenticationService = new AuthenticationService(); diff --git a/sdnr/wt/odlux/framework/src/services/broadcastService.ts b/sdnr/wt/odlux/framework/src/services/broadcastService.ts index 85ae3e65c..f2c3ebc57 100644 --- a/sdnr/wt/odlux/framework/src/services/broadcastService.ts +++ b/sdnr/wt/odlux/framework/src/services/broadcastService.ts @@ -33,7 +33,7 @@ export type SettingsMessage={key: SettingsType, enableNotifications: boolean, us let channels: Broadcaster[] = []; let store : ApplicationStore | null = null; -export const subscribe = (channel: BroadcastChannel, channelName: string) => { +export const saveChannel = (channel: BroadcastChannel, channelName: string) => { channels.push({channel: channel, key: channelName}); } diff --git a/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts b/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts deleted file mode 100644 index a57739025..000000000 --- a/sdnr/wt/odlux/framework/src/services/forceLogoutService.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * ============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 { UpdateUser } 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; - if (process.env.NODE_ENV === "development") { - console.warn("logout timer not started in development mode"); - } else { - 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 UpdateUser(undefined)); - 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/restService.ts b/sdnr/wt/odlux/framework/src/services/restService.ts index b21e3ec75..d727e4c9e 100644 --- a/sdnr/wt/odlux/framework/src/services/restService.ts +++ b/sdnr/wt/odlux/framework/src/services/restService.ts @@ -19,6 +19,7 @@ import { ApplicationStore } from "../store/applicationStore"; import { ReplaceAction } from "../actions/navigationActions"; +import { AddErrorInfoAction } from "../actions/errorActions"; const baseUri = `${ window.location.origin }`; const absUrlPattern = /^https?:\/\//; @@ -118,12 +119,22 @@ export async function requestRestExt<TData>(path: string = '', init: RequestInit //'Authorization': 'Basic YWRtaW46YWRtaW4=' }); } + 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}`)); + + if(fetchResult.status === 403){ + applicationStore && applicationStore.dispatch(new AddErrorInfoAction({title: "Forbidden", message:"Status: [403], access denied."})); return { ...result, status: 403, + message: "Forbidden." + }; + } + else if (fetchResult.status === 401) { + applicationStore && applicationStore.dispatch(new ReplaceAction(`/login?returnTo=${applicationStore.state.framework.navigationState.pathname}`)); + return { + ...result, + status: 401, message: "Authentication requested by server." }; } diff --git a/sdnr/wt/odlux/framework/src/services/userSessionService.ts b/sdnr/wt/odlux/framework/src/services/userSessionService.ts index 0d5936a7e..8d899c4d6 100644 --- a/sdnr/wt/odlux/framework/src/services/userSessionService.ts +++ b/sdnr/wt/odlux/framework/src/services/userSessionService.ts @@ -24,7 +24,7 @@ import { User } from "../models/authentication"; let currentUser: User | null; let applicationStore: ApplicationStore | null = null; -let timer : NodeJS.Timeout | null = null; +let timer : null | ReturnType<typeof setTimeout> = null; export const startUserSessionService = (store: ApplicationStore) =>{ applicationStore=store; diff --git a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts index 6f4c71ec7..e1d37522d 100644 --- a/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts +++ b/sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts @@ -16,7 +16,7 @@ * ============LICENSE_END========================================================================== */ -import { Result } from '../models'; +import { Result, ResultTopology } from '../models'; import { DataCallback } from '../components/material-table'; import { requestRest } from '../services/restService'; @@ -31,43 +31,79 @@ type dataType = { [prop: string]: propType }; * @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)> { +export function createSearchDataHandler<TResult>(typeName: (() => string) | string, connectToTopologyServer?: boolean, additionalFilters?: {} | null | undefined): DataCallback<(TResult)> { const fetchData: DataCallback<(TResult)> = async (pageIndex, rowsPerPage, orderBy, order, filter) => { - const url = `/rests/operations/data-provider:read-${typeof typeName === "function" ? typeName(): typeName}-list`; + + const topologyUrl = `/topology/network/read-${typeof typeName === "function" ? typeName() : typeName}-list`; + const dataProviderUrl = `/rests/operations/data-provider:read-${typeof typeName === "function" ? typeName() : typeName}-list`; + + const url = connectToTopologyServer ? topologyUrl : dataProviderUrl; filter = { ...filter, ...additionalFilters }; const filterKeys = filter && Object.keys(filter) || []; - const query = { - "data-provider: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>>(url, { - method: "POST", // *GET, POST, PUT, DELETE, etc. - 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", - // "Content-Type": "application/x-www-form-urlencoded", - }, - body: JSON.stringify(convertPropertyValues(query, replaceUpperCase)), // body data type must match "Content-Type" header - }); - - if (result) { - let rows: TResult[] = []; - - if (result && result["data-provider:output"] && result["data-provider:output"].data) { - rows = result["data-provider:output"].data.map(obj => convertPropertyNames(obj, replaceHyphen)) || [] + const 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 } + } + + if (url.includes('data-provider')) { + const query = { + "data-provider:input": input + }; + + const result = await requestRest<Result<TResult>>(url, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + 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", + // "Content-Type": "application/x-www-form-urlencoded", + }, + body: JSON.stringify(convertPropertyValues(query, replaceUpperCase)), // body data type must match "Content-Type" header + }); + if (result) { + let rows: TResult[] = []; + + if (result && result["data-provider:output"] && result["data-provider:output"].data) { + rows = result["data-provider:output"].data.map(obj => convertPropertyNames(obj, replaceHyphen)) || [] + } + + const data = { + page: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.page != null && result["data-provider:output"].pagination.page - 1 || 0), total: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.total || 0), rows: rows + }; + return data; } + } else if (url.includes('topology')) { - const data = { - page: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.page != null && result["data-provider:output"].pagination.page - 1 || 0) , total: +(result["data-provider:output"].pagination && result["data-provider:output"].pagination.total || 0), rows: rows + const queryTopology = { + "input": input }; - return data; + + const resultTopology = await requestRest<ResultTopology<TResult>>(url, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + 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", + // "Content-Type": "application/x-www-form-urlencoded", + }, + body: JSON.stringify(queryTopology), // body data type must match "Content-Type" header + }); + if (resultTopology) { + let rows: TResult[] = []; + + if (resultTopology && resultTopology.output && resultTopology.output.data) { + rows = resultTopology.output.data.map(obj => obj) || [] + } + + const data = { + page: +(resultTopology.output.pagination && resultTopology.output.pagination.page != null && resultTopology.output.pagination.page - 1 || 0), total: +(resultTopology.output.pagination && resultTopology.output.pagination.total || 0), rows: rows + }; + return data; + } } return { page: 1, total: 0, rows: [] }; diff --git a/sdnr/wt/odlux/framework/src/views/about.tsx b/sdnr/wt/odlux/framework/src/views/about.tsx index 1b6135e5f..ac219708d 100644 --- a/sdnr/wt/odlux/framework/src/views/about.tsx +++ b/sdnr/wt/odlux/framework/src/views/about.tsx @@ -38,10 +38,8 @@ type odluxVersion= {version:string,build:string, framework: string, faultApp: string, helpApp: string, inventoryApp: string, - linkCalculationApp: string, maintenanceApp: string, mediatorApp: string, - networkMapApp: string, permanceHistoryApp: string }}; @@ -74,8 +72,6 @@ class AboutComponent extends React.Component<any, AboutState> { `| InventoryApp | ${data.applications.inventoryApp}|\n `+ `| EventLogApp | ${data.applications.eventLogApp}|\n `+ `| MediatorApp | ${data.applications.mediatorApp}|\n `+ - `| NetworkMapApp | ${data.applications.networkMapApp}|\n `+ - `| LinkCalculatorApp | ${data.applications.linkCalculationApp}|\n `+ `| HelpApp | ${data.applications.helpApp}|\n `; } @@ -167,7 +163,7 @@ class AboutComponent extends React.Component<any, AboutState> { <div style={containerStyle}> { this.state.isContentLoadedSucessfully && <div style={{float: "right", marginRight: "10px"}}> - <Button color="inherit" variant="contained" onClick={e => this.copyToClipboard(e)}> + <Button aria-label="copy-version-information-button" color="inherit" variant="contained" onClick={e => this.copyToClipboard(e)}> Copy to clipboard </Button> { diff --git a/sdnr/wt/odlux/framework/src/views/frame.tsx b/sdnr/wt/odlux/framework/src/views/frame.tsx index 278fbe1db..4676f5ac2 100644 --- a/sdnr/wt/odlux/framework/src/views/frame.tsx +++ b/sdnr/wt/odlux/framework/src/views/frame.tsx @@ -22,7 +22,8 @@ import { Theme } from '@mui/material/styles'; import { WithStyles } from '@mui/styles'; import withStyles from '@mui/styles/withStyles'; import createStyles from '@mui/styles/createStyles'; -import { faHome, faAddressBook, faSignInAlt, faCog } from '@fortawesome/free-solid-svg-icons'; +import { faHome, faAddressBook, faSignInAlt, faCog } from '@fortawesome/free-solid-svg-icons' + import { SnackbarProvider } from 'notistack'; import { ConfirmProvider } from 'material-ui-confirm'; diff --git a/sdnr/wt/odlux/framework/src/views/login.tsx b/sdnr/wt/odlux/framework/src/views/login.tsx index 8eb7a6c0f..e037edf82 100644 --- a/sdnr/wt/odlux/framework/src/views/login.tsx +++ b/sdnr/wt/odlux/framework/src/views/login.tsx @@ -119,6 +119,7 @@ interface ILoginState { password: string; scope: string; message: string; + isServerReady: boolean; providers: { id: string; title: string; @@ -141,6 +142,7 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { scope: 'sdn', message: '', providers: null, + isServerReady: false }; } @@ -148,6 +150,13 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { if (this.props.authentication === "oauth" && (this.props.externalLoginProviders == null || this.props.externalLoginProviders.length === 0)){ this.props.updateExternalProviders(); } + + authenticationService.getServerReadyState().then(result =>{ + this.setState({isServerReady: result}); + }) + + + } private setExternalProviderAnchor = (el: HTMLElement | null) => { @@ -262,10 +271,21 @@ class LoginComponent extends React.Component<LoginProps, ILoginState> { this.props.history.replace(returnTo && returnTo[1] || "/"); } else { - this.setState({ - message: "Could not log in. Please check your credentials or ask your administrator for assistence.", - password: "" - }) + + if(!this.state.isServerReady){ + const ready = await authenticationService.getServerReadyState(); + if(ready){ + this.setState({isServerReady: true}); + }else{ + this.setState({message: "Login is currently not possible. Please re-try in a few minutes. If the problem persits, ask your administrator for assistence."}); + } + + }else{ + this.setState({ + message: "Could not log in. Please check your credentials or ask your administrator for assistence.", + password: "" + }) + } } } } diff --git a/sdnr/wt/odlux/framework/src/views/test.tsx b/sdnr/wt/odlux/framework/src/views/test.tsx index 84c4094c3..72f8d2cc8 100644 --- a/sdnr/wt/odlux/framework/src/views/test.tsx +++ b/sdnr/wt/odlux/framework/src/views/test.tsx @@ -848,7 +848,7 @@ const TestComponent = (props: WithComponents<typeof components> & WithStyles<typ <Typography className={props.classes.heading}>Client Side Table Demo</Typography> </AccordionSummary> <AccordionDetails> - <SampleDataMaterialTable rows={tableData} columns={ + <SampleDataMaterialTable rows={tableData} tableId={null} columns={ [ { property: "index", type: ColumnType.text, title: "Index", width: "80px", disableFilter: true, disableSorting: true }, { property: "firstName", type: ColumnType.text, title: "First Name" }, |