atlas-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mad...@apache.org
Subject [03/25] incubator-atlas git commit: ATLAS-1898: initial commit of ODF
Date Wed, 28 Jun 2017 05:57:16 GMT
http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-client.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-client.js b/odf/odf-web/src/main/webapp/scripts/odf-client.js
new file mode 100755
index 0000000..de64367
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-client.js
@@ -0,0 +1,1087 @@
+/**
+ *
+ * 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.
+ */
+require("bootstrap/dist/css/bootstrap.min.css");
+
+var $ = require("jquery");
+var bootstrap = require("bootstrap");
+
+var React = require("react");
+var ReactDOM = require("react-dom");
+var LinkedStateMixin = require('react-addons-linked-state-mixin');
+var ReactBootstrap = require("react-bootstrap");
+
+var Nav = ReactBootstrap.Nav;
+var NavItem = ReactBootstrap.NavItem;
+var Navbar = ReactBootstrap.Navbar;
+var NavDropdown = ReactBootstrap.NavDropdown;
+var Button = ReactBootstrap.Button;
+var Grid = ReactBootstrap.Grid;
+var Row = ReactBootstrap.Row;
+var Col = ReactBootstrap.Col;
+var Table = ReactBootstrap.Table;
+var Modal = ReactBootstrap.Modal;
+var Alert = ReactBootstrap.Alert;
+var Panel = ReactBootstrap.Panel;
+var Label = ReactBootstrap.Label;
+var Input = ReactBootstrap.Input;
+var Jumbotron = ReactBootstrap.Jumbotron;
+var Image = ReactBootstrap.Image;
+var Dropdown = ReactBootstrap.Dropdown;
+var DropdownButton = ReactBootstrap.DropdownButton;
+var CustomMenu = ReactBootstrap.CustomMenu;
+var MenuItem = ReactBootstrap.MenuItem;
+var Tooltip = ReactBootstrap.Tooltip;
+var OverlayTrigger = ReactBootstrap.OverlayTrigger;
+var Glyphicon = ReactBootstrap.Glyphicon;
+
+var ODFGlobals = require("./odf-globals.js");
+var OdfAnalysisRequest = require("./odf-analysis-request.js");
+var NewAnalysisRequestButton = OdfAnalysisRequest.NewCreateAnnotationsButton;
+var ODFBrowser = require("./odf-metadata-browser.js");
+var Utils = require("./odf-utils.js");
+var AtlasHelper = Utils.AtlasHelper;
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var UISpec = require("./odf-ui-spec.js");
+
+
+var knownAnnotations = {
+	"Default": [{value : "annotationType", style : "primary", label: "Unknown"}],
+	"ColumnAnalysisColumnAnnotation" : [{value: "jsonProperties.inferredDataClass.className", style: "danger" , label: "Class name"}, {value: "jsonProperties.inferredDataType.type", style: "info", label :"Datatype"}],
+	"DataQualityColumnAnnotation": [{style: "warning", value: "jsonProperties.qualityScore" , label: "Data quality score"}],
+    "MatcherAnnotation": [{style: "success", value: "jsonProperties.termAssignments", label: "Matching terms"}]
+};
+
+////////////////////////////////////////////////////////////////
+// toplevel navigation bar
+
+const constants_ODFNavBar = {
+  odfDataLakePage: "navKeyDataLakePage",
+  odfTermPage: "navKeyTermPage"
+}
+
+var ODFNavBar = React.createClass({
+   render: function() {
+       return (
+         <Navbar inverse>
+           <Navbar.Header>
+             <Navbar.Brand>
+               <b>Shop for Data Application, powered by Open Discovery Framework</b>
+             </Navbar.Brand>
+             <Navbar.Toggle />
+           </Navbar.Header>
+           <Navbar.Collapse>
+             <Nav pullRight activeKey={this.props.activeKey} onSelect={this.props.selectCallback}>
+               <NavItem eventKey={constants_ODFNavBar.odfDataLakePage} href="#">Data Lake Browser</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.odfTermPage} href="#">Glossary</NavItem>
+             </Nav>
+           </Navbar.Collapse>
+         </Navbar>
+       );
+   }
+});
+
+var ODFAnnotationLegend = React.createClass({
+
+	render : function(){
+		var items = [];
+		$.each(knownAnnotations, function(key, val){
+			$.each(val, function(key2, item){
+				items.push(<Label key={key + "_" + key2} bsStyle={item.style}>{item.label}</Label>);
+			});
+		});
+
+		return <div>{items}</div>;
+	}
+
+});
+
+var ODFAnnotationMarker = React.createClass({
+
+	render : function(){
+		var annotationKey = "Default";
+		var annotationLabels = [];
+		if(this.props.annotation && knownAnnotations[this.props.annotation.annotationType]){
+			annotationKey = this.props.annotation.annotationType;
+			var tooltip = <Tooltip id={this.props.annotation.annotationType}>{this.props.annotation.annotationType}<br/>{this.props.annotation.summary}</Tooltip>
+			$.each(knownAnnotations[annotationKey], function(key, val){
+				var style = val.style;
+				var value = ODFGlobals.getPathValue(this.props.annotation, val.value);
+				if (annotationKey === "MatcherAnnotation") {
+					value = value[0].matchingString; // if no abbreviation matches this will be the term; ideally it should be based on the OMBusinessTerm reference
+				}
+				else if(value && !isNaN(value)){
+					value = Math.round(value*100) + " %";
+				}
+				annotationLabels.push(<OverlayTrigger key={key} placement="top" overlay={tooltip}><Label style={{margin: "5px"}} bsStyle={style}>{value}</Label></OverlayTrigger>);
+			}.bind(this));
+		}else{
+			var tooltip = <Tooltip id={this.props.annotation.annotationType}>{this.props.annotation.annotationType}<br/>{this.props.annotation.summary}</Tooltip>
+			annotationLabels.push(<OverlayTrigger key="unknownAnnotation" placement="top" overlay={tooltip}><Label style={{margin: "5px"}} bsStyle={knownAnnotations[annotationKey][0].style}>{this.props.annotation.annotationType}</Label></OverlayTrigger>);
+		}
+
+		return <div style={this.props.style}>{annotationLabels}</div>;
+	}
+});
+
+
+var AnnotationsColumn = React.createClass({
+	mixins : [AJAXCleanupMixin],
+
+	getInitialState : function(){
+		return {annotations: []};
+	},
+
+	componentDidMount : function() {
+		if(this.props.annotations){
+			this.setState({loadedAnnotations : this.props.annotations});
+			return;
+		}
+
+		if(this.props.annotationReferences){
+			this.loadColumnAnnotations(this.props.annotationReferences);
+		}
+	},
+
+	componentWillReceiveProps : function(nextProps){
+		if(!this.isMounted()){
+			return;
+		}
+
+		if(nextProps.annotations){
+			this.setState({loadedAnnotations : nextProps.annotations});
+			return;
+		}
+	},
+
+	render : function(){
+		if(this.state){
+			var annotations = this.state.loadedAnnotations;
+			if(!annotations || annotations.length > 0 && annotations[0].repositoryId){
+				return <noscript/>;
+			}
+
+			var processedTypes = [];
+			var colAnnotations = [];
+			$.each(annotations, function(key, val){
+				if(processedTypes.indexOf(val.annotationType) == -1){
+					processedTypes.push(val.annotationType);
+					var style = {float: "left"};
+					if(key % 6 == 0){
+						style = {clear: "both"};
+					}
+
+					var summary = (val.summary ? val.summary : "");
+					colAnnotations.push(<ODFAnnotationMarker style={style} key={key} annotation={val}/>);
+				}
+			});
+
+			return <div>{colAnnotations}</div>;
+		}
+		return <noscript/>;
+	}
+
+});
+
+var QualityScoreFilter = React.createClass({
+
+	getInitialState : function(){
+		return {key: "All", val : "0", showMenu : false};
+	},
+
+	onSelect : function(obj, key){
+
+		if(obj.target.tagName != "INPUT"){
+			this.setState({key: key});
+			var equation = "All";
+			if(key != "All"){
+				if(this.refs.numberInput.getValue().trim() == ""){
+					return;
+				}
+				equation = key + this.refs.numberInput.getValue();
+			}
+			this.props.onFilter(equation);
+		}
+	},
+
+	textChange : function(event){
+		var equation = "All";
+		if(this.state.key != "All"){
+			if(this.refs.numberInput.getValue().trim() == ""){
+				return;
+			}
+			equation = this.state.key + this.refs.numberInput.getValue();
+		}
+		this.props.onFilter(equation);
+	},
+
+	render : function(){
+		var items = [];
+		var values = ["<", "<=", "==", ">=", ">", "!=", "All"];
+		$.each(values, function(key, val){
+			items.push(<MenuItem onSelect={this.onSelect} id={val} key={key} eventKey={val}>{val}</MenuItem>)
+		}.bind(this));
+
+		var menu = <div bsRole="menu" className={"dropdown-menu"}>
+			<h5 style={{float: "left", marginLeft: "15px"}}><Label ref="typeLabel">{this.state.key}</Label></h5>
+			<Input style={{width: "100px"}} ref="numberInput" onChange={this.textChange} type="number" defaultValue="1"/>
+			{items}
+		</div>;
+
+		return <div style={this.props.style}  >
+			<Dropdown id="quality score select" onSelect={this.onSelect} open={this.state.showMenu} onToggle={function(){}}>
+				<Button bsRole="toggle" onClick={function(){this.setState({showMenu: !this.state.showMenu})}.bind(this)}>Qualityscore filter</Button>
+				{menu}
+			</Dropdown>
+		</div>;
+	}
+});
+
+var DataClassFilter = React.createClass({
+
+	defaultClasses : ["US Zip", "Credit Card"],
+
+	render : function(){
+		var items = [];
+		var classes = (this.props.dataClasses ? this.props.dataClasses.slice() : this.defaultClasses);
+		classes.push("All");
+		$.each(classes, function(key, val){
+			items.push(<MenuItem id={val} key={key} eventKey={val}>{val}</MenuItem>)
+		});
+
+		return <div style={this.props.style}>
+			<DropdownButton id="Data class filter" onSelect={function(obj, key){this.props.onFilter(key)}.bind(this)} title="Data Class filter">
+				{items}
+			</DropdownButton>
+		</div>;
+	}
+});
+
+
+var FilterMenu = React.createClass({
+
+	getInitialState : function(){
+		return {showMenu : false, dataClassFilter: "All", qualityScoreFilter: "All"};
+	},
+
+	onQualityScoreFilter: function(param){
+		this.setState({qualityScoreFilter: param});
+		if(this.props.onFilter){
+			this.props.onFilter({dataClassFilter: this.state.dataClassFilter, qualityScoreFilter: param});
+		}
+	},
+
+	onDataClassFilter : function(param){
+		this.setState({dataClassFilter: param});
+		if(this.props.onFilter){
+			this.props.onFilter({dataClassFilter: param, qualityScoreFilter: this.state.qualityScoreFilter});
+		}
+	},
+
+	render : function(){
+		var menu = <div bsRole="menu" className={"dropdown-menu"}>
+			<QualityScoreFilter onFilter={this.onQualityScoreFilter}/>
+			<br />
+			<DataClassFilter dataClasses={this.props.dataClasses} onFilter={this.onDataClassFilter}  />
+		</div>;
+
+		return <div style={this.props.style}  >
+			<Dropdown id="filter menu" open={this.state.showMenu} onToggle={function(){}}>
+				<Button bsRole="toggle" onClick={function(){this.setState({showMenu: !this.state.showMenu})}.bind(this)}>Filter annotations</Button>
+				{menu}
+			</Dropdown>
+		</div>;
+	}
+
+});
+
+
+var SelectCheckbox = React.createClass({
+
+	getInitialState : function(){
+		return {selected : this.props.asset.isSelected};
+	},
+
+	componentWillReceiveProps : function(nextProps){
+		if(!this.isMounted()){
+			return;
+		}
+		if(nextProps.asset.reference.id != this.props.asset.reference.id){
+			this.setState({selected : nextProps.asset.isSelected});
+		}
+	},
+
+	onChange : function(selected){
+		if(this.props.onChange){
+			this.props.onChange(selected);
+		}
+		this.setState({selected : selected});
+	},
+
+	render : function(){
+		return <div><Input style={{marginTop: "-6px"}} type="checkbox" label=" " checked={this.state.selected} onChange={function(e){
+			this.onChange($(e.target).prop("checked"));
+		}.bind(this)}/></div>;
+	}
+
+});
+
+var ODFDataLakePage = React.createClass({
+
+	columnAnnotations : {},
+
+	getInitialState : function(){
+		return {
+			ajaxAborts : [],
+			sourceLoading: false,
+			columns: [],
+			dataClasses: [],
+			qualityScoreFilter: "All",
+			dataClassFilter: "All",
+			importFeedback: {msg: null, style: "primary"}
+		};
+	},
+
+	componentDidMount : function() {
+		this.loadSources();
+	},
+
+	loadSources : function(){
+		this.searchAtlasMetadata("from RelationalDataSet", function(data){
+			 $.each(data, function(key, source){
+				 source.isSelected = false;
+			  });
+			this.setState({filteredSources: data, sources: data});
+		}.bind(this));
+	},
+
+	searchAtlasMetadata : function(query, successCallback, errorCallback) {
+		var url = ODFGlobals.metadataUrl + "/search?" + $.param({query: query});
+		$.ajax({
+			url: url,
+			dataType: 'json',
+			type: 'GET',
+			success: function(data) {
+				successCallback(data);
+			},
+			error: function(xhr, status, err) {
+				console.error(url, status, err.toString());
+				var msg = "Error while loading metadata: " + err.toString();
+				if(errorCallback){
+					errorCallback(msg);
+				}
+			}
+		});
+	 },
+
+	load : function(assetRef){
+		$.each(this.state.ajaxAborts, function(key, abort){
+			if(abort && abort.call){
+				abort.call();
+			}
+		});
+		this.setState({ajaxAborts : []});
+
+		var req = AtlasHelper.loadAtlasAsset(assetRef, function(data){
+			var source = data;
+			var refresh = false;
+			if(this.state == null || this.state.selectedTable == null || this.state.selectedTable.reference.id != source.reference.id){
+				console.log("set state source " + new Date().toLocaleString());
+				this.setState({selectedTable: source});
+				if(source.annotations == null){
+					source.annotations = [];
+				}
+				if(source.columns == null){
+					source.columns = [];
+				}
+			}else{
+				source.annotations = this.state.selectedTable.annotations;
+				refresh = true;
+			}
+
+			this.loadSourceAnnotations(source, refresh);
+			this.loadColumns(source, refresh);
+		}.bind(this), function(){
+
+		});
+	},
+
+	loadSourceAnnotations : function(source, refresh){
+		if(!refresh || !source.loadedAnnotations){
+			source.loadedAnnotations = [];
+		}
+        var reqs = AtlasHelper.loadMostRecentAnnotations(source.reference, function(annotationList){
+            if (refresh) {
+            	var newAnnotations = [];
+            	if(source.loadedAnnotations.length > 0){
+            		$.each(annotationList, function(key, val){
+            			if(!this.atlasAssetArrayContains(source.loadedAnnotations, val)){
+            				newAnnotations.push(val);
+            			}
+            		}.bind(this));
+            	}else{
+            		newAnnotations = annotationList;
+            	}
+                source.loadedAnnotations = newAnnotations;
+            }else{
+            	source.loadedAnnotations = annotationList;
+            }
+            console.log("set state source anns " + new Date().toLocaleString());
+            this.setState({selectedTable: source});
+        }.bind(this), function(){
+
+        });
+
+        var ajaxAborts = [];
+		$.each(reqs, function(key, req){
+			ajaxAborts.push(req.abort);
+		}.bind(this))
+		this.setState({ajaxAborts : ajaxAborts});
+	},
+
+	atlasAssetArrayContains : function(array, obj){
+		for(var no = 0; no < array.length; no++){
+			var val = array[no];
+			if(val && val.reference && obj && obj.reference && val.reference.id == obj.reference.id){
+				return true;
+			}
+		}
+		return false;
+	},
+
+	loadColumns : function(dataSet, refresh){
+		var columns = [];
+		if(refresh){
+			columns = this.state.columns;
+		}
+		var reqs = AtlasHelper.loadRelationalDataSet(dataSet, function(result){
+			var foundAnnotations = false;
+			if(!refresh){
+				$.each(result, function(key, col){
+					if(col.annotations && col.annotations.length > 0){
+						foundAnnotations = true;
+					}
+					if(col.isSelected == null || col.isSelected == undefined){
+						col.isSelected = false;
+					}
+					columns.push(col);
+				});
+			}else{
+				//if result size is different, reset completely
+				if(result.length != columns.length){
+					columns = [];
+				}
+				//if the old array contains any column that is not in the new columns, reset completely
+				$.each(columns, function(key, col){
+					if(!this.atlasAssetArrayContains(result, col)){
+						columns = [];
+					}
+				}.bind(this));
+				$.each(result, function(key, col){
+					//only add new columns
+					if(!this.atlasAssetArrayContains(columns, col)){
+						columns.push(col);
+					}
+					if(col.annotations && col.annotations.length > 0){
+						for(var no = 0; no < columns.length; no++){
+							if(columns[no] == null || columns[no] == undefined){
+								col.isSelected = false;
+							}
+							if(columns[no].reference.id == col.reference.id){
+								columns[no].annotations = col.annotations;
+								break;
+							}
+						}
+						foundAnnotations = true;
+					}
+				}.bind(this));
+			}
+
+			if(!foundAnnotations){
+				if(!Utils.arraysEqual(this.state.columns, columns)){
+					console.log("set state columns " + new Date().toLocaleString());
+					this.setState({currentlyLoading : false, columns: columns, filteredColumns: columns});
+				}else{
+					console.log("columns same, no annotations, dont update");
+				}
+			}else{
+				this.loadColumnAnnotations(columns, refresh);
+			}
+		}.bind(this), function(){
+
+		});
+
+        var ajaxAborts = [];
+		$.each(reqs, function(key, req){
+			ajaxAborts.push(req.abort);
+		}.bind(this))
+		this.setState({ajaxAborts : ajaxAborts});
+	},
+
+	loadColumnAnnotations : function(columns, refresh){
+		var annotationRefs = [];
+		$.each(columns, function(key, col){
+			if(!refresh || !col.loadedAnnotations){
+				col.loadedAnnotations = [];
+			}
+		});
+
+		var requests = [];
+		var annotationsChanged = false;
+		var dataClasses = [];
+		$.each(columns, function(key, column){
+			var req = AtlasHelper.loadMostRecentAnnotations(column.reference, function(annotations){
+				$.each(annotations, function(key, annotation){
+					if(!this.atlasAssetArrayContains(column.loadedAnnotations, annotation)){
+						annotationsChanged = true;
+						column.loadedAnnotations.push(annotation);
+					}
+					if(annotation &&
+							annotation.inferredDataClass && dataClasses.indexOf(annotation.inferredDataClass.className) == -1){
+						dataClasses.push(annotation.inferredDataClass.className);
+					}
+				}.bind(this));
+			}.bind(this));
+			requests.push(req);
+		}.bind(this));
+
+		$.when.apply(undefined, requests).done(function(){
+			if(annotationsChanged){
+				console.log("set state column anns " + new Date().toLocaleString());
+				this.setState({currentlyLoading : false, columns: columns, filteredColumns: columns, dataClasses: dataClasses});
+			}else{
+				if(!Utils.arraysEqual(this.state.columns, columns)){
+					console.log("set state column anns " + new Date().toLocaleString());
+					this.setState({currentlyLoading : false, columns: columns, filteredColumns: columns});
+				}else{
+					console.log("columns same, annotations same, dont update");
+				}
+			}
+		}.bind(this));
+
+        var ajaxAborts = [];
+		$.each(requests, function(key, req){
+			ajaxAborts.push(req.abort);
+		}.bind(this));
+		this.setState({ajaxAborts : ajaxAborts});
+	},
+
+	storeColumnAnnotation : function(columnId, annotation){
+		if(!this.columnAnnotations[columnId]){
+			this.columnAnnotations[columnId] = [];
+		}
+		if(!this.atlasAssetArrayContains(this.columnAnnotations[columnId], annotation)){
+			this.columnAnnotations[columnId].push(annotation);
+		}
+	},
+
+	componentWillUnmount : function() {
+		if(this.refreshInterval){
+			clearInterval(this.refreshInterval);
+		}
+   	},
+
+	referenceClick : function(asset){
+		if(this.state == null || this.state.selectedTable == null || this.state.selectedTable.reference.id != asset.reference.id){
+			if(this.refreshInterval){
+				clearInterval(this.refreshInterval);
+			}
+			this.setState({currentlyLoading : true, selectedTable: null, filteredColumns : [], columns: []});
+			this.load(asset.reference);
+			this.refreshInterval = setInterval(function(){this.load(asset.reference)}.bind(this), 15000);
+		}
+	},
+
+	doFilter : function(params){
+		var columns = this.state.columns.slice();
+		var filteredColumns = this.filterOnDataQualityScore(columns, params.qualityScoreFilter);
+		filteredColumns = this.filterOnDataClass(filteredColumns, params.dataClassFilter);
+		this.setState({filteredColumns: filteredColumns});
+	},
+
+	filterOnDataQualityScore : function(columns, equation){
+		if(equation.indexOf("All")>-1){
+			return columns;
+		}
+
+		var columns = columns.slice();
+		var matchedColumns = [];
+		$.each(columns, function(index, col){
+			var match = false;
+			$.each(col.loadedAnnotations, function(k, annotation){
+				if(equation && annotation.qualityScore){
+						if(eval("annotation.qualityScore" + equation)){
+							if(matchedColumns.indexOf(col) == -1){
+								matchedColumns.push(col);
+							}
+						}
+				}
+			}.bind(this));
+		}.bind(this));
+
+		return matchedColumns;
+	},
+
+	filterOnDataClass : function(columns, key){
+		if(key == "All"){
+			return columns;
+		}
+		var matchedColumns = [];
+		$.each(columns, function(index, col){
+			var match = false;
+			$.each(col.loadedAnnotations, function(k, annotation){
+				if(annotation.inferredDataClass &&
+						annotation.inferredDataClass.className == key){
+					if(matchedColumns.indexOf(col) == -1){
+						matchedColumns.push(col);
+					}
+				}
+			});
+		});
+
+		return matchedColumns;
+	},
+
+	doImport : function(){
+		var params = {
+				jdbcString : this.refs.jdbcInput.getValue(),
+				user: this.refs.userInput.getValue(),
+				password : this.refs.passInput.getValue(),
+				database :this.refs.dbInput.getValue(),
+				schema : this.refs.schemaInput.getValue(),
+				table : this.refs.sourceInput.getValue()
+		};
+
+		this.setState({importingTable : true, tableWasImported : true, });
+
+		$.ajax({
+		      url: ODFGlobals.importUrl,
+		      contentType: "application/json",
+		      dataType: 'json',
+		      type: 'POST',
+		      data: JSON.stringify(params),
+		      success: function(data) {
+		    	  this.setState({importFeedback: {msg: "Registration successful!", style: "primary"}, importingTable: false});
+		      }.bind(this),
+		      error: function(xhr, status, err) {
+				  if(this.isMounted()){
+					var errorMsg = status;
+					if(xhr.responseJSON && xhr.responseJSON.error){
+		    				errorMsg = xhr.responseJSON.error;
+		    		  	}
+				    	var msg = "Table could not be registered: " + errorMsg + ", " + err.toString();
+			    	  	this.setState({importFeedback: {msg: msg, style: "warning"}, importingTable: false});
+				  }
+		      }.bind(this)
+		    });
+	},
+
+	closeImportingDialog : function(){
+		if(this.state.importingTable){
+			return;
+		}
+
+		var newState = {tableWasImported: false, showImportDialog : false, importFeedback: {msg: null}};
+		if(this.state.tableWasImported){
+			this.loadSources();
+			newState.sources = null;
+			newState.filteredSources = null;
+		}
+		this.setState(newState);
+	},
+
+	shopData : function(){
+		var selectedColumns = [];
+		var selectedSources = [];
+
+		$.each(this.state.columns, function(key, col){
+			if(col.isSelected){
+				selectedColumns.push(col);
+			}
+		});
+
+		$.each(this.state.sources, function(key, src){
+			if(src.isSelected){
+				selectedSources.push(src);
+			}
+		});
+
+		console.log("Do something with the selected columns!")
+		console.log(selectedColumns);
+		console.log(selectedSources);
+	},
+
+	filterSources : function(e){
+		var value = $(e.target).val();
+		var filtered = [];
+		if(value.trim() == ""){
+			filtered = this.state.sources;
+		}else{
+			$.each(this.state.sources, function(key, source){
+				if(source.name.toUpperCase().indexOf(value.toUpperCase()) > -1){
+					filtered.push(source);
+				}
+			});
+		}
+		this.setState({filteredSources : filtered});
+	},
+
+	storeImportDialogDefaults: function() {
+		var defaultValues = {
+		   "jdbcInput": this.refs.jdbcInput.getValue(),
+		   "userInput": this.refs.userInput.getValue(),
+		   "passInput": this.refs.passInput.getValue(),
+		   "dbInput": this.refs.dbInput.getValue(),
+		   "schemaInput": this.refs.schemaInput.getValue(),
+		   "sourceInput": this.refs.sourceInput.getValue(),
+		};
+		localStorage.setItem("odf-client-defaults", JSON.stringify(defaultValues) );
+	},
+
+	render : function(){
+		var columnRows = [];
+		var sourceHead = null;
+		var sourceList = null;
+		var columnsGridHeader = <thead><tr><th>Column</th><th>Datatype</th><th>Annotations</th></tr></thead>;
+		var currentlyLoadingImg = null;
+		if(this.state){
+			var sourceListContent = null;
+			if(this.state.sources){
+				var sourceSpec =  {
+
+						attributes: [
+						       {key: "isSelected", label: "",
+								func: function(val, asset){
+									return <SelectCheckbox onChange={function(selected){
+										asset.isSelected = selected;
+									}.bind(this)} asset={asset} />
+
+								}},
+								{key: "icon", label: "", func:
+						    	   function(val, asset){
+							    	   if(asset && asset.type && UISpec[asset.type] && UISpec[asset.type].icon){
+							    		   return UISpec[asset.type].icon;
+							    	   }
+							    	   return UISpec["DefaultDocument"].icon;
+						       		}
+						       },
+							   {key: "name", label: "Name"},
+			                   {key: "type", label: "Type"},
+			                   {key: "annotations", label: "Annotations",
+					        	  func: function(val){
+					        		  if(!val){
+					        			  return 0;
+					        			  }
+					        		  return val.length;
+					        		}
+			                   }
+			            ]};
+
+				sourceListContent = <ODFBrowser.ODFPagingTable rowAssets={this.state.filteredSources} onRowClick={this.referenceClick} spec={sourceSpec}/>;
+			}else{
+				sourceListContent = <Image src="img/lg_proc.gif" rounded />;
+			}
+
+			var sourceImportBtn = <Button style={{float:"right"}} onClick={function(){this.setState({showImportDialog: true});}.bind(this)}>Register new data set</Button>;
+			var sourceImportingImg = null;
+			if(this.state.importingTable){
+				sourceImportingImg = <Image src="img/lg_proc.gif" rounded />;
+			}
+
+			var importFeedback = <h3><Label style={{whiteSpace: "normal"}} bsStyle={this.state.importFeedback.style}>{this.state.importFeedback.msg}</Label></h3>
+
+			var storedDefaults = null;
+			try {
+			   storedDefaults = JSON.parse(localStorage.getItem("odf-client-defaults"));
+			} catch(e) {
+				console.log("Couldnt parse defaults from localStorage: " + e);
+				storedDefaults = {};
+			}
+			if (!storedDefaults) {
+				storedDefaults = {};
+			}
+			console.log("Stored defaults: " + storedDefaults);
+
+			var sourceImportDialog =  <Modal show={this.state.showImportDialog} onHide={this.closeImportingDialog}>
+								          <Modal.Header closeButton>
+								             <Modal.Title>Register new JDBC data set</Modal.Title>
+								          </Modal.Header>
+								          <Modal.Body>
+								          	{importFeedback}
+								            <form>
+								          	 <Input type="text" ref="jdbcInput" defaultValue={storedDefaults.jdbcInput} label="JDBC string" />
+								             <Input type="text" ref="userInput" defaultValue={storedDefaults.userInput} label="Username" />
+								             <Input type="password" ref="passInput" defaultValue={storedDefaults.passInput} label="Password" />
+								             <Input type="text" ref="dbInput" defaultValue={storedDefaults.dbInput} label="Database" />
+								             <Input type="text" ref="schemaInput" defaultValue={storedDefaults.schemaInput} label="Schema" />
+								             <Input type="text" ref="sourceInput" defaultValue={storedDefaults.sourceInput} label="Table" />
+								             </form>
+								             {sourceImportingImg}
+								         </Modal.Body>
+								         <Modal.Footer>
+								         <Button onClick={this.storeImportDialogDefaults}>Store values as defaults</Button>
+								         <Button bsStyle="primary" onClick={this.doImport}>Register</Button>
+								         <Button onClick={this.closeImportingDialog}>Close</Button>
+								         </Modal.Footer>
+									</Modal>;
+			sourceList = <Panel style={{float:"left", marginRight: 30, maxWidth:600, minHeight: 550}}>
+								{sourceImportDialog}
+								<h3 style={{float: "left", marginTop: "5px"}}>
+									Data sets
+								</h3>
+								{sourceImportBtn}<br style={{clear: "both"}}/>
+								<Input onChange={this.filterSources} addonBefore={<Glyphicon glyph="search" />} label=" " type="text" placeholder="Filter ..." />
+								<br/>
+								{sourceListContent}
+							</Panel>;
+			if(this.state.currentlyLoading){
+				currentlyLoadingImg = <Image src="img/lg_proc.gif" rounded />;
+			}
+			var panel = <div style={{float: "left"}}>{currentlyLoadingImg}</div>;
+
+			if(this.state.selectedTable){
+				var source = this.state.selectedTable;
+				var sourceAnnotations = [];
+				if(source.loadedAnnotations){
+					//reverse so newest is at front
+					var sourceAnns = source.loadedAnnotations.slice();
+					sourceAnns.reverse();
+					var processedTypes = [];
+					$.each(sourceAnns, function(key, val){
+						if(processedTypes.indexOf(val.annotationType) == -1){
+							processedTypes.push(val.annotationType);
+							var summary = (val.summary ? ", " + val.summary : "");
+							sourceAnnotations.push(<ODFAnnotationMarker key={key} annotation={val}/>);
+						}
+					});
+				}
+
+				var hasColumns = (source.columns && source.columns.length > 0 ? true : false);
+				var columnsString = (hasColumns ? "Columns: " + source.columns.length : null);
+				var annotationsFilter = (hasColumns ? <FilterMenu onFilter={this.doFilter} dataClasses={this.state.dataClasses} style={{float: "right"}} /> : null);
+
+				sourceHead = <div>
+								<h3>{source.name} </h3>
+									<div style={{}}>
+										<NewAnalysisRequestButton dataSetId={this.state.selectedTable.reference.id} />
+									</div>
+								<br/>
+								Description: {source.description}
+								<br/>
+								{columnsString}
+								<br/>Annotations:{sourceAnnotations}
+								<br/>
+								{annotationsFilter}
+								</div>;
+
+				panel = <Panel style={{float: "left", width: "50%"}} header={sourceHead}>
+							{currentlyLoadingImg}
+						</Panel>;
+			}
+			var columnsTable = null;
+			var filteredColumns = (this.state.filteredColumns ? this.state.filteredColumns : []).slice();
+
+			if(filteredColumns.length > 0){
+				var colSpec = {attributes: [{key: "isSelected", label: "Select",
+					func: function(val, col){
+						return <SelectCheckbox onChange={function(selected){
+							col.isSelected = selected;
+						}.bind(this)} asset={col} />
+
+					}},
+	               {key: "name", label: "Name", sort: true},
+		           {key: "dataType", label: "Datatype"},
+		           {key: "loadedAnnotations", label: "Annotations",
+			        	  func: function(annotations, obj){
+			        		  return <AnnotationsColumn annotations={annotations} />;
+			        	  }
+			          }]};
+				columnsTable = <div><ODFBrowser.ODFPagingTable ref="columnsTable" rowAssets={filteredColumns} assetType={"columns"} spec={colSpec}/><br/><ODFAnnotationLegend /></div>;
+				panel = (<Panel style={{float:"left", width: "50%"}} header={sourceHead}>
+							{columnsTable}
+						</Panel>);
+			}
+		}
+
+		var contentComponent = <Jumbotron>
+	      <div>
+	         <h2>Welcome to your Data Lake</h2>
+	         	<Button bsStyle="success" onClick={this.shopData}>
+	         		Shop selected data  <Glyphicon glyph="shopping-cart" />
+         		</Button>
+	         	<br/>
+	         	<br/>
+		         {sourceList}
+		         {panel}
+		        <div style={{clear: "both"}} />
+         </div>
+       </Jumbotron>;
+
+		return <div>{contentComponent}</div>;
+	}
+});
+
+var ODFTermPage = React.createClass({
+
+  getInitialState() {
+    return {terms: []};
+  },
+
+  loadTerms : function() {
+    // clear alert
+    this.props.alertCallback({type: "", message: ""});
+    var req = AtlasHelper.searchAtlasMetadata("from BusinessTerm",
+
+        function(data){
+		   	if(!this.isMounted()){
+				return;
+			}
+			this.setState({terms: data});
+        }.bind(this),
+
+        function() {
+        }.bind(this)
+    );
+  },
+
+  componentDidMount() {
+    this.loadTerms();
+  },
+
+  render: function() {
+     var terms = $.map(
+        this.state.terms,
+        function(term) {
+          return <tr style={{cursor: 'pointer'}} key={term.name} title={term.example} onClick={function(){
+        	  var win = window.open(term.originRef, '_blank');
+        	  win.focus();}
+          }>
+                  <td>
+                     {term.name}
+                  </td>
+                  <td>
+                	{term.description}
+                  </td>
+                 </tr>
+        }.bind(this)
+       );
+
+     return (
+       <div className="jumbotron">
+       <h2>Glossary</h2>
+       <br/>
+       <br/>
+       <Panel>
+       	  <h3>Terms</h3>
+          <Table>
+          	 <thead>
+          	 	<tr>
+          	 	<th>Name</th>
+          	 	<th>Description</th>
+          	 	</tr>
+          	 </thead>
+             <tbody>
+                {terms}
+             </tbody>
+          </Table>
+          </Panel>
+       </div>
+     )
+   }
+});
+
+var ODFClient = React.createClass({
+
+   componentDidMount: function() {
+     $(window).bind("hashchange", this.parseUrl);
+     this.parseUrl();
+   },
+
+   parseUrl : function(){
+    var target = constants_ODFNavBar.odfDataLakePage;
+    var navAddition = null;
+    var hash = document.location.hash;
+    if(hash && hash.length > 1){
+      hash = hash.split("#")[1];
+      var split = hash.split("/");
+      var navHash = split[0];
+      if(split.length > 0){
+        navAddition = split.slice(1);
+      }
+      if(constants_ODFNavBar[navHash]){
+        target = constants_ODFNavBar[navHash];
+      }
+    }
+    this.setState({
+      activeNavBarItem: target,
+        navAddition: navAddition}
+    );
+  },
+
+  getInitialState: function() {
+    return ({
+        activeNavBarItem: constants_ODFNavBar.odfDataLakePage,
+        navAddition: null,
+        globalAlert: {
+          type: "",
+          message: ""
+        }
+    });
+  },
+
+  handleNavBarSelection: function(selection) {
+    $.each(constants_ODFNavBar, function(key, ref){
+      if(ref == selection){
+        document.location.hash = key;
+      }
+    });
+    this.setState({ activeNavBarItem: selection });
+  },
+
+  handleAlert: function(alertInfo) {
+    this.setState({ globalAlert: alertInfo });
+  },
+
+  render: function() {
+    var alertComp = null;
+    if (this.state.globalAlert.type != "") {
+       alertComp = <Alert bsStyle={this.state.globalAlert.type}>{this.state.globalAlert.message}</Alert>;
+    }
+
+    var contentComponent = <ODFDataLakePage alertCallback={this.handleAlert}/>;
+    if (this.state.activeNavBarItem == constants_ODFNavBar.odfDataLakePage) {
+       contentComponent = <ODFDataLakePage alertCallback={this.handleAlert}/>;
+    } else if (this.state.activeNavBarItem == constants_ODFNavBar.odfTermPage) {
+       contentComponent = <ODFTermPage alertCallback={this.handleAlert}/>;
+    }
+
+    var divStyle = {
+//      marginLeft: "80px",
+//      marginRight: "80px"
+    };
+
+    return (
+        <div>
+           <ODFNavBar activeKey={this.state.activeNavBarItem} selectCallback={this.handleNavBarSelection}></ODFNavBar>
+           <div style={divStyle}>
+              {alertComp}
+              {contentComponent}
+           </div>
+        </div>
+    );
+  }
+});
+
+var div = $("#odf-toplevel-div")[0];
+ReactDOM.render(<ODFClient/>, div);

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-configuration-store.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-configuration-store.js b/odf/odf-web/src/main/webapp/scripts/odf-configuration-store.js
new file mode 100755
index 0000000..cf50075
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-configuration-store.js
@@ -0,0 +1,63 @@
+/**
+ *
+ * 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.
+ */
+var $ = require("jquery");
+var ODFGlobals = require("./odf-globals.js");
+
+var ConfigurationStore = {
+
+  // readUserDefinedProperties(successCallback, alertCallback) {
+   readConfig(successCallback, alertCallback) {
+	   if (alertCallback) {
+	     alertCallback({type: ""});
+	   }
+     // clear alert
+
+     return $.ajax({
+       url: ODFGlobals.apiPrefix + "settings",
+       dataType: 'json',
+       type: 'GET',
+       success: successCallback,
+       error: function(xhr, status, err) {
+         if (alertCallback) {
+            var msg = "Error while reading user defined properties: " + err.toString();
+            alertCallback({type: "danger", message: msg});
+         }
+       }
+      }).abort;
+   },
+
+   updateConfig(config, successCallback, alertCallback) {
+		if (alertCallback) {
+			 alertCallback({type: ""});
+		}
+
+	    return $.ajax({
+		       url: ODFGlobals.apiPrefix + "settings",
+		       contentType: "application/json",
+		       dataType: 'json',
+		       type: 'PUT',
+		       data: JSON.stringify(config),
+		       success: successCallback,
+		       error: function(xhr, status, err) {
+		         if (alertCallback) {
+		            var msg = "Error while reading user defined properties: " + err.toString();
+		            alertCallback({type: "danger", message: msg});
+		         }
+		       }
+	     }).abort;
+   }
+}
+
+module.exports = ConfigurationStore;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-console.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-console.js b/odf/odf-web/src/main/webapp/scripts/odf-console.js
new file mode 100755
index 0000000..aa70808
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-console.js
@@ -0,0 +1,967 @@
+/**
+ *
+ * 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.
+ */
+//css imports
+require("bootstrap/dist/css/bootstrap.min.css");
+require("bootstrap-material-design/dist/css/bootstrap-material-design.min.css");
+require("bootstrap-material-design/dist/css/ripples.min.css");
+require("roboto-font/css/fonts.css");
+
+
+//js imports
+var $ = require("jquery");
+var bootstrap = require("bootstrap");
+
+var React = require("react");
+var ReactDOM = require("react-dom");
+var LinkedStateMixin = require("react-addons-linked-state-mixin");
+var ReactBootstrap = require("react-bootstrap");
+
+var ODFGlobals = require("./odf-globals.js");
+var ODFStats = require("./odf-statistics.js");
+var ODFSettings = require("./odf-settings.js");
+var ODFServices = require("./odf-services.js");
+var ODFBrowser = require("./odf-metadata-browser.js").ODFMetadataBrowser;
+var ODFRequestBrowser = require("./odf-request-browser.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var configurationStore = require("./odf-utils.js").ConfigurationStore;
+var servicesStore = require("./odf-utils.js").ServicesStore;
+var AtlasHelper = require("./odf-utils.js").AtlasHelper;
+var AnnotationStoreHelper = require("./odf-utils.js").AnnotationStoreHelper;
+var OdfAnalysisRequest = require("./odf-analysis-request.js");
+var LogViewer = require("./odf-logs.js");
+//var Notifications = require("./odf-notifications.js");
+var NewAnalysisRequestButton = OdfAnalysisRequest.NewAnalysisRequestButton;
+var NewAnalysisRequestDialog = OdfAnalysisRequest.NewAnalysisRequestDialog;
+var NewCreateAnnotationsButton = OdfAnalysisRequest.NewCreateAnnotationsButton;
+var NewCreateAnnotationsDialog = OdfAnalysisRequest.NewCreateAnnotationsDialog;
+
+var Button = ReactBootstrap.Button;
+var Nav = ReactBootstrap.Nav;
+var NavItem = ReactBootstrap.NavItem;
+var Navbar = ReactBootstrap.Navbar;
+var NavDropdown = ReactBootstrap.NavDropdown;
+var MenuItem = ReactBootstrap.MenuItem;
+var Jumbotron = ReactBootstrap.Jumbotron;
+var Grid = ReactBootstrap.Grid;
+var Row = ReactBootstrap.Row;
+var Col = ReactBootstrap.Col;
+var Table = ReactBootstrap.Table;
+var Modal = ReactBootstrap.Modal;
+var Input = ReactBootstrap.Input;
+var Alert = ReactBootstrap.Alert;
+var Panel = ReactBootstrap.Panel;
+var Label = ReactBootstrap.Label;
+var Input = ReactBootstrap.Input;
+var ProgressBar = ReactBootstrap.ProgressBar;
+var Image = ReactBootstrap.Image;
+var ListGroup = ReactBootstrap.ListGroup;
+var ListGroupItem = ReactBootstrap.ListGroupItem;
+var Tabs = ReactBootstrap.Tabs;
+var Tab = ReactBootstrap.Tab;
+var Glyphicon = ReactBootstrap.Glyphicon;
+
+var PerServiceStatusGraph = ODFStats.PerServiceStatusGraph;
+var TotalAnalysisGraph = ODFStats.TotalAnalysisGraph;
+var SystemDiagnostics = ODFStats.SystemDiagnostics;
+
+////////////////////////////////////////////////////////////////
+// toplevel navigation bar
+
+const constants_ODFNavBar = {
+  gettingStarted: "navKeyGettingStarted",
+  configuration: "navKeyConfiguration",
+  monitor: "navKeyMonitor",
+  discoveryServices: "navKeyDiscoveryServices",
+  data: "navKeyData",
+  analysis: "navKeyAnalysis"
+}
+
+var ODFNavBar = React.createClass({
+   render: function() {
+       return (
+         <Navbar inverse>
+           <Navbar.Header>
+             <Navbar.Brand>
+               <b>Open Discovery Framework</b>
+             </Navbar.Brand>
+             <Navbar.Toggle />
+           </Navbar.Header>
+           <Navbar.Collapse>
+             <Nav pullRight activeKey={this.props.activeKey} onSelect={this.props.selectCallback}>
+               <NavItem eventKey={constants_ODFNavBar.gettingStarted} href="#">Getting Started</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.monitor} href="#">System Monitor</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.configuration} href="#">Settings</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.discoveryServices} href="#">Services</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.data} href="#">Data sets</NavItem>
+               <NavItem eventKey={constants_ODFNavBar.analysis} href="#">Analysis</NavItem>
+             </Nav>
+           </Navbar.Collapse>
+         </Navbar>
+       );
+   }
+});
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Configuration page
+
+var ConfigurationPage = React.createClass({
+  componentWillMount() {
+      this.props.alertCallback({type: ""});
+  },
+
+  render: function() {
+    return (
+    <div className="jumbotron">
+      <Tabs position="left" defaultActiveyKey={1}>
+        <Tab eventKey={1} title="General">
+          <ODFSettings.ODFConfigPage alertCallback={this.props.alertCallback}/>
+        </Tab>
+        <Tab eventKey={2} title="Spark settings">
+          <ODFSettings.SparkConfigPage alertCallback={this.props.alertCallback}/>
+        </Tab>
+        <Tab eventKey={3} title="User-defined">
+          <ODFSettings.UserDefinedConfigPage alertCallback={this.props.alertCallback}/>
+        </Tab>
+      </Tabs>
+      </div>
+      );
+  }
+
+});
+
+const GettingStartedPage = React.createClass({
+  getInitialState() {
+     return ({version: "NOTFOUND"});
+  },
+
+  componentWillMount() {
+     this.props.alertCallback({type: ""});
+     $.ajax({
+         url: ODFGlobals.engineUrl + "/version",
+         type: 'GET',
+         success: function(data) {
+             this.setState(data);
+         }.bind(this)
+       });
+  },
+
+  render: function() {
+    var divStyle = {
+      marginLeft: "80px",
+      marginRight: "80px"
+    };
+    return (
+      <Jumbotron>
+      <div style={divStyle}>
+         <h2>Welcome to the Open Discovery Framework Console</h2>
+         <p/>The "Open Discovery Framework" (ODF) is an open metadata-based platform
+         that strives to be a common home for different analytics technologies
+         that discover characteristics of data sets and relationships between
+         them (think "AppStore for discovery algorithms").
+         Using ODF, applications can leverage new discovery algorithms and their
+         results with minimal integration effort.
+         <p/>
+         This console lets you administer and configure your ODF system, as well as
+         run analyses and browse their results.
+         <p/>
+         <p><Button target="_blank" href="doc" bsStyle="primary">Open Documentation</Button></p>
+         <p><Button target="_blank" href="swagger" bsStyle="success">Show API Reference</Button></p>
+         <p/>
+		 Version: {this.state.version}
+         </div>
+       </Jumbotron>
+
+      )
+  }
+
+});
+
+/////////////////////////////////////////////////////////////////////
+// monitor page
+var StatusGraphs = React.createClass({
+
+	selectTab : function(key){
+		this.setState({key});
+	},
+
+	getInitialState() {
+	    return {
+	      key: "system_state"
+	    };
+	 },
+
+	render : function() {
+		var divStyle = {
+		     marginLeft: "20px"
+	    };
+
+		return (
+			<div>
+				<Tabs position="left" activeKey={this.state.key} onSelect={this.selectTab}>
+					<Tab eventKey={"system_state"} title="System state">
+						<div style={divStyle}>
+							<TotalAnalysisGraph visible={this.state.key == "system_state"} alertCallback={this.props.alertCallback}/>
+							<PerServiceStatusGraph visible={this.state.key == "system_state"} alertCallback={this.props.alertCallback}/>
+						</div>
+					</Tab>
+				    <Tab eventKey={"diagnostics"} title="Diagnostics">
+						<div style={divStyle}>
+							<SystemDiagnostics visible={this.state.key == "diagnostics"} alertCallback={this.props.alertCallback}/>
+						</div>
+					</Tab>
+					<Tab eventKey={"logs"} title="System logs">
+						<div style={divStyle}>
+							<LogViewer visible={this.state.key == "logs"} alertCallback={this.props.alertCallback}/>
+						</div>
+					</Tab>
+				</Tabs>
+			</div>
+         );
+	}
+
+
+});
+
+var MonitorPage = React.createClass({
+	mixins : [AJAXCleanupMixin],
+
+	getInitialState() {
+		return ( {
+				monitorStatusVisible: false,
+				monitorStatusStyle:"success",
+				monitorStatusMessage: "OK",
+				monitorWorkInProgress: false
+		});
+	},
+
+	componentWillMount() {
+	   this.props.alertCallback({type: ""});
+	},
+
+	checkHealth() {
+		this.setState({monitorWorkInProgress: true, monitorStatusVisible: false});
+	    var url = ODFGlobals.engineUrl + "/health";
+		var req = $.ajax({
+	         url: url,
+	         dataType: 'json',
+	         type: 'GET',
+	         success: function(data) {
+	        	 var status = data.status;
+	        	 var newState = {
+	        		monitorStatusVisible: true,
+	        		monitorWorkInProgress: false
+	        	 };
+
+	        	 if (status == "OK") {
+	        		 newState.monitorStatusStyle = "success";
+	        	 } else if (status == "WARNING") {
+	        		 newState.monitorStatusStyle = "warning";
+	        	 } else if (status == "ERROR") {
+	        		 newState.monitorStatusStyle = "danger";
+	        	 }
+	        	 // TODO show more than just the first message
+        		 newState.monitorStatusMessage = "Status: " + status + ". " + data.messages[0];
+
+	        	 this.setState(newState);
+	         }.bind(this),
+	         error: function(xhr, status, err) {
+	      	   if(this.isMounted()){
+	      		   this.setState({
+	        	   monitorStatusVisible: true,
+	        	   monitorStatusStyle:"danger",
+	        	   monitorStatusMessage: "An error occured: " + err.toString(),
+	        	   monitorWorkInProgress: false});
+	      	   };
+	         }.bind(this)
+	        });
+		this.storeAbort(req.abort);
+	},
+
+	performRestart : function(){
+		$.ajax({
+		      url: ODFGlobals.engineUrl + "/shutdown",
+		      contentType: "application/json",
+		      type: 'POST',
+		      data: JSON.stringify({restart: "true"}),
+		      success: function(data) {
+		  			this.setState({monitorStatusVisible : true, monitorStatusStyle: "info", monitorStatusMessage: "Restart in progress..."});
+		      }.bind(this),
+		      error: function(xhr, status, err) {
+		  			this.setState({monitorStatusVisible : true, monitorStatusStyle: "warning", monitorStatusMessage: "Restart request failed"});
+		      }.bind(this)
+		    });
+	},
+
+	render() {
+	  var divStyle = {
+		      marginLeft: "20px"
+		    };
+	  var monitorStatus = null;
+	  if (this.state.monitorStatusVisible) {
+		  monitorStatus = <Alert bsStyle={this.state.monitorStatusStyle}>{this.state.monitorStatusMessage}</Alert>;
+	  }
+	  var progressIndicator = null;
+	  if (this.state.monitorWorkInProgress) {
+		  progressIndicator = <Image src="img/lg_proc.gif" rounded />;
+	  }
+	  return (
+	    	<div className="jumbotron">
+	    	<h3>System health</h3>
+	    	  <div style={divStyle}>
+	           	<Button className="btn-raised" bsStyle="primary" disabled={this.state.monitorWorkInProgress} onClick={this.checkHealth}>Check health</Button>
+	           	<Button className="btn-raised" bsStyle="warning" onClick={this.performRestart}>Restart ODF</Button>
+	           	{progressIndicator}
+	           	{monitorStatus}
+	           	<hr/>
+	           	<div>
+	           	</div>
+	           	<StatusGraphs alertCallback={this.props.alertCallback}/>
+	    	  </div>
+	    	</div>
+	  );
+	}
+
+});
+
+//////////////////////////////////////////////////////
+// discovery services page
+var DiscoveryServicesPage = React.createClass({
+  mixins : [AJAXCleanupMixin],
+
+  getInitialState() {
+	  return ({discoveryServices: []});
+  },
+
+  loadDiscoveryServices() {
+	  // clear alert
+    this.props.alertCallback({type: "", message: ""});
+
+	var req = $.ajax({
+	    url: ODFGlobals.servicesUrl,
+	    dataType: 'json',
+	    type: 'GET',
+	    success: function(data) {
+	       this.setState({discoveryServices: data});
+	    }.bind(this),
+	    error: function(xhr, status, err) {
+    	   if(this.isMounted()){
+    		   var msg = "Error while reading ODF services: " + err.toString();
+    		   this.props.alertCallback({type: "danger", message: msg});
+    	   }
+	    }.bind(this)
+	  });
+
+	this.storeAbort(req.abort);
+  },
+
+  componentDidMount() {
+	  this.loadDiscoveryServices();
+  },
+
+  render: function() {
+	var services = $.map(
+        this.state.discoveryServices,
+        function(dsreg) {
+          return <tr key={dsreg.id}>
+                  <td>
+                     <ODFServices.DiscoveryServiceInfo dsreg={dsreg} refreshCallback={this.loadDiscoveryServices} alertCallback={this.props.alertCallback}/>
+                  </td>
+                 </tr>
+        }.bind(this)
+    );
+
+	return (
+	     <div className="jumbotron">
+           <h3>Services</h3>
+           This page lets you manage the services for this ODF instance.
+           You can add services manually by clicking the <em>Add Service</em> button or
+           register remote services (e.g. deployed on Bluemix) you have built with the ODF service developer kit by
+           clicking the <em>Register remote services</em> link.
+           <p/>
+					 <ODFServices.AddDiscoveryServiceButton refreshCallback={this.loadDiscoveryServices}/>
+           <p/>
+	       	<Table bordered responsive>
+	         <tbody>
+	         {services}
+             </tbody>
+          </Table>
+	     </div>
+	);
+  }
+
+});
+
+//////////////////////////////////////////////////////////////
+// Analysis Page
+var AnalysisRequestsPage = React.createClass({
+  mixins : [AJAXCleanupMixin],
+
+  getInitialState() {
+      return {recentAnalysisRequests: null, config: {}, services : []};
+  },
+
+  componentWillReceiveProps : function(nextProps){
+  	var selection = null;
+	if(nextProps.navAddition && nextProps.navAddition.length > 0 && nextProps.navAddition[0] && nextProps.navAddition[0].length > 0){
+		var jsonAddition = {};
+
+		try{
+			jsonAddition = JSON.parse(decodeURIComponent(nextProps.navAddition[0]));
+		}catch(e){
+
+		}
+
+		if(jsonAddition.requestId){
+			$.each(this.state.recentAnalysisRequests, function(key, tracker){
+				var reqId = jsonAddition.requestId;
+
+				if(tracker.request.id == reqId){
+					selection = reqId;
+				}
+			}.bind(this));
+		}else if(jsonAddition.id && jsonAddition.repositoryId){
+			selection = jsonAddition;
+		}
+	}
+
+	if(selection != this.state.selection){
+		this.setState({selection : selection});
+	}
+  },
+
+  componentDidMount() {
+	  if(!this.refreshInterval){
+		  this.refreshInterval = window.setInterval(this.refreshAnalysisRequests, 5000);
+	  }
+      this.initialLoadServices();
+      this.initialLoadRecentAnalysisRequests();
+  },
+
+  componentWillUnmount : function() {
+	  if(this.refreshInterval){
+		  window.clearInterval(this.refreshInterval);
+	  }
+  },
+
+  getDiscoveryServiceNameFromId(id) {
+      var servicesWithSameId = this.state.services.filter(
+         function(dsreg) {
+             return dsreg.id == id;
+         }
+      );
+      if (servicesWithSameId.length > 0) {
+        return servicesWithSameId[0].name;
+      }
+      return null;
+  },
+
+  refreshAnalysisRequests : function(){
+	  var req = configurationStore.readConfig(
+		      function(config) {
+		          this.setState({config: config});
+		          const url = ODFGlobals.analysisUrl + "?offset=0&limit=20";
+		          $.ajax({
+		            url: url,
+		            dataType: 'json',
+		            type: 'GET',
+		            success: function(data) {
+		            	$.each(data.analysisRequestTrackers, function(key, tracker){
+		                	//collect service names by id and add to json so that it can be displayed later
+		            		$.each(tracker.discoveryServiceRequests, function(key, request){
+			            		var serviceName = this.getDiscoveryServiceNameFromId(request.discoveryServiceId);
+			            		request.discoveryServiceName = serviceName;
+		            		}.bind(this));
+		            	}.bind(this));
+		                this.setState({recentAnalysisRequests: data.analysisRequestTrackers});
+		            }.bind(this),
+		            error: function(xhr, status, err) {
+		            	if(status != "abort" ){
+		            		console.error(url, status, err.toString());
+		            	}
+		            	if(this.isMounted()){
+		            	  var msg = "Error while refreshing recent analysis requests: " + err.toString();
+		            	  this.props.alertCallback({type: "danger", message: msg});
+		            	}
+		            }.bind(this)
+		          });
+		      }.bind(this),
+	      this.props.alertCallback
+	    );
+
+	    this.storeAbort(req.abort);
+  },
+
+  initialLoadServices() {
+	this.setState({services: null});
+
+    var req = servicesStore.getServices(
+      function(services) {
+          this.setState({services: services});
+      }.bind(this),
+      this.props.alertCallback
+    );
+
+    this.storeAbort(req.abort);
+  },
+
+  initialLoadRecentAnalysisRequests() {
+	this.setState({recentAnalysisRequests: null});
+
+    var req = configurationStore.readConfig(
+      function(config) {
+          this.setState({config: config});
+          const url = ODFGlobals.analysisUrl + "?offset=0&limit=20";
+          $.ajax({
+            url: url,
+            dataType: 'json',
+            type: 'GET',
+            success: function(data) {
+            	var selection = null;
+            	$.each(data.analysisRequestTrackers, function(key, tracker){
+            		if(this.props.navAddition && this.props.navAddition.length > 0 && this.props.navAddition[0].length > 0){
+            			var reqId = "";
+            			try{
+            				reqId = JSON.parse(decodeURIComponent(this.props.navAddition[0])).requestId
+            			}catch(e){
+
+            			}
+            			if(tracker.request.id == reqId){
+            				selection = reqId;
+            			}
+        			}
+
+                	//collect service names by id and add to json so that it can be displayed later
+            		$.each(tracker.discoveryServiceRequests, function(key, request){
+	            		var serviceName = this.getDiscoveryServiceNameFromId(request.discoveryServiceId);
+	            		request.discoveryServiceName = serviceName;
+            		}.bind(this));
+            	}.bind(this));
+
+            	var newState = {recentAnalysisRequests: data.analysisRequestTrackers};
+            	if(selection){
+            		newState.selection = selection;
+            	}
+
+               this.setState(newState);
+            }.bind(this),
+            error: function(xhr, status, err) {
+            	if(status != "abort" ){
+            		console.error(url, status, err.toString());
+            	}
+            	if(this.isMounted()){
+            	  var msg = "Error while loading recent analysis requests: " + err.toString();
+            	  this.props.alertCallback({type: "danger", message: msg});
+            	}
+            }.bind(this)
+          });
+      }.bind(this),
+      this.props.alertCallback
+    );
+
+    this.storeAbort(req.abort);
+  },
+
+  cancelAnalysisRequest(tracker) {
+      var url = ODFGlobals.analysisUrl + "/" + tracker.request.id + "/cancel";
+
+      $.ajax({
+          url: url,
+          type: 'POST',
+          success: function() {
+			  if(this.isMounted()){
+				  this.refreshAnalysisRequests();
+			  }
+          }.bind(this),
+          error: function(xhr, status, err) {
+        	  if(status != "abort" ){
+          		console.error(url, status, err.toString());
+        	  }
+
+        	  var errMsg = null;
+        	  if(err == "Forbidden"){
+        		  errMsg = "only analyses that have not been started yet can be cancelled!";
+        	  }else if(err == "Bad Request"){
+        		  errMsg = "the requested analysis could not be found!";
+        	  }
+        	  if(this.isMounted()){
+				  var msg = "Analysis could not be cancelled: " + (errMsg ? errMsg : err.toString());
+				  if(this.props.alertCallback){
+					  this.props.alertCallback({type: "danger", message: msg});
+				  }
+        	  }
+          }.bind(this)
+      });
+  },
+
+  viewResultAnnotations : function(target){
+	  this.setState({
+			resultAnnotations : null,
+			showAnnotations: true
+		});
+	  var req = AnnotationStoreHelper.loadAnnotationsForRequest(target.request.id,
+			function(data){
+				this.setState({
+					resultAnnotations : data.annotations
+				});
+			}.bind(this),
+			function(error){
+				console.error('Annotations could not be loaded ' + error);
+			}
+		);
+	  this.storeAbort(req.abort);
+  },
+
+  viewInAtlas : function(target){
+	  var repo =  target.request.dataSets[0].repositoryId;
+	  repo = repo.split("atlas:")[1];
+      var annotationQueryUrl = repo + "/#!/search?query=from%20ODFAnnotation%20where%20analysisRun%3D'"+ target.request.id + "'";
+	  var win = window.open(annotationQueryUrl, '_blank');
+  },
+
+  render : function() {
+    var loadingImg = null;
+    if(this.state.recentAnalysisRequests == null){
+    	loadingImg = <Image src="img/lg_proc.gif" rounded />;
+    }
+    var requestActions = [
+                           {
+                        	   assetType: ["requests"],
+                        	   actions : [
+                	              {
+                	            	  label: "Cancel analysis",
+                	            	  func: this.cancelAnalysisRequest,
+                	            	  filter: function(obj){
+                	            		  var val = obj.status;
+                	            		  if (val == "INITIALIZED" || val == "IN_DISCOVERY_SERVICE_QUEUE") {
+                	            			  return true;
+                	            		  }
+                	            		  return false;
+                	            	  }
+                	              },
+                	              {
+                	            	  label: "View results",
+                	            	  func: this.viewResultAnnotations
+                	              },
+                	              {
+                	            	  label: "View results in atlas",
+                	            	  func: this.viewInAtlas
+                	              }
+                	           ]
+                           	}
+                           ];
+    return (
+    		<div className="jumbotron">
+			   <h3>Analysis requests</h3>
+			   <div>
+		        Click Refresh to refresh the list of existing analysis requests.
+		        Only the last 20 valid requests are shown.
+		         <p/>
+		        <NewAnalysisRequestButton bsStyle="primary" onClose={this.refreshAnalysisRequests} alertCallback={this.props.alertCallback}/>
+		        <NewCreateAnnotationsButton bsStyle="primary" onClose={this.refreshAnalysisRequests} alertCallback={this.props.alertCallback}/>
+
+		        <Button bsStyle="success" onClick={this.refreshAnalysisRequests}>Refresh</Button> &nbsp;
+            	{loadingImg}
+		        <ODFRequestBrowser registeredServices={this.state.config.registeredServices} actions={requestActions} ref="requestBrowser" selection={this.state.selection} assets={this.state.recentAnalysisRequests}/>
+		       </div>
+            	<Modal show={this.state.showAnnotations} onHide={function(){this.setState({showAnnotations : false})}.bind(this)}>
+	            	<Modal.Header closeButton>
+	                	<Modal.Title>Analysis results for analysis {this.state.resultTarget}</Modal.Title>
+		             </Modal.Header>
+		             <Modal.Body>
+		             	<ODFBrowser ref="resultBrowser" type={"annotations"} assets={this.state.resultAnnotations} />
+		             </Modal.Body>
+		             <Modal.Footer>
+		            <Button onClick={function(){this.setState({showAnnotations : false})}.bind(this)}>Close</Button>
+		            </Modal.Footer>
+            	</Modal>
+		    </div>
+    );
+  }
+
+});
+
+var AnalysisDataSetsPage = React.createClass({
+  mixins : [AJAXCleanupMixin],
+
+  componentDidMount() {
+      this.loadDataFiles();
+      this.loadTables();
+      this.loadDocuments();
+  },
+
+  getInitialState() {
+      return ({	showDataFiles: true,
+    	  		showHideDataFilesIcon: "chevron-up",
+    	  		showTables: true,
+    	  		showHideTablesIcon: "chevron-up",
+    	  		showDocuments: true,
+    	  		showHideDocumentsIcon: "chevron-up",
+    	  		config: null});
+  },
+
+  componentWillReceiveProps : function(nextProps){
+	if(nextProps.navAddition && nextProps.navAddition.length > 0 && nextProps.navAddition[0]){
+		this.setState({selection : nextProps.navAddition[0]});
+	}else{
+		this.setState({selection : null});
+	}
+  },
+
+  showHideDataFiles() {
+	  this.setState({showDataFiles: !this.state.showDataFiles, showHideDataFilesIcon: (!this.state.showDataFiles? "chevron-up" : "chevron-down")});
+  },
+
+  showHideTables() {
+	  this.setState({showTables: !this.state.showTables, showHideTablesIcon: (!this.state.showTables? "chevron-up" : "chevron-down")});
+  },
+
+  showHideDocuments() {
+	  this.setState({showDocuments: !this.state.showDocuments, showHideDocumentsIcon: (!this.state.showDocuments ? "chevron-up" : "chevron-down")});
+  },
+
+  createAnnotations : function(target){
+		this.setState({showCreateAnnotationsDialog: true, selectedAsset : target.reference.id});
+  },
+
+  startAnalysis : function(target){
+		this.setState({showAnalysisRequestDialog: true, selectedAsset : target.reference.id});
+  },
+
+  viewInAtlas : function(target){
+	  var win = window.open(target.reference.url, '_blank');
+	  win.focus();
+  },
+
+  loadDataFiles : function(){
+	  var  resultQuery = "from DataFile";
+	  this.setState({
+			dataFileAssets : null
+	  });
+	  var req = AtlasHelper.searchAtlasMetadata(resultQuery,
+			function(data){
+				this.setState({
+					dataFileAssets : data
+				});
+			}.bind(this),
+			function(error){
+
+			}
+		);
+	  this.storeAbort(req.abort);
+  },
+
+  loadTables : function(){
+	  var  resultQuery = "from Table";
+	  this.setState({
+			tableAssets : null
+	  });
+	  var req = AtlasHelper.searchAtlasMetadata(resultQuery,
+			function(data){
+				this.setState({
+					tableAssets : data
+				});
+			}.bind(this),
+			function(error){
+
+			}
+		);
+	  this.storeAbort(req.abort);
+  },
+
+  loadDocuments : function(){
+	  var  resultQuery = "from Document";
+	  this.setState({
+			docAssets : null
+	  });
+	  var req = AtlasHelper.searchAtlasMetadata(resultQuery,
+			function(data){
+				this.setState({
+					docAssets : data
+				});
+			}.bind(this),
+			function(error){
+
+			}
+		);
+	  this.storeAbort(req.abort);
+  },
+
+  render() {
+    var actions = [
+             {
+        	   assetType: ["DataFiles", "Tables", "Documents"],
+        	   actions : [
+	              {
+	            	  label: "Start analysis (annotation types)",
+	            	  func: this.createAnnotations
+	              } ,
+	              {
+	            	  label: "Start analysis (service sequence)",
+	            	  func: this.startAnalysis
+	              } ,
+	              {
+	            	  label: "View in atlas",
+	            	  func: this.viewInAtlas
+	              }
+        	    ]
+	         }
+	     ];
+
+    return (
+    		<div className="jumbotron">
+    		   <h3>Data sets</h3>
+		       <div>
+		       	 <NewAnalysisRequestDialog alertCallback={this.props.alertCallback} dataSetId={this.state.selectedAsset} show={this.state.showAnalysisRequestDialog} onClose={function(){this.setState({showAnalysisRequestDialog: false});}.bind(this)} />
+		       	 <NewCreateAnnotationsDialog alertCallback={this.props.alertCallback} dataSetId={this.state.selectedAsset} show={this.state.showCreateAnnotationsDialog} onClose={function(){this.setState({showCreateAnnotationsDialog: false});}.bind(this)} />
+		         Here are all data sets of the metadata repository that are available for analysis.
+		         <p/>
+		         <Panel collapsible expanded={this.state.showDataFiles} header={
+		        		 <div style={{textAlign:"right"}}>
+				         	<span style={{float: "left"}}>Data Files</span>
+				         	<Button bsStyle="primary" onClick={function(){this.loadDataFiles();}.bind(this)}>
+				         		Refresh
+				         	</Button>
+			         		<Button onClick={this.showHideDataFiles}>
+			         			<Glyphicon glyph={this.state.showHideDataFilesIcon} />
+			         		</Button>
+			         	</div>}>
+		            	<ODFBrowser ref="dataFileBrowser" type={"DataFiles"} selection={this.state.selection} actions={actions} assets={this.state.dataFileAssets} />
+		         </Panel>
+		         <Panel collapsible expanded={this.state.showTables} header={
+		        		 <div style={{textAlign:"right"}}>
+				         	<span style={{float: "left"}}>Relational Tables</span>
+				         	<Button bsStyle="primary" onClick={function(){this.loadTables();}.bind(this)}>
+				         		Refresh
+				         	</Button>
+			         		<Button onClick={this.showHideTables}>
+			         			<Glyphicon glyph={this.state.showHideTablesIcon} />
+			         		</Button>
+			         	</div>}>
+		            	<ODFBrowser ref="tableBrowser" type={"Tables"} actions={actions} assets={this.state.tableAssets} />
+		         </Panel>
+		         <Panel collapsible expanded={this.state.showDocuments}  header={
+		        		 <div style={{textAlign:"right"}}>
+		        		 	<span style={{float: "left"}}>Documents</span>
+		        		 	<Button bsStyle="primary" onClick={function(){this.loadDocuments();}.bind(this)}>
+		        		 		Refresh
+		        		 	</Button>
+		        		 	<Button onClick={this.showHideDocuments}>
+			         			<Glyphicon glyph={this.state.showHideDocumentsIcon} />
+			         		</Button>
+			         	</div>}>
+		     			<ODFBrowser ref="docBrowser" type={"Documents"} actions={actions} assets={this.state.docAssets}/>
+		         </Panel>
+		       </div>
+		    </div>
+		     );
+  }
+
+});
+
+
+////////////////////////////////////////////////////////////////////////
+// main component
+var ODFUI = React.createClass({
+
+   componentDidMount: function() {
+	   $(window).bind("hashchange", this.parseUrl);
+	   this.parseUrl();
+   },
+
+   parseUrl : function(){
+	  var target = constants_ODFNavBar.gettingStarted;
+	  var navAddition = null;
+	  var hash = document.location.hash;
+	  if(hash && hash.length > 1){
+		  hash = hash.split("#")[1];
+		  var split = hash.split("/");
+		  var navHash = split[0];
+		  if(split.length > 0){
+			  navAddition = split.slice(1);
+		  }
+		  if(constants_ODFNavBar[navHash]){
+			  target = constants_ODFNavBar[navHash];
+		  }
+	  }
+	  this.setState({
+		  activeNavBarItem: target,
+	      navAddition: navAddition}
+	  );
+  },
+
+  getInitialState: function() {
+	  return ({
+	      activeNavBarItem: constants_ODFNavBar.gettingStarted,
+	      navAddition: null,
+	      globalAlert: {
+	        type: "",
+	        message: ""
+	      }
+	  });
+  },
+
+  handleNavBarSelection: function(selection) {
+	  $.each(constants_ODFNavBar, function(key, ref){
+		  if(ref == selection){
+			  document.location.hash = key;
+		  }
+	  });
+    this.setState({ activeNavBarItem: selection });
+  },
+
+  handleAlert: function(alertInfo) {
+    this.setState({ globalAlert: alertInfo });
+  },
+
+  render: function() {
+    var alertComp = null;
+    if (this.state.globalAlert.type != "") {
+       alertComp = <Alert bsStyle={this.state.globalAlert.type}>{this.state.globalAlert.message}</Alert>;
+    }
+
+    var contentComponent = <GettingStartedPage alertCallback={this.handleAlert}/>;
+    if (this.state.activeNavBarItem == constants_ODFNavBar.configuration) {
+       contentComponent = <ConfigurationPage alertCallback={this.handleAlert}/>;
+    } else if (this.state.activeNavBarItem == constants_ODFNavBar.discoveryServices) {
+       contentComponent = <DiscoveryServicesPage alertCallback={this.handleAlert}/>;
+    } else if (this.state.activeNavBarItem == constants_ODFNavBar.monitor) {
+       contentComponent = <MonitorPage alertCallback={this.handleAlert}/>;
+    } else if (this.state.activeNavBarItem == constants_ODFNavBar.analysis) {
+       contentComponent = <AnalysisRequestsPage navAddition={this.state.navAddition} alertCallback={this.handleAlert}/>;
+    } else if (this.state.activeNavBarItem == constants_ODFNavBar.data) {
+       contentComponent = <AnalysisDataSetsPage navAddition={this.state.navAddition} alertCallback={this.handleAlert}/>;
+    }
+
+    var divStyle = {
+      marginLeft: "80px",
+      marginRight: "80px"
+    };
+
+    return (
+        <div>
+           <ODFNavBar activeKey={this.state.activeNavBarItem} selectCallback={this.handleNavBarSelection}></ODFNavBar>
+           <div style={divStyle}>
+              {alertComp}
+              {contentComponent}
+           </div>
+        </div>
+    );
+  }
+});
+
+var div = $("#odf-toplevel-div")[0];
+ReactDOM.render(<ODFUI/>, div);

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-globals.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-globals.js b/odf/odf-web/src/main/webapp/scripts/odf-globals.js
new file mode 100755
index 0000000..d67a2d3
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-globals.js
@@ -0,0 +1,54 @@
+/**
+ *
+ * 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.
+ */
+const CONTEXT_ROOT = ""; // window.location.origin + "/" + (window.location.pathname.split("/")[1].length > 0 ? window.location.pathname.split("/")[1] + "/" : "");
+const API_PREFIX = CONTEXT_ROOT + API_PATH;
+const SERVICES_URL = API_PREFIX + "services";
+const ANALYSIS_URL = API_PREFIX + "analyses";
+const ENGINE_URL = API_PREFIX + "engine";
+const CONFIG_URL = API_PREFIX + "config";
+const METADATA_URL = API_PREFIX + "metadata";
+const IMPORT_URL = API_PREFIX + "import";
+const ANNOTATIONS_URL = API_PREFIX + "annotations";
+
+var OdfUrls = {
+	"contextRoot": CONTEXT_ROOT,
+	"apiPrefix": API_PREFIX,
+	"servicesUrl": SERVICES_URL,
+	"analysisUrl": ANALYSIS_URL,
+	"engineUrl": ENGINE_URL,
+	"configUrl": CONFIG_URL,
+	"metadataUrl": METADATA_URL,
+	"importUrl": IMPORT_URL,
+	"annotationsUrl": ANNOTATIONS_URL,
+
+	getPathValue: function(obj, path) {
+	    var value = obj;
+        $.each(path.split("."),
+            function(propKey, prop) {
+               // if value is null, do nothing
+               if (value) {
+                   if(value[prop] != null && value[prop] != undefined){
+                       value = value[prop];
+                   } else {
+                       value = null;
+                   }
+               }
+           }
+        );
+        return value;
+	}
+};
+
+module.exports = OdfUrls;

http://git-wip-us.apache.org/repos/asf/incubator-atlas/blob/6d19e129/odf/odf-web/src/main/webapp/scripts/odf-logs.js
----------------------------------------------------------------------
diff --git a/odf/odf-web/src/main/webapp/scripts/odf-logs.js b/odf/odf-web/src/main/webapp/scripts/odf-logs.js
new file mode 100755
index 0000000..ecca602
--- /dev/null
+++ b/odf/odf-web/src/main/webapp/scripts/odf-logs.js
@@ -0,0 +1,83 @@
+/**
+ *
+ * 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.
+ */
+var $ = require("jquery");
+var React = require("react");
+var ReactDOM = require("react-dom");
+var d3 = require("d3");
+var ReactBootstrap = require("react-bootstrap");
+var ReactD3 = require("react-d3-components");
+var ODFGlobals = require("./odf-globals.js");
+var AJAXCleanupMixin = require("./odf-mixins.js");
+var Input = ReactBootstrap.Input;
+
+var REFRESH_DELAY = 5000;
+
+var ODFLogViewer = React.createClass({
+	mixins : [AJAXCleanupMixin],
+
+	getInitialState : function(){
+		return {logLevel : "ALL", log : ""};
+	},
+
+	getLogs : function() {
+		const url = ODFGlobals.engineUrl + "/log?numberOfLogs=50&logLevel=" + this.state.logLevel;
+        var req = $.ajax({
+            url: url,
+            contentType: "text/plain",
+            type: 'GET',
+            success: function(data) {
+               this.setState({log: data});
+            }.bind(this),
+            error: function(xhr, status, err) {
+              var msg = "ODF log request failed, " + err.toString();
+              this.props.alertCallback({type: "danger", message: msg});
+            }.bind(this)
+        });
+
+        this.storeAbort(req.abort);
+	},
+
+	componentWillMount : function() {
+		this.getLogs();
+	},
+
+	componentWillUnmount () {
+	    this.refreshInterval && clearInterval(this.refreshInterval);
+	    this.refreshInterval = false;
+	},
+
+	componentWillReceiveProps: function(nextProps){
+		if(!nextProps.visible){
+			 this.refreshInterval && clearInterval(this.refreshInterval);
+			 this.refreshInterval = false;
+		}else if(!this.refreshInterval){
+			this.refreshInterval = window.setInterval(this.getLogs, REFRESH_DELAY);
+		}
+	},
+	render : function(){
+		return (<div>
+					<h4>ODF system logs</h4>
+					<h5>(This only works for the node this web application is running on, logs from other ODF nodes in a clustered environment will not be displayed)</h5>
+					<Input label="Log level:" type="select" onChange={(el) => {this.setState({logLevel : el.target.value}); this.getLogs()}} value={this.state.logLevel}>
+					<option value="ALL">ALL</option>
+					<option value="FINE">FINE</option>
+					<option value="INFO">INFO</option>
+					<option value="WARNING">WARNING</option>
+				</Input>
+				<textarea disabled style={{width: '100%', height: '700px'}} value={this.state.log} /></div>);
+	}
+});
+
+module.exports = ODFLogViewer;


Mime
View raw message