ConfigApp bugfixes

fix remove button in lists and boolean filters not working, fix delay-period not configurable

Issue-ID: CCSDK-2878
Signed-off-by: Aijana Schumann <aijana.schumann@highstreet-technologies.com>
Change-Id: I49f03840de6092f9ac4641b7c98785b5feb760b4
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
index 1db66c0..790d251 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/actions/deviceActions.ts
@@ -49,6 +49,9 @@
 
 export const updateNodeIdAsyncActionCreator = (nodeId: string) => async (dispatch: Dispatch, getState: () => IApplicationStoreState ) => {
 
+  dispatch(new UpdateDeviceDescription("", {}, []));
+  dispatch(new SetCollectingSelectionData(true));
+  
   const availableCapabilities = await restService.getCapabilitiesByMoutId(nodeId);
 
   if (!availableCapabilities || availableCapabilities.length <= 0) {
@@ -70,7 +73,7 @@
 
   parser.postProcess();
 
-  console.log(parser.modules, parser.views)
+  dispatch(new SetCollectingSelectionData(false));
 
   return dispatch(new UpdateDeviceDescription(nodeId, parser.modules, parser.views));
 }
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
index 8f7300c..73812a4 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/baseProps.ts
@@ -18,4 +18,10 @@
 
 import { ViewElement } from "../models/uiModels";
 
-export type BaseProps<TValue = string> = { value: ViewElement, inputValue: TValue, readOnly: boolean, disabled: boolean, onChange(newValue: TValue): void };
\ No newline at end of file
+export type BaseProps<TValue = string> = { 
+    value: ViewElement, 
+    inputValue: TValue, 
+    readOnly: boolean, 
+    disabled: boolean, 
+    onChange(newValue: TValue): void 
+};
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx
new file mode 100644
index 0000000..c705b98
--- /dev/null
+++ b/sdnr/wt/odlux/apps/configurationApp/src/components/uiElementLeafList.tsx
@@ -0,0 +1,204 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from "react"
+import { FormControl, InputLabel, Paper, Chip, FormHelperText, Dialog, DialogTitle, DialogContentText, DialogActions, Button, DialogContent } from "@material-ui/core";
+import { makeStyles } from '@material-ui/core/styles';
+import AddIcon from '@material-ui/icons/Add';
+
+import { ViewElement } from "../models/uiModels";
+
+import { BaseProps } from "./baseProps";
+
+type LeafListProps = BaseProps<any []> & {
+  getEditorForViewElement:  (uiElement: ViewElement) => (null | React.ComponentType<BaseProps<any>>)  
+};
+
+const useStyles = makeStyles((theme) => {
+  const light = theme.palette.type === 'light';
+  const bottomLineColor = light ? 'rgba(0, 0, 0, 0.42)' : 'rgba(255, 255, 255, 0.7)';
+
+  return ({
+    root: {
+      display: 'flex',
+      justifyContent: 'left',
+      verticalAlign: 'bottom',
+      flexWrap: 'wrap',
+      listStyle: 'none',
+      margin: 0,
+      padding: 0,
+      paddingTop: theme.spacing(0.5),
+      marginTop: theme.spacing(1),
+    },
+    chip: {
+      margin: theme.spacing(0.5),
+    },
+    underline: {
+        '&:after': {
+          borderBottom: `2px solid ${theme.palette.primary.main}`,
+          left: 0,
+          bottom: 0,
+          // Doing the other way around crash on IE 11 "''" https://github.com/cssinjs/jss/issues/242
+          content: '""',
+          position: 'absolute',
+          right: 0,
+          transform: 'scaleX(0)',
+          transition: theme.transitions.create('transform', {
+            duration: theme.transitions.duration.shorter,
+            easing: theme.transitions.easing.easeOut,
+          }),
+          pointerEvents: 'none', // Transparent to the hover style.
+        },
+        '&$focused:after': {
+          transform: 'scaleX(1)',
+        },
+        '&$error:after': {
+          borderBottomColor: theme.palette.error.main,
+          transform: 'scaleX(1)', // error is always underlined in red
+        },
+        '&:before': {
+          borderBottom: `1px solid ${bottomLineColor}`,
+          left: 0,
+          bottom: 0,
+          // Doing the other way around crash on IE 11 "''" https://github.com/cssinjs/jss/issues/242
+          content: '"\\00a0"',
+          position: 'absolute',
+          right: 0,
+          transition: theme.transitions.create('border-bottom-color', {
+            duration: theme.transitions.duration.shorter,
+          }),
+          pointerEvents: 'none', // Transparent to the hover style.
+        },
+        '&:hover:not($disabled):before': {
+          borderBottom: `2px solid ${theme.palette.text.primary}`,
+          // Reset on touch devices, it doesn't add specificity
+          '@media (hover: none)': {
+            borderBottom: `1px solid ${bottomLineColor}`,
+          },
+        },
+        '&$disabled:before': {
+          borderBottomStyle: 'dotted',
+        },
+      },
+  })
+});
+
+export const UiElementLeafList = (props: LeafListProps) => {
+  const { value: element, inputValue, onChange } = props;
+
+  const classes = useStyles();
+
+  const [open, setOpen] = React.useState(false);
+  const [editorValue, setEditorValue] = React.useState("");
+  const [editorValueIndex, setEditorValueIndex] = React.useState(-1);
+  
+
+  const handleClickOpen = () => {
+    setOpen(true);
+  };
+
+  const handleClose = () => {
+    setOpen(false);
+  };
+
+  const onApplyButton = () => { 
+     if (editorValue != null && editorValue != "" && editorValueIndex < 0) {
+       props.onChange([
+         ...inputValue,
+         editorValue,
+       ]);
+     } else if (editorValue != null && editorValue != "") {
+       props.onChange([
+         ...inputValue.slice(0, editorValueIndex),
+         editorValue,
+         ...inputValue.slice(editorValueIndex+1),
+       ]);
+     }
+     setOpen(false);
+  };
+
+  const onDelete = (index : number) => {
+    const newValue : any[] = [
+      ...inputValue.slice(0, index),
+      ...inputValue.slice(index+1),
+    ];
+    onChange(newValue);
+  };
+
+  const ValueEditor = props.getEditorForViewElement(props.value); 
+
+  return (
+    <>
+      <FormControl style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+        <InputLabel htmlFor={`list-${element.id}`} shrink={!props.readOnly || !!(inputValue && inputValue.length)} >{element.label}</InputLabel>
+        <ul className={`${classes.root} ${classes.underline}`} id={`list-${element.id}`}>
+        { !props.readOnly ? <li>
+          <Chip
+            icon={<AddIcon />}
+            label={"Add"}
+            className={classes.chip}
+            size="small"
+            color="secondary"
+            onClick={ () => { 
+              setOpen(true); 
+              setEditorValue("");
+              setEditorValueIndex(-1);
+             } 
+            }
+          />
+        </li> : null }  
+        { inputValue.map((val, ind) => (
+          <li key={ind}>
+            <Chip
+              className={classes.chip}
+              size="small"
+              variant="outlined"
+              label={String(val)}
+              onDelete={ !props.readOnly ? () => { onDelete(ind); } : undefined }  
+              onClick={ !props.readOnly ? () => { 
+                  setOpen(true); 
+                  setEditorValue(val);
+                  setEditorValueIndex(ind);
+                  } : undefined
+              }   
+            />
+            </li>
+          ))
+        }
+        </ul>
+        {/* <FormHelperText>{ "Value is mandetory"}</FormHelperText> */}
+        </FormControl>
+        <Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
+          <DialogTitle id="form-dialog-title">{editorValueIndex < 0 ? "Add new value" : "Edit value" } </DialogTitle>
+          <DialogContent>
+            { ValueEditor && <ValueEditor 
+                inputValue={ editorValue }
+                value={{ ...element, isList: false}}
+                disabled={false}
+                readOnly={props.readOnly}
+                onChange={ setEditorValue }
+            /> || null }
+          </DialogContent>
+          <DialogActions>
+            <Button onClick={ handleClose }> Cancel </Button>
+            <Button disabled={editorValue == null || editorValue === "" } onClick={ onApplyButton } color="secondary"> {editorValueIndex < 0 ? "Add" : "Apply"} </Button>
+          </DialogActions>
+        </Dialog>
+      </>
+  );
+};
\ No newline at end of file
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index 6465feb..fc3c68e 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -45,15 +45,19 @@
 import Select from "@material-ui/core/Select";
 import MenuItem from "@material-ui/core/MenuItem";
 import Breadcrumbs from "@material-ui/core/Breadcrumbs";
+import { Button } from '@material-ui/core';
 import Link from "@material-ui/core/Link";
 
+import { BaseProps } from '../components/baseProps';
 import { UIElementReference } from '../components/uiElementReference';
 import { UiElementNumber } from '../components/uiElementNumber';
 import { UiElementString } from '../components/uiElementString';
 import { UiElementBoolean } from '../components/uiElementBoolean';
 import { UiElementSelection } from '../components/uiElementSelection';
 import { UIElementUnion } from '../components/uiElementUnion';
-import { Button } from '@material-ui/core';
+import { UiElementLeafList } from '../components/uiElementLeafList';
+
+import { useConfirm } from 'material-ui-confirm';
 
 const styles = (theme: Theme) => createStyles({
   header: {
@@ -281,68 +285,130 @@
     }, {} as { [key: string]: any });
   }
 
+  private getEditorForViewElement = (uiElement: ViewElement) : (null | React.ComponentType<BaseProps<any>>) => { 
+  if (isViewElementEmpty(uiElement)) {
+     return null;
+  } else if (isViewElementSelection(uiElement)) {
+     return UiElementSelection;
+  } else if (isViewElementBoolean(uiElement)) {
+    return UiElementBoolean;
+  } else if (isViewElementString(uiElement)) {
+      return UiElementString;
+  } else if (isViewElementNumber(uiElement)) {
+     return UiElementNumber;
+  } else if (isViewElementUnion(uiElement)) {
+     return UIElementUnion;
+  } else {
+     if (process.env.NODE_ENV !== "production") {
+       console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+     }
+    return null;
+    }
+  }
+
   private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
     const isKey = (uiElement.label === keyProperty);
     const canEdit = editMode && (isNew || (uiElement.config && !isKey));
-    
-    // do not show elements w/o any value from the backend
+
+     // do not show elements w/o any value from the backend
     if (viewData[uiElement.id] == null && !editMode) {
       return null;
-    } else  if (isViewElementEmpty(uiElement)) {
+    } else if (isViewElementEmpty(uiElement)) {
       return null;  
-    } else if (isViewElementSelection(uiElement)) {
-
-      return <UiElementSelection
-        key={uiElement.id}
-        inputValue={viewData[uiElement.id] || ''}
-        value={uiElement}
-        readOnly={!canEdit}
-        disabled={editMode && !canEdit}
-        onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
-      />
-
-    } else if (isViewElementBoolean(uiElement)) {
-      return <UiElementBoolean
-        key={uiElement.id}
-        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
-        value={uiElement}
-        readOnly={!canEdit}
-        disabled={editMode && !canEdit}
-        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
-
-    } else if (isViewElementString(uiElement)) {
-      return <UiElementString
-        key={uiElement.id}
-        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
-        value={uiElement}
-        isKey={isKey}
-        readOnly={!canEdit}
-        disabled={editMode && !canEdit}
-        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
-
-    } else if (isViewElementNumber(uiElement)) {
-      return <UiElementNumber
-        key={uiElement.id}
-        value={uiElement}
-        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
-        readOnly={!canEdit}
-        disabled={editMode && !canEdit}
-        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
-    } else if (isViewElementUnion(uiElement)) {
-      return <UIElementUnion
-        key={uiElement.id}
-        isKey={false}
-        value={uiElement}
-        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
-        readOnly={!canEdit}
-        disabled={editMode && !canEdit}
-        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+    } else if (uiElement.isList) {
+      /* element is a leaf-list */
+      return <UiElementLeafList
+         key={uiElement.id}
+         inputValue={viewData[uiElement.id] == null ? [] : viewData[uiElement.id]} 
+         value={uiElement}
+         readOnly={!canEdit}
+         disabled={editMode && !canEdit}
+         onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+         getEditorForViewElement = { this.getEditorForViewElement }
+      />;  
     } else {
-      if (process.env.NODE_ENV !== "production") {
-        console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
-      }
-      return null;
+        const Element = this.getEditorForViewElement(uiElement);
+        return Element != null   
+          ? (
+            <Element
+                key={uiElement.id}
+                inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+                value={uiElement}
+                readOnly={!canEdit}
+                disabled={editMode && !canEdit}
+                onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+            /> )
+          : null ;
     }
+    
+    // // do not show elements w/o any value from the backend
+    // if (viewData[uiElement.id] == null && !editMode) {
+    //   return null;
+    // } else if (isViewElementEmpty(uiElement)) {
+    //   return null;  
+    // } else if (uiElement.isList) {
+    //   /* element is a leaf-list */
+    //   return <UiElementLeafList
+    //      key={uiElement.id}
+    //     inputValue={viewData[uiElement.id] || ''}
+    //     value={uiElement}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+    //   />;  
+    // } else if (isViewElementSelection(uiElement)) {
+
+    //   return <UiElementSelection
+    //     key={uiElement.id}
+    //     inputValue={viewData[uiElement.id] || ''}
+    //     value={uiElement}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+    //   />
+
+    // } else if (isViewElementBoolean(uiElement)) {
+    //   return <UiElementBoolean
+    //     key={uiElement.id}
+    //     inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+    //     value={uiElement}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+    // } else if (isViewElementString(uiElement)) {
+    //   return <UiElementString
+    //     key={uiElement.id}
+    //     inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+    //     value={uiElement}
+    //     isKey={isKey}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+    // } else if (isViewElementNumber(uiElement)) {
+    //   return <UiElementNumber
+    //     key={uiElement.id}
+    //     value={uiElement}
+    //     inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+    // } else if (isViewElementUnion(uiElement)) {
+    //   return <UIElementUnion
+    //     key={uiElement.id}
+    //     isKey={false}
+    //     value={uiElement}
+    //     inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+    //     readOnly={!canEdit}
+    //     disabled={editMode && !canEdit}
+    //     onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+    // } else {
+    //   if (process.env.NODE_ENV !== "production") {
+    //     console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+    //   }
+    //   return null;
+    // }
   };
 
   // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
@@ -399,7 +465,7 @@
                 Object.keys(uiElement.cases).map(caseKey => {
                   const caseElm = uiElement.cases[caseKey];
                   return (
-                    <MenuItem key={caseElm.id} title={caseElm.description} value={caseKey}>{caseElm.label}</MenuItem>
+                    <MenuItem key={caseElm.id} value={caseKey}><Tooltip title={caseElm.description}><div style={{width:"100%"}}>{caseElm.label}</div></Tooltip></MenuItem>
                   );
                 })
               }
@@ -508,31 +574,41 @@
 
     const { classes, removeElement } = this.props;
 
+    const DeleteIconWithConfirmation : React.FC<{rowData: {[key:string]:any}, onReload: () => void} > = (props) => {
+        const confirm = useConfirm();
+
+        return (
+              <Tooltip title={"Remove"} >
+                <IconButton className={classes.button} onClick={async (e) => {
+                  e.stopPropagation();
+                  e.preventDefault();
+                  confirm({title: "Do you really want to delete this element ?", description: "This action is permanent!", confirmationButtonProps: { color: "secondary" }})
+                    .then(() => removeElement(`${this.props.vPath}[${props.rowData[listKeyProperty]}]`))
+                    .then( props.onReload );
+                }} >
+                  <RemoveIcon />
+                </IconButton>
+              </Tooltip>
+        );
+    }
+
     return (
       <SelectElementTable stickyHeader idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
         Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
           const elm = listElements[cur];
           if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
             if (elm.label !== listKeyProperty) {
-              acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+              acc.push(elm.uiType === "boolean" ? { property: elm.label, type: ColumnType.boolean } : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
             } else {
-              acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+              acc.unshift(elm.uiType === "boolean" ? { property: elm.label, type: ColumnType.boolean } : { property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
             }
           }
           return acc;
         }, []).concat([{
           property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: ( ({ rowData })=> {
             return (
-              <Tooltip title={"Remove"} >
-                <IconButton className={classes.button} onClick={(e) => {
-                  e.stopPropagation();
-                  e.preventDefault();
-                  removeElement(`${this.props.vPath}[${rowData[listKeyProperty]}]`)
-                }} >
-                  <RemoveIcon />
-                </IconButton>
-              </Tooltip>
-            )
+              <DeleteIconWithConfirmation rowData={rowData} onReload={() => this.props.vPath && this.props.reloadView(this.props.vPath) } />
+            );
           })
         }])
       } onHandleClick={(ev, row) => {
@@ -725,7 +801,7 @@
       <div className={this.props.classes.outer}>
         <div className={this.props.classes.inner}>
           <Loader />
-          <h3>Collecting Data ...</h3>
+          <h3>Processing ...</h3>
         </div>
       </div>
     );
diff --git a/sdnr/wt/odlux/framework/src/services/restService.ts b/sdnr/wt/odlux/framework/src/services/restService.ts
index 19ef34f..f05c7b8 100644
--- a/sdnr/wt/odlux/framework/src/services/restService.ts
+++ b/sdnr/wt/odlux/framework/src/services/restService.ts
@@ -85,7 +85,7 @@
     };
   }
   const contentType = fetchResult.headers.get("Content-Type") || fetchResult.headers.get("content-type");
-  const isJson = contentType && contentType.toLowerCase().startsWith("application/json");
+  const isJson = contentType && (contentType.toLowerCase().startsWith("application/json") || contentType.toLowerCase().startsWith("application/yang-data+json"));
   try {
     const data = (isJson ? await fetchResult.json() : await fetchResult.text()) as TData;
     return {
diff --git a/sdnr/wt/odlux/framework/src/views/frame.tsx b/sdnr/wt/odlux/framework/src/views/frame.tsx
index 5218975..c8e24fd 100644
--- a/sdnr/wt/odlux/framework/src/views/frame.tsx
+++ b/sdnr/wt/odlux/framework/src/views/frame.tsx
@@ -21,6 +21,9 @@
 import { withStyles, WithStyles, createStyles, Theme } from '@material-ui/core/styles';

 import { faHome, faAddressBook, faSignInAlt } from '@fortawesome/free-solid-svg-icons';

 

+import { SnackbarProvider } from 'notistack';

+import { ConfirmProvider } from 'material-ui-confirm';

+

 import AppFrame from '../components/routing/appFrame';

 import TitleBar from '../components/titleBar';

 import Menu from '../components/navigationMenu';

@@ -33,7 +36,7 @@
 import Test from '../views/test';

 

 import applicationService from '../services/applicationManager';

-import { SnackbarProvider } from 'notistack';

+

 

 const styles = (theme: Theme) => createStyles({

   root: {

@@ -63,52 +66,54 @@
     const registrations = applicationService.applications;

     const { classes } = this.props;

     return (

-      <SnackbarProvider maxSnack={3}>

-        <Router>

-          <div className={classes.root}>

-            <SnackDisplay />

-            <ErrorDisplay />

-            <TitleBar />

-            <Menu />

-            <main className={classes.content}>

-              {

-                <div className={classes.toolbar} /> //needed for margins, don't remove!

-              }

-              <Switch>

-                <Route exact path="/" component={() => (

-                  <AppFrame title={"Home"} icon={faHome} >

-                    <Home />

-                  </AppFrame>

-                )} />

-                <Route path="/about" component={() => (

-                  <AppFrame title={"About"} icon={faAddressBook} >

-                    <About />

-                  </AppFrame>

-                )} />

-                {process.env.NODE_ENV === "development" ? <Route path="/test" component={() => (

-                  <AppFrame title={"Test"} icon={faAddressBook} >

-                    <Test />

-                  </AppFrame>

-                )} /> : null}

-                <Route path="/login" component={() => (

-                  <AppFrame title={"Login"} icon={faSignInAlt} >

-                    <Login />

-                  </AppFrame>

-                )} />

-                {Object.keys(registrations).map(p => {

-                  const application = registrations[p];

-                  return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => (

-                    <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} >

-                      <application.rootComponent />

+      <ConfirmProvider>

+        <SnackbarProvider maxSnack={3}>

+            <Router>

+            <div className={classes.root}>

+                <SnackDisplay />

+                <ErrorDisplay />

+                <TitleBar />

+                <Menu />

+                <main className={classes.content}>

+                {

+                    <div className={classes.toolbar} /> //needed for margins, don't remove!

+                }

+                <Switch>

+                    <Route exact path="/" component={() => (

+                    <AppFrame title={"Home"} icon={faHome} >

+                        <Home />

                     </AppFrame>

-                  )} />)

-                })}

-                <Redirect to="/" />

-              </Switch>

-            </main>

-          </div>

-        </Router>

-      </SnackbarProvider>

+                    )} />

+                    <Route path="/about" component={() => (

+                    <AppFrame title={"About"} icon={faAddressBook} >

+                        <About />

+                    </AppFrame>

+                    )} />

+                    {process.env.NODE_ENV === "development" ? <Route path="/test" component={() => (

+                    <AppFrame title={"Test"} icon={faAddressBook} >

+                        <Test />

+                    </AppFrame>

+                    )} /> : null}

+                    <Route path="/login" component={() => (

+                    <AppFrame title={"Login"} icon={faSignInAlt} >

+                        <Login />

+                    </AppFrame>

+                    )} />

+                    {Object.keys(registrations).map(p => {

+                    const application = registrations[p];

+                    return (<Route key={application.name} path={application.path || `/${application.name}`} component={() => (

+                        <AppFrame title={application.title || (typeof application.menuEntry === 'string' && application.menuEntry) || application.name} icon={application.icon} appId={application.name} >

+                        <application.rootComponent />

+                        </AppFrame>

+                    )} />)

+                    })}

+                    <Redirect to="/" />

+                </Switch>

+                </main>

+            </div>

+            </Router>

+        </SnackbarProvider>

+      </ConfirmProvider>  

     );

   }

 }

diff --git a/sdnr/wt/odlux/package.json b/sdnr/wt/odlux/package.json
index 9053e08..4eaf5fc 100644
--- a/sdnr/wt/odlux/package.json
+++ b/sdnr/wt/odlux/package.json
@@ -32,6 +32,7 @@
     "jsonwebtoken": "8.3.0",
     "jss": "10.0.3",
     "lerna": "3.13.1",
+    "material-ui-confirm": "2.1.1",
     "notistack": "0.9.6",
     "prop-types": "15.7.2",
     "react": "16.12.0",
diff --git a/sdnr/wt/odlux/yarn.lock b/sdnr/wt/odlux/yarn.lock
index e71ad3f..9f1065b 100644
--- a/sdnr/wt/odlux/yarn.lock
+++ b/sdnr/wt/odlux/yarn.lock
@@ -7274,6 +7274,11 @@
   resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.0.tgz#a18d01cfdcf8d15c3c455b71c8329e5e0f01faa1"
   integrity sha512-HduzIW2xApSXKXJSpCipSxKyvMbwRRa/TwMbepmlZziKdH8548WSoDP4SxzulEKjlo8BE39l+2fwJZuRKOln6g==
 
+material-ui-confirm@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/material-ui-confirm/-/material-ui-confirm-2.1.1.tgz#dbc3ff66502a183966a3f0a2ebb2ff8ba22a148d"
+  integrity sha512-d671LgozdJP54buZTv+Eemo0ySYTCXF3QqfYKO7axoG/8g659G5+aD7PovYupsfSSOXOzyzpYRhjbIhE4yrPHA==
+
 math-random@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c"