diff --git a/package-lock.json b/package-lock.json
index 96c996405754fb834e6f3fd62590c3de693e0992..f4f72430163cf4f2afb3f02a08a46530b61a3457 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "nrp-frontend",
-  "version": "0.1.0",
+  "version": "4.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -10048,6 +10048,12 @@
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
           "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
           "dev": true
+        },
+        "uuid": {
+          "version": "8.3.2",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+          "dev": true
         }
       }
     },
@@ -12005,6 +12011,12 @@
         "which": "^2.0.2"
       },
       "dependencies": {
+        "uuid": {
+          "version": "8.3.2",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+          "optional": true
+        },
         "which": {
           "version": "2.0.2",
           "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -15972,6 +15984,13 @@
         "faye-websocket": "^0.11.3",
         "uuid": "^8.3.2",
         "websocket-driver": "^0.7.4"
+      },
+      "dependencies": {
+        "uuid": {
+          "version": "8.3.2",
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+          "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+        }
       }
     },
     "sockjs-client": {
@@ -17231,9 +17250,9 @@
       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
     },
     "uuid": {
-      "version": "8.3.2",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
-      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
     },
     "v8-compile-cache": {
       "version": "2.3.0",
diff --git a/package.json b/package.json
index b8ed19bbae254e10428b48e902ab98b5f2b7f564..f8202c9354f2fa522358b59c6c6f857f5e98dbdc 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
       "src/services/experiments/execution/running-simulation-service.js",
       "src/services/roslib-service.js",
       "src/services/experiments/files/import-experiment-service.js",
-      "src/services/experiments/files/remote-experiment-files-service.js"
+      "src/services/experiments/files/remote-experiment-files-service.js",
+      "src/services/nrp-analytics-service.js"
     ]
   },
   "version": "4.0.0",
@@ -44,6 +45,7 @@
     "react-tabs": "3.1.2",
     "roslib": "1.1.0",
     "rxjs": "6.6.3",
+    "uuid": "9.0.0",
     "web-vitals": "^0.2.4"
   },
   "devDependencies": {
diff --git a/src/components/constants.js b/src/components/constants.js
new file mode 100644
index 0000000000000000000000000000000000000000..d73d9f7ab9cb121d7dcd55cb7f9c46d9f47bfdb3
--- /dev/null
+++ b/src/components/constants.js
@@ -0,0 +1,14 @@
+const CONSTANTS = Object.freeze({
+  SIM_TOOL: {
+    CATEGORY: {
+      EXTERNAL_IFRAME: 'EXTERNAL_IFRAME',
+      REACT_COMPONENT: 'REACT_COMPONENT'
+    },
+    TOOL_TYPE: {
+      FLEXLAYOUT_TAB: 'flexlayout-tab',
+      EXTERNAL_TAB: 'external-tab'
+    }}
+});
+
+
+module.exports = CONSTANTS;
\ No newline at end of file
diff --git a/src/components/entry-page/entry-page.js b/src/components/entry-page/entry-page.js
index 6a3c88def7eb9d53b2e168d714ac706ea216a3e1..bb74203fe318c206d1cc0d008e4e694712675320 100644
--- a/src/components/entry-page/entry-page.js
+++ b/src/components/entry-page/entry-page.js
@@ -1,5 +1,4 @@
 import React from 'react';
-import Grid from '@material-ui/core/Grid';
 
 import NrpHeader from '../nrp-header/nrp-header.js';
 
@@ -35,7 +34,8 @@ export default class EntryPage extends React.Component {
         <div className='nrp-core-dashboard sidebar-left'>
           <NrpCoreDashboard />
         </div >
-        <iframe className='nrp-news sidebar-right' src='https://neurorobotics.net/latest.html' />
+        <iframe title='neurorobotics-news' className='nrp-news sidebar-right'
+          src='https://neurorobotics.net/latest.html' />
         {/*<TransceiverFunctionEditor experimentId='mqtt_simple_1'/>*/}
       </div>
     );
diff --git a/src/components/experiment-list/experiment-list-element.js b/src/components/experiment-list/experiment-list-element.js
index 5e690b7f26659a443bbbe28ea8ff77d84dfd53c8..3180a68655d1d01bfe0f85f8b0dd7976277ae9d5 100644
--- a/src/components/experiment-list/experiment-list-element.js
+++ b/src/components/experiment-list/experiment-list-element.js
@@ -1,7 +1,7 @@
 import React from 'react';
 // import { Link, useHistory } from 'react-router-dom';
 import { withRouter } from 'react-router-dom';
-import { FaTrash, FaFileExport, FaShareAlt, FaClone, FaBullseye, FaLastfmSquare } from 'react-icons/fa';
+import { FaTrash, FaFileExport, FaShareAlt, FaClone } from 'react-icons/fa';
 // import { MdOutlineDownloadDone } from 'react-icons/md';
 // import { RiPlayFill, RiPlayLine, RiPlayList2Fill } from 'react-icons/ri';
 // import { GoX } from 'react-icons/go';
@@ -49,7 +49,7 @@ class ExperimentListElement extends React.Component {
   }
 
   async componentDidMount() {
-    this.state.edibleName = this.state.visibleName;
+    this.setState({ edibleName: this.state.visibleName });
     this.handleClickOutside = this.handleClickOutside.bind(this);
     document.addEventListener('mousedown', this.handleClickOutside);
   }
diff --git a/src/components/experiment-list/simulation-details.js b/src/components/experiment-list/simulation-details.js
index fba5b183b81a0a1ea8f12d6627f8a84ca674a9ea..66d2973756d5df0b034919a8f6bb0ebbde0bb643 100644
--- a/src/components/experiment-list/simulation-details.js
+++ b/src/components/experiment-list/simulation-details.js
@@ -1,5 +1,5 @@
 import React from 'react';
-import { FaStop, FaStopCircle } from 'react-icons/fa';
+import { FaStop } from 'react-icons/fa';
 import { ImEnter } from 'react-icons/im';
 import { withRouter } from 'react-router-dom';
 
diff --git a/src/components/experiment-workbench/experiment-tools-service.js b/src/components/experiment-workbench/experiment-tools-service.js
index bf3d401277b628bda94e602c9dde1e2cac7cf773..aa98743f45db90fbb5a9d099c3ec62c9a4b3cd36 100644
--- a/src/components/experiment-workbench/experiment-tools-service.js
+++ b/src/components/experiment-workbench/experiment-tools-service.js
@@ -1,10 +1,13 @@
-import { Description } from '@material-ui/icons';
-import NrpCoreDashboard from '../nrp-core-dashboard/nrp-core-dashboard';
-import TransceiverFunctionEditor from '../tf-editor/tf-editor';
+import FlexLayout from 'flexlayout-react';
 
 import DescriptionIcon from '@material-ui/icons/Description';
 import ListAltIcon from '@material-ui/icons/ListAlt';
 
+import NrpCoreDashboard from '../nrp-core-dashboard/nrp-core-dashboard';
+import TransceiverFunctionEditor from '../tf-editor/tf-editor';
+import XpraView from '../xpra/xpra-view';
+import { SIM_TOOL } from '../constants';
+
 
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
@@ -23,6 +26,7 @@ class ExperimentToolsService {
       this.registerToolConfig(ExperimentToolsService.TOOLS[toolEntry]);
     }
     this.registerToolConfig(NrpCoreDashboard.CONSTANTS.TOOL_CONFIG);
+    this.registerToolConfig(XpraView.CONSTANTS.TOOL_CONFIG);
   }
 
   static get instance() {
@@ -33,6 +37,10 @@ class ExperimentToolsService {
     return _instance;
   }
 
+  setFlexLayoutModel(model) {
+    this.flexLayoutModel = model;
+  }
+
   registerToolConfig(toolConfig) {
     let id = toolConfig.flexlayoutNode.component;
     if (this.tools.has(id)) {
@@ -49,28 +57,57 @@ class ExperimentToolsService {
     if (toolConfig && toolConfig.flexlayoutFactoryCb) {
       return toolConfig.flexlayoutFactoryCb();
     }
-
-    if (component === 'button') {
-      return <button>{node.getName()}</button>;
+    else {
+      console.error('tool config for "' + component + '" is missing a callback for creating a flex-layout window!');
     }
-    else if (component === 'tab') {
-      return component.flexlayoutFactoryCb();
+  }
+
+  startToolDrag(toolConfig, layoutReference) {
+    let instances = this.getComponentInstanceList(
+      toolConfig.flexlayoutNode.component,
+      layoutReference.current.previousModel);
+    if (toolConfig.singleton && instances.length > 0) {
+      layoutReference.current.doAction(FlexLayout.Actions.selectTab(instances[0].getId()));
     }
-    else if (component === 'nest_wiki') {
-      return <iframe src='https://en.wikipedia.org/wiki/NEST_(software)' title='nest_wiki'
-        className='flexlayout-iframe'></iframe>;
+    else {
+      layoutReference.current.addTabWithDragAndDrop(toolConfig.flexlayoutNode.name, toolConfig.flexlayoutNode);
     }
   }
 
-  startToolDrag(flexlayoutNode, layoutReference) {
-    layoutReference.current.addTabWithDragAndDrop(flexlayoutNode.name, flexlayoutNode);
+  addTool(toolConfig, layoutReference) {
+    let instances = this.getComponentInstanceList(
+      toolConfig.flexlayoutNode.component,
+      layoutReference.current.previousModel);
+    if (toolConfig.singleton && instances.length > 0) {
+      layoutReference.current.doAction(FlexLayout.Actions.selectTab(instances[0].getId()));
+    }
+    else {
+      layoutReference.current.addTabToActiveTabSet(toolConfig.flexlayoutNode);
+    }
   }
 
-  addTool(flexlayoutNode, layoutReference) {
-    layoutReference.current.addTabToActiveTabSet(flexlayoutNode);
+  getComponentInstanceList(flexLayoutComponent, flexLayoutModel) {
+    let list = [];
+    flexLayoutModel.visitNodes((node, level) => {
+      if (node._attributes.component === flexLayoutComponent) {
+        list.push(node);
+      }
+    });
+    return list;
   }
 }
 
+ExperimentToolsService.CONSTANTS = Object.freeze({
+  /*CATEGORY: {
+    EXTERNAL_IFRAME: 'EXTERNAL_IFRAME',
+    REACT_COMPONENT: 'REACT_COMPONENT'
+  },
+  TOOL_TYPE: {
+    FLEXLAYOUT_TAB: 'flexlayout-tab',
+    EXTERNAL_TAB: 'external-tab'
+  }*/
+});
+
 ExperimentToolsService.TOOLS = Object.freeze({
   // NEST_DESKTOP: {
   //   singleton: true,
@@ -91,9 +128,9 @@ ExperimentToolsService.TOOLS = Object.freeze({
   //   }
   // },
   TEST_NRP_CORE_DOCU: {
-    singleton: true,
+    singleton: false,
+    type: SIM_TOOL.TOOL_TYPE.FLEXLAYOUT_TAB,
     flexlayoutNode: {
-      'type': 'tab',
       'name': 'NRP-Core Docs',
       'component': 'nrp-core-docu'
     },
@@ -103,12 +140,50 @@ ExperimentToolsService.TOOLS = Object.freeze({
     },
     getIcon: () => {
       return <DescriptionIcon/>;
+    },
+    isShown: () => {
+      return true;
     }
   },
+  /*XPRA_EXTERNAL_TAB: {
+    singleton: true,
+    type: ExperimentToolsService.CONSTANTS.TOOL_TYPE.EXTERNAL_TAB,
+    flexlayoutNode: {
+      'name': 'Xpra',
+      'component': 'xpra-external'
+    },
+    getIcon: () => {
+      return <div>
+        <a href='http://localhost:9000/xpra/index.html' target='_blank' rel="noreferrer">
+          <img src={'https://www.xpra.org/icons/xpra-logo.png'}
+            alt="Xpra"
+            style={{width: 40+ 'px', height: 20 + 'px'}} />
+        </a>
+      </div>;
+    }
+  },*/
+  /*XPRA: {
+    singleton: true,
+    type: ExperimentToolsService.CONSTANTS.TOOL_TYPE.FLEXLAYOUT_TAB,
+    flexlayoutNode: {
+      'name': 'Server Videostream (Xpra)',
+      'component': 'xpra'
+    },
+    flexlayoutFactoryCb: () =>  {
+      return <XpraView />;
+    },
+    getIcon: () => {
+      return <OndemandVideoIcon />;
+    },
+    isShown: () => {
+      const xpra = ExperimentWorkbenchService.instance.xpraUrls;
+      return xpra && xpra.length > 0;
+    }
+  },*/
   TRANSCEIVER_FUNCTIONS_EDITOR: {
     singleton: true,
+    type: SIM_TOOL.TOOL_TYPE.FLEXLAYOUT_TAB,
     flexlayoutNode: {
-      'type': 'tab',
       'name': 'Edit experiment files',
       'component': 'TransceiverFunctionEditor'
     },
@@ -117,13 +192,10 @@ ExperimentToolsService.TOOLS = Object.freeze({
     },
     getIcon: () => {
       return <ListAltIcon/>;
+    },
+    isShown: () => {
+      return true;
     }
-  }});
-
-ExperimentToolsService.CONSTANTS = Object.freeze({
-  CATEGORY: {
-    EXTERNAL_IFRAME: 'EXTERNAL_IFRAME',
-    REACT_COMPONENT: 'REACT_COMPONENT'
   }
 });
 
diff --git a/src/components/experiment-workbench/experiment-workbench-service.js b/src/components/experiment-workbench/experiment-workbench-service.js
index 4a9edc07d3812bd5ebe107089f468baf250c0437..362c13dd306476ebddc5e6448df1b621e8b97b03 100644
--- a/src/components/experiment-workbench/experiment-workbench-service.js
+++ b/src/components/experiment-workbench/experiment-workbench-service.js
@@ -2,6 +2,9 @@ import { EventEmitter } from 'events';
 
 import MqttClientService from '../../services/mqtt-client-service';
 import DialogService from '../../services/dialog-service';
+import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service';
+import ServerResourcesService from '../../services/experiments/execution/server-resources-service.js';
+import { EXPERIMENT_STATE } from '../../services/experiments/experiment-constants';
 
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
@@ -19,6 +22,8 @@ class ExperimentWorkbenchService extends EventEmitter {
     this._serverURL = undefined;
     this._errorToken = undefined;
     this._statusToken = undefined;
+    this._xpraUrlsConfig = [];
+    this._xpraUrlsConfirmed = [];
   }
 
   static get instance() {
@@ -34,7 +39,7 @@ class ExperimentWorkbenchService extends EventEmitter {
   }
   set experimentInfo(info) {
     this._expInfo = info;
-    console.info(['ExperimentWorkbenchService - experimentInfo', this._expInfo]);
+    //console.info(['ExperimentWorkbenchService - experimentInfo', this._expInfo]);
   }
 
   get experimentID() {
@@ -42,7 +47,7 @@ class ExperimentWorkbenchService extends EventEmitter {
   }
   set experimentID(experimentID) {
     this._experimentID = experimentID;
-    console.info(['ExperimentWorkbenchService - experimentID', this._experimentID]);
+    //console.info(['ExperimentWorkbenchService - experimentID', this._experimentID]);
   }
 
   get serverURL() {
@@ -50,7 +55,14 @@ class ExperimentWorkbenchService extends EventEmitter {
   }
   set serverURL(serverURL) {
     this._serverURL = serverURL;
-    console.info(['ExperimentWorkbenchService - serverURL', this._serverURL]);
+    //console.info(['ExperimentWorkbenchService - serverURL', this._serverURL]);
+  }
+
+  get simulationState() {
+    return this._simulationState;
+  }
+  set simulationState(state) {
+    this._simulationState = state;
   }
 
   /**
@@ -64,7 +76,7 @@ class ExperimentWorkbenchService extends EventEmitter {
   }
   set simulationInfo(simulationInfo) {
     this._simulationInfo = simulationInfo;
-    console.info(['ExperimentWorkbenchService - simulationInfo', this._simulationInfo]);
+    //console.info(['ExperimentWorkbenchService - simulationInfo', this._simulationInfo]);
     ExperimentWorkbenchService.instance.emit(
       ExperimentWorkbenchService.EVENTS.SIMULATION_SET,
       this._simulationInfo
@@ -72,6 +84,61 @@ class ExperimentWorkbenchService extends EventEmitter {
     this.setTopics(this._simulationInfo);
   }
 
+  get xpraConfigUrls() {
+    return this._xpraUrlsConfig;
+  }
+  set xpraConfigUrls(urls) {
+    this._xpraUrlsConfig = urls;
+    //console.info(['ExperimentWorkbenchService - xpraUrls', this._xpraConfigUrls]);
+  }
+
+  get xpraUrls() {
+    return this._xpraUrlsConfirmed;
+  }
+
+  async confirmXpraUrls() {
+    const simState = this.simulationState;
+    if (!this._xpraUrlsConfig || this._xpraUrlsConfig.length === 0
+        || !simState || simState === EXPERIMENT_STATE.CREATED
+        || simState === EXPERIMENT_STATE.UNDEFINED || simState === EXPERIMENT_STATE.FAILED) {
+      return;
+    }
+    else {
+      let confirmedUrls = [];
+      for (let url of this._xpraUrlsConfig) {
+        const response = await fetch(url, { method: 'GET' });
+        if (response.ok) {
+          confirmedUrls.push(url);
+        }
+      }
+      this._xpraUrlsConfirmed = confirmedUrls;
+    }
+  }
+
+  /**
+   * Gather experiment information.
+   * @param experimentID The experiment's ID
+   */
+  async initExperimentInformation(experimentID) {
+    let experiments = await ExperimentStorageService.instance.getExperiments();
+    const experimentInfo = experiments.find(experiment => experiment.id === experimentID);
+    this.experimentInfo = experimentInfo;
+
+    if (experimentInfo.joinableServers.length === 1) {
+      const runningSimulation = experimentInfo.joinableServers[0].runningSimulation;
+      if (runningSimulation) {
+        let serverConfig = await ServerResourcesService.instance.getServerConfig(
+          experimentInfo.joinableServers[0].server);
+        this.serverURL = serverConfig['nrp-services'];
+        this.xpraConfigUrls = [serverConfig.xpra];
+        this.simulationInfo = {
+          ID: runningSimulation.simulationID,
+          MQTTPrefix: runningSimulation.MQTTPrefix
+        };
+      }
+    }
+  }
+
   /**
    * Returns the MQTT broker connection status
    * @returns {boolean} the MQTT broker connection status
@@ -144,6 +211,9 @@ class ExperimentWorkbenchService extends EventEmitter {
     try {
       const status = JSON.parse(msg);
       if (status.state) {
+        this.simulationState = status.state;
+        this.confirmXpraUrls();
+
         ExperimentWorkbenchService.instance.emit(
           ExperimentWorkbenchService.EVENTS.SIMULATION_STATUS_UPDATED,
           status
diff --git a/src/components/experiment-workbench/experiment-workbench.js b/src/components/experiment-workbench/experiment-workbench.js
index 58bbed9ed9e5336101691900302180631776a96a..6b2c1677d6526aacd6e47f82f8567673c8b64364 100644
--- a/src/components/experiment-workbench/experiment-workbench.js
+++ b/src/components/experiment-workbench/experiment-workbench.js
@@ -4,15 +4,13 @@ import FlexLayout from 'flexlayout-react';
 import ExperimentToolsService from './experiment-tools-service';
 import ExperimentWorkbenchService from './experiment-workbench-service';
 import ExperimentTimeBox from './experiment-time-box';
-import ExperimentStorageService from '../../services/experiments/files/experiment-storage-service';
 import SimulationService from '../../services/experiments/execution/running-simulation-service';
 import ExperimentExecutionService from '../../services/experiments/execution/experiment-execution-service';
 import ServerResourcesService from '../../services/experiments/execution/server-resources-service.js';
 import DialogService from '../../services/dialog-service';
 import { EXPERIMENT_STATE, EXPERIMENT_FINAL_STATE } from '../../services/experiments/experiment-constants';
-import timeDDHHMMSS from '../../utility/time-filter';
-
 import LeaveWorkbenchDialog from './leave-workbench-dialog';
+import { SIM_TOOL } from '../constants';
 
 import '../../../node_modules/flexlayout-react/style/light.css';
 import './experiment-workbench.css';
@@ -33,6 +31,7 @@ import Divider from '@material-ui/core/Divider';
 import List from '@material-ui/core/List';
 import Grid from '@material-ui/core/Grid';
 import Paper from '@material-ui/core/Paper';
+import Tooltip from '@material-ui/core/Tooltip';
 
 import ListItem from '@material-ui/core/ListItem';
 import ListItemIcon from '@material-ui/core/ListItemIcon';
@@ -180,6 +179,7 @@ class ExperimentWorkbench extends React.Component {
 
     const {experimentID} = props.match.params;
     this.experimentID = experimentID;
+    ExperimentWorkbenchService.instance.experimentID = this.experimentID;
     this.serverURL = ExperimentWorkbenchService.instance.serverURL;
 
     this.state = {
@@ -196,33 +196,28 @@ class ExperimentWorkbench extends React.Component {
       availableServers: []
     };
 
-    ExperimentWorkbenchService.instance.experimentID = this.experimentID;
-
     this.refLayout = React.createRef();
     this.state.modelFlexLayout.doAction(FlexLayout.Actions.setActiveTabset('defaultTabset'));
+    ExperimentToolsService.instance.setFlexLayoutModel(this.state.modelFlexLayout);
   }
 
-  async UNSAFE_componentWillMount() {
-    let experiments = await ExperimentStorageService.instance.getExperiments();
-    const experimentInfo = experiments.find(experiment => experiment.id === this.experimentID);
-    ExperimentWorkbenchService.instance.experimentInfo = experimentInfo;
-
-    this.setState({experimentConfiguration: experimentInfo.configuration});
-  };
-
   async componentDidMount() {
-    // Get the simulation ID from ExperimentWorkbenchService, if is defined (for joining the simulation)
+    await ExperimentWorkbenchService.instance.initExperimentInformation(this.experimentID);
+
+    if (ExperimentWorkbenchService.instance.experimentInfo) {
+      this.setState({experimentConfiguration: ExperimentWorkbenchService.instance.experimentInfo.configuration});
+    }
     if (ExperimentWorkbenchService.instance.simulationInfo !== undefined) {
-      this.state.runningSimulationID = ExperimentWorkbenchService.instance.simulationInfo.ID;
+      this.setState({ runningSimulationID: ExperimentWorkbenchService.instance.simulationInfo.ID });
     }
 
     // Update simulation state, if it is defined
     if (this.state.runningSimulationID !== undefined) {
       await SimulationService.instance.getInfo(
-        this.serverURL,
+        ExperimentWorkbenchService.instance.serverURL,
         this.state.runningSimulationID
       ).then((simInfo) => {
-        this.setState({ simulationState: simInfo.state});
+        simInfo && this.setState({ simulationState: simInfo.state});
       });
     }
 
@@ -233,7 +228,7 @@ class ExperimentWorkbench extends React.Component {
     );
 
     // update the list of available servers
-    this.state.availableServers = await ServerResourcesService.instance.getServerAvailability();
+    this.setState({ availableServers: await ServerResourcesService.instance.getServerAvailability() });
 
     // subscribe to server availablility
     ServerResourcesService.instance.addListener(
@@ -318,8 +313,13 @@ class ExperimentWorkbench extends React.Component {
       this.setState({ simulationState: undefined });
       await ExperimentExecutionService.instance.startNewExperiment(
         ExperimentWorkbenchService.instance.experimentInfo
-      ).then(async (simRespose) => {
-        const simInfo = await simRespose['simulation'].json();
+      ).then(async (simResponse) => {
+        if (typeof simResponse === 'undefined') {
+          console.error('startNewExperiment() returned with simResponse === undefined');
+          return;
+        }
+
+        const simInfo = await simResponse.simulation.json();
         // TODO: get proper simulation information
         if (simInfo) {
           ExperimentWorkbenchService.instance.simulationInfo = {
@@ -333,8 +333,8 @@ class ExperimentWorkbench extends React.Component {
         else {
           throw new Error('Could not parse the response from the backend after initializing the simulation');
         }
-        ExperimentWorkbenchService.instance.serverURL = simRespose['serverURL'];
-        this.serverURL = simRespose['serverURL'];
+        ExperimentWorkbenchService.instance.serverURL = simResponse['serverURL'];
+        this.serverURL = simResponse['serverURL'];
       }).catch((failure) => {
         DialogService.instance.simulationError({ message: failure });
       });
@@ -375,7 +375,7 @@ class ExperimentWorkbench extends React.Component {
     if (this.state.runningSimulationID !== undefined) {
       this.setState({ simStateLoading: true });
       await SimulationService.instance.updateState(
-        this.serverURL,
+        ExperimentWorkbenchService.instance.serverURL,
         this.state.runningSimulationID,
         newState
       ).then((simInfo) => {
@@ -542,25 +542,35 @@ class ExperimentWorkbench extends React.Component {
           <Divider />
           <List>
             {Array.from(ExperimentToolsService.instance.tools.values()).map((tool, index) => {
-              return (
-                <ListItem button key={index}
-                  onMouseDown={() => {
-                    ExperimentToolsService.instance.startToolDrag(
-                      tool.flexlayoutNode,
-                      this.refLayout);
-                  }}
-                  onClick={() => {
-                    ExperimentToolsService.instance.addTool(
-                      tool.flexlayoutNode,
-                      this.refLayout);
-                  }}
-                >
-                  <ListItemIcon >
-                    {tool.getIcon()}
-                  </ListItemIcon>
-                  <ListItemText primary={tool.flexlayoutNode.name} />
-                </ListItem>
-              );
+              if (typeof tool.isShown !== 'undefined' && tool.isShown()) {
+                return (
+                  tool.type === SIM_TOOL.TOOL_TYPE.EXTERNAL_TAB ?
+                    <ListItem button key={index}
+                      disabled={typeof tool.isDisabled !== 'undefined' && tool.isDisabled()}>
+                      <ListItemIcon >{tool.getIcon()}</ListItemIcon>
+                      <ListItemText primary={tool.flexlayoutNode.name} />
+                    </ListItem>
+                    :
+                    <ListItem button key={index}
+                      disabled={typeof tool.isDisabled !== 'undefined' && tool.isDisabled()}
+                      onMouseDown={() => {
+                        ExperimentToolsService.instance.startToolDrag(
+                          tool,
+                          this.refLayout);
+                      }}
+                      onClick={() => {
+                        ExperimentToolsService.instance.addTool(
+                          tool,
+                          this.refLayout);
+                      }}
+                    >
+                      <Tooltip title={tool.flexlayoutNode.name} placement="right">
+                        <ListItemIcon >{tool.getIcon()}</ListItemIcon>
+                      </Tooltip>
+                      <ListItemText primary={tool.flexlayoutNode.name} />
+                    </ListItem>
+                );
+              }
             })}
           </List>
         </Drawer>
diff --git a/src/components/experiments-overview/experiments-overview.js b/src/components/experiments-overview/experiments-overview.js
index abebaaf199e49d6bc7e908440f1104404514bb11..bb2a4dbd27903b3219046ade0016fdc1e9e5d0fa 100644
--- a/src/components/experiments-overview/experiments-overview.js
+++ b/src/components/experiments-overview/experiments-overview.js
@@ -71,7 +71,7 @@ export default class ExperimentsOverview extends React.Component {
       this.onUpdateServerAvailability
     );
 
-    ServerResourcesService.instance.removeListener(
+    ExperimentExecutionService.instance.removeListener(
       ExperimentExecutionService.EVENTS.START_EXPERIMENT,
       this.onStartExperiment
     );
diff --git a/src/components/xpra/xpra-view.css b/src/components/xpra/xpra-view.css
new file mode 100644
index 0000000000000000000000000000000000000000..4b01ed1ad9f85b167e103aa0b536d8ad39783993
--- /dev/null
+++ b/src/components/xpra/xpra-view.css
@@ -0,0 +1,18 @@
+.xpra-view-wrapper {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+}
+
+.xpra-url-selector-header {
+    display: flex;
+    flex-direction: row;
+}
+
+.xpra-url-dropdown-selector {
+    margin-left: 10px;
+}
+
+.note-no-streams {
+    margin: 20px;
+}
\ No newline at end of file
diff --git a/src/components/xpra/xpra-view.js b/src/components/xpra/xpra-view.js
new file mode 100644
index 0000000000000000000000000000000000000000..4dc439b72a5ea60cc8507a745ffe9cca8b3c24b3
--- /dev/null
+++ b/src/components/xpra/xpra-view.js
@@ -0,0 +1,114 @@
+import React from 'react';
+import OndemandVideoIcon from '@material-ui/icons/OndemandVideo';
+
+import ExperimentWorkbenchService from '../experiment-workbench/experiment-workbench-service';
+import RunningSimulationService from '../../services/experiments/execution/running-simulation-service';
+import { SIM_TOOL } from '../constants';
+import { EXPERIMENT_STATE } from '../../services/experiments/experiment-constants';
+
+import './xpra-view.css';
+
+export default class XpraView extends React.Component {
+  constructor() {
+    super();
+
+    this.state = {
+      xpraUrls: ExperimentWorkbenchService.instance.xpraUrls,
+      currentUrl: undefined
+    };
+    if (this.state.xpraUrls.length > 0) {
+      this.state.currentUrl = this.state.xpraUrls[0];
+    }
+
+    ExperimentWorkbenchService.instance.on(
+      ExperimentWorkbenchService.EVENTS.SIMULATION_STATUS_UPDATED,
+      (status) => {
+        this.simulationState = status.state;
+        if (status.state === EXPERIMENT_STATE.PAUSED || status.state === EXPERIMENT_STATE.STARTED) {
+          if (ExperimentWorkbenchService.instance.xpraUrls.length > 0) {
+            this.setState({
+              xpraUrls: ExperimentWorkbenchService.instance.xpraUrls,
+              currentUrl: ExperimentWorkbenchService.instance.xpraUrls[0]
+            });
+          }
+        }
+      }
+    );
+  }
+
+  onChangeSelectedXpraUrl(event) {
+    this.setState({
+      currentUrl: event.target.value
+    });
+  }
+
+  render() {
+    return (
+      <div className='xpra-view-wrapper'>
+        {this.state.currentUrl ?
+          <div style={{height: '100%'}}>
+            <div className='xpra-url-selector-header'>
+              <div>Streaming Engine:</div>
+              <select
+                className='xpra-url-dropdown-selector'
+                name="selectXpraUrl"
+                value={this.state.currentUrl}
+                onChange={(event) => this.onChangeSelectedXpraUrl(event)}>
+                {this.state.xpraUrls.map(url => {
+                  return (<option key={url} value={url}>{url}</option>);
+                })}
+              </select>
+            </div>
+            <iframe src={this.state.currentUrl + '?printing=No&file_transfer=No&floating_menu=No&sound=No'}
+              title='Xpra' />
+          </div>
+          :
+          <div className='note-no-streams'>No streams available, maybe no simulation has been started yet?</div>
+        }
+      </div>
+    );
+  }
+}
+
+XpraView.CONSTANTS = Object.freeze({
+  TOOL_CONFIG: {
+    singleton: true,
+    type: SIM_TOOL.TOOL_TYPE.FLEXLAYOUT_TAB,
+    flexlayoutNode: {
+      'name': 'Server Videostream (Xpra)',
+      'component': 'xpra'
+    },
+    flexlayoutFactoryCb: () =>  {
+      return <XpraView />;
+    },
+    getIcon: () => {
+      return <OndemandVideoIcon />;
+    },
+    isShown: () => {
+      if (!ExperimentWorkbenchService.instance.experimentInfo) {
+        return false;
+      }
+
+      // this is not really clean to parse the engine configs here in the frontend,
+      // but necessary until the proxy can provide information with the experiment config / simulation start
+      const engineConfigs = ExperimentWorkbenchService.instance.experimentInfo.configuration.EngineConfigs;
+      let show = false;
+      for (let config of engineConfigs) {
+        if (config.EngineProcCmd && config.EngineProcCmd.includes('/usr/xpra-entrypoint.sh')) {
+          show = true;
+        }
+      }
+
+      return show;
+    },
+    isDisabled: () => {
+      const xpraUrls = ExperimentWorkbenchService.instance.xpraUrls;
+      if (!xpraUrls || xpraUrls.length === 0) {
+        return true;
+      }
+      else  {
+        return false;
+      }
+    }
+  }
+});
diff --git a/src/mocks/mock_available-servers.json b/src/mocks/mock_available-servers.json
index 7b0c1d99e405d23217e08c0d0268525eee976913..c3a26979cc6779cc38531f8e2a8f933b7dcd8cda 100644
--- a/src/mocks/mock_available-servers.json
+++ b/src/mocks/mock_available-servers.json
@@ -3,12 +3,14 @@
         "internalIp": "http://localhost:8080",
         "nrp-services": "http://localhost:8080",
         "serverJobLocation": "local",
-        "id": "localhost"
+        "id": "localhost",
+        "xpra": "localhost:1234/xpra/index.html"
     },
     {
         "internalIp": "http://1.2.3.4:8080",
         "nrp-services": "http://1.2.3.4:8080",
         "serverJobLocation": "1.2.3.4",
-        "id": "1.2.3.4-port-8080"
+        "id": "1.2.3.4-port-8080",
+        "xpra": "http://1.2.3.4:8080/xpra/index.html"
     }
 ]
\ No newline at end of file
diff --git a/src/services/experiments/execution/__tests__/experiment-execution-service.test.js b/src/services/experiments/execution/__tests__/experiment-execution-service.test.js
index 239e09c514381b038966c1b02826ec98b7145b31..eae0de05d3f10df6068f11cc6de14fbd2b864490 100644
--- a/src/services/experiments/execution/__tests__/experiment-execution-service.test.js
+++ b/src/services/experiments/execution/__tests__/experiment-execution-service.test.js
@@ -13,18 +13,8 @@ import ExperimentExecutionService from '../../../../services/experiments/executi
 import SimulationService from '../../../../services/experiments/execution/running-simulation-service';
 import ServerResourcesService from '../../../../services/experiments/execution/server-resources-service';
 import { EXPERIMENT_STATE } from '../../../../services/experiments/experiment-constants.js';
-import config from '../../../../config.json';
 
 jest.mock('../../../authentication-service.js');
-//jest.setTimeout(10000);
-
-beforeEach(() => {
-  //jest.genMockFromModule('AuthenticationService');
-  //jest.mock('AuthenticationService');
-  if (config.auth.enableOIDC) {
-    jest.mock('../../../authentication-service.js');
-  }
-});
 
 afterEach(() => {
   jest.restoreAllMocks();
@@ -67,34 +57,37 @@ describe('ExperimentExecutionService', () => {
     );
   });
 
-  test('should go through the list of available servers when trying to start an experiment', (done) => {
+  test('should go through the list of available servers when trying to start an experiment', async () => {
+    const targetServer = MockAvailableServers[MockAvailableServers.length-1];
     jest.spyOn(console, 'error').mockImplementation();
     ServerResourcesService.instance.availableServers = MockAvailableServers;
-    jest.spyOn(ServerResourcesService.instance, 'getServerConfig').mockImplementation((server) =>{
-      if (server===MockAvailableServers[-1]) {
-        return Promise.resolve();
+    jest.spyOn(ServerResourcesService.instance, 'getServerAvailability').mockImplementation((server) =>{
+      return Promise.resolve(MockAvailableServers);
+    });
+    jest.spyOn(ServerResourcesService.instance, 'getServerConfig').mockImplementation((serverId) =>{
+      if (serverId === targetServer.id) {
+        return Promise.resolve(targetServer);
       }
       else {
         return Promise.reject();
       }
-    }
-    );
-    let properServerID;
+    });
 
+    let properServerID;
     jest.spyOn(ExperimentExecutionService.instance, 'launchExperimentOnServer').mockImplementation(
       // only the last server in the list will return a successful launch
-      (id,privateparam,configFile,serverID,serverConfig, progressCallback) => {
+      (id, privateparam, configFile, serverID, serverConfig, progressCallback) => {
         properServerID = serverID;
         return Promise.resolve();
       }
     );
+
     let experiment = MockExperiments[0];
-    ExperimentExecutionService.instance.startNewExperiment(experiment).then(() => {
+    await ExperimentExecutionService.instance.startNewExperiment(experiment).then(() => {
       MockAvailableServers.forEach(server => {
         expect(ServerResourcesService.instance.getServerConfig).toHaveBeenCalledWith(server.id);
       });
-      expect(properServerID).toBe(MockAvailableServers[-1]);
-      done();
+      expect(properServerID).toBe(targetServer.id);
     });
   });
 
diff --git a/src/services/experiments/execution/experiment-execution-service.js b/src/services/experiments/execution/experiment-execution-service.js
index 6f5af6c992472f29c09a94847d70efdacb831bfc..027e8607de69a1714cf10d17bf8a16ced349f1d5 100644
--- a/src/services/experiments/execution/experiment-execution-service.js
+++ b/src/services/experiments/execution/experiment-execution-service.js
@@ -6,6 +6,7 @@ import SimulationService from './running-simulation-service.js';
 import DialogService from '../../dialog-service';
 import { HttpService } from '../../http-service.js';
 import { EXPERIMENT_STATE } from '../experiment-constants.js';
+import ExperimentWorkbenchService from '../../../components/experiment-workbench/experiment-workbench-service.js';
 
 let _instance = null;
 const SINGLETON_ENFORCER = Symbol();
@@ -43,7 +44,7 @@ class ExperimentExecutionService extends HttpService {
     //NrpAnalyticsService.instance.tickDurationEvent('Server-initialization');
 
     ExperimentExecutionService.instance.emit(ExperimentExecutionService.EVENTS.START_EXPERIMENT, experiment);
-    let serversToTry = experiment.devServer
+    let serverIdsToTry = experiment.devServer
       ? [experiment.devServer]
       : (await ServerResourcesService.instance.getServerAvailability(true))
         .map(s => s.id);
@@ -64,11 +65,11 @@ class ExperimentExecutionService extends HttpService {
     };
     let serverConfig;
     let serverFound = false;
-    let serverID;
-    for (let server of serversToTry){
+    let targetServerID;
+    for (let serverId of serverIdsToTry){
       try {
-        serverConfig = await ServerResourcesService.instance.getServerConfig(server);
-        serverID = server;
+        serverConfig = await ServerResourcesService.instance.getServerConfig(serverId);
+        targetServerID = serverId;
         serverFound = true;
         break;
       }
@@ -81,10 +82,13 @@ class ExperimentExecutionService extends HttpService {
       experiment.id,
       experiment.private,
       experiment.configuration.configFile,
-      serverID,
+      targetServerID,
       serverConfig,
       progressCallback
-    ).catch((failure) => {
+    ).then(success => {
+      ExperimentWorkbenchService.instance.xpraConfigUrls = [serverConfig.xpra];
+      return success;
+    }).catch((failure) => {
       if (failure && failure.isFatal) {
         return Promise.reject(ExperimentExecutionService.ERRORS.LAUNCH_FATAL_ERROR);
 
@@ -109,7 +113,7 @@ class ExperimentExecutionService extends HttpService {
    * Try launching an experiment on a specific server.
    * @param {string} experimentID - ID of the experiment to launch
    * @param {boolean} privateExperiment - whether the experiment is private or not
-   * @param {string} configFile - experiment configuration file name
+   * @param {string} experimentConfigFileName - experiment configuration file name
    * @param {string} serverID - server ID
    * @param {object} serverConfiguration - configuration of server
    * @param {function} progressCallback - a callback for progress updates
@@ -118,7 +122,7 @@ class ExperimentExecutionService extends HttpService {
   launchExperimentOnServer(
     experimentID,
     privateExperiment,
-    configFile,
+    experimentConfigFileName,
     serverID,
     serverConfiguration,
     progressCallback
@@ -130,6 +134,7 @@ class ExperimentExecutionService extends HttpService {
       }); //called once caller has the promise
 
       let serverURL = serverConfiguration['nrp-services'];
+      //let serverURL = serverConfiguration.id;
 
       // Create a new simulation.
       // >>Request:
@@ -142,13 +147,13 @@ class ExperimentExecutionService extends HttpService {
       // }
       let simInitData = {
         experimentID: experimentID,
-        experimentConfiguration: configFile,
+        experimentConfiguration: experimentConfigFileName,
         state: EXPERIMENT_STATE.CREATED,
         private: privateExperiment
       };
       this.httpRequestPOST(serverURL + '/simulation', JSON.stringify(simInitData))
-        .then((simulation) => {
-          resolve({'simulation': simulation, 'serverURL': serverURL});
+        .then((response) => {
+          resolve({'simulation': response, 'serverURL': serverURL});
         })
         .catch(reject);
       // <<Response: simulation
diff --git a/src/services/mqtt-client-service.js b/src/services/mqtt-client-service.js
index 2f5dcfd2942bee58086bab57614b244e1f1bb121..b72f0cb96797e719b876528614ccd7bf4e0c7106 100644
--- a/src/services/mqtt-client-service.js
+++ b/src/services/mqtt-client-service.js
@@ -1,4 +1,5 @@
 import mqtt from 'mqtt';
+import { v4 as uuidv4 } from 'uuid';
 import { EventEmitter } from 'events';
 
 //import { DataPackMessage } from 'nrp-jsproto/engine_grpc_pb';
@@ -28,7 +29,7 @@ export default class MqttClientService extends EventEmitter {
 
     this.subTokensMap = new Map();
 
-    // Since it's a- singleton, shoud the url be defined here?
+    // Since it's a singleton, shoud the url be defined here?
     const websocket_s = frontendConfig.mqtt.websocket ? frontendConfig.mqtt.websocket : 'ws';
     this.mqttBrokerUrl = websocket_s + '://' + frontendConfig.mqtt.url + ':' + frontendConfig.mqtt.port;
 
@@ -57,7 +58,7 @@ export default class MqttClientService extends EventEmitter {
 
   connect() {
     console.info('MQTT connecting to ' + this.mqttBrokerUrl + ' ...');
-    this.client = mqtt.connect(this.mqttBrokerUrl, { clientId: 'nrp-frontend'});
+    this.client = mqtt.connect(this.mqttBrokerUrl, { clientId: 'nrp-frontend_' + uuidv4() });
     this.client.on('connect', () => {
       this.onConnect();
     });
diff --git a/src/setupTests.js b/src/setupTests.js
index e3fec7fd9dc3109840fb06f3b5580dd71ec02f86..b4dbbec28c208da85329240f724d49bd565c89c5 100644
--- a/src/setupTests.js
+++ b/src/setupTests.js
@@ -11,7 +11,6 @@ beforeAll(() => {
   // Enable the mocking in tests.
   server.listen();
   jest.mock('./services/authentication-service.js');
-  // AuthenticationService.instance.mockClear();
 });
 
 afterEach(() => {