aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/framework/src
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/framework/src')
-rw-r--r--sdnr/wt/odlux/framework/src/actions/authentication.ts38
-rw-r--r--sdnr/wt/odlux/framework/src/actions/settingsAction.ts86
-rw-r--r--sdnr/wt/odlux/framework/src/assets/images/home.svg20
-rw-r--r--sdnr/wt/odlux/framework/src/assets/images/home.svg.d.ts20
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/columnModel.ts1
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/index.tsx51
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/showColumnDialog.tsx188
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableFilter.tsx7
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableHead.tsx14
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/tableToolbar.tsx36
-rw-r--r--sdnr/wt/odlux/framework/src/components/material-table/utilities.ts40
-rw-r--r--sdnr/wt/odlux/framework/src/components/navigationMenu.tsx11
-rw-r--r--sdnr/wt/odlux/framework/src/components/titleBar.tsx8
-rw-r--r--sdnr/wt/odlux/framework/src/handlers/applicationStateHandler.ts29
-rw-r--r--sdnr/wt/odlux/framework/src/handlers/authenticationHandler.ts4
-rw-r--r--sdnr/wt/odlux/framework/src/index.dev.html2
-rw-r--r--sdnr/wt/odlux/framework/src/index.html2
-rw-r--r--sdnr/wt/odlux/framework/src/models/elasticSearch.ts11
-rw-r--r--sdnr/wt/odlux/framework/src/models/iconDefinition.ts2
-rw-r--r--sdnr/wt/odlux/framework/src/models/settings.ts24
-rw-r--r--sdnr/wt/odlux/framework/src/services/authenticationService.ts8
-rw-r--r--sdnr/wt/odlux/framework/src/services/broadcastService.ts2
-rw-r--r--sdnr/wt/odlux/framework/src/services/forceLogoutService.ts57
-rw-r--r--sdnr/wt/odlux/framework/src/services/restService.ts15
-rw-r--r--sdnr/wt/odlux/framework/src/services/userSessionService.ts2
-rw-r--r--sdnr/wt/odlux/framework/src/utilities/elasticSearch.ts94
-rw-r--r--sdnr/wt/odlux/framework/src/views/about.tsx6
-rw-r--r--sdnr/wt/odlux/framework/src/views/frame.tsx3
-rw-r--r--sdnr/wt/odlux/framework/src/views/login.tsx28
-rw-r--r--sdnr/wt/odlux/framework/src/views/test.tsx2
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" },