eagle-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From qingwz...@apache.org
Subject [2/5] incubator-eagle git commit: EAGLE-271 Topology management in remote/local mode including start/stop operations
Date Mon, 25 Apr 2016 04:01:56 GMT
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
new file mode 100644
index 0000000..776edf7
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+(function() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("topology");
+
+	// ==============================================================
+	// =                       Initialization                       =
+	// ==============================================================
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+	//feature.service("DashboardFormatter", function() {
+	//});
+
+	// ==============================================================
+	// =                         Controller                         =
+	// ==============================================================
+	feature.configNavItem("monitoring", "Topology", "usb");
+
+	// ========================= Monitoring =========================
+	feature.configController('monitoring', function(PageConfig, $scope, $interval, Entities,
UI, Site, Application) {
+		var topologyRefreshInterval;
+
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		PageConfig.pageTitle = "Topology Execution";
+
+		$scope.topologyExecutionList = null;
+
+		$scope.currentTopologyExecution = null;
+		$scope.currentTopologyExecutionOptList = [];
+
+		// ======================= Function =======================
+		function refreshExecutionList() {
+			var _list = Entities.queryEntities("TopologyExecutionService");
+			_list._promise.then(function () {
+				$scope.topologyExecutionList = _list;
+			});
+		}
+
+		$scope.showTopologyDetail = function (topologyExecution) {
+			$scope.currentTopologyExecution = topologyExecution;
+			$("#topologyMDL").modal();
+
+			$scope.currentTopologyExecutionOptList = Entities.queryEntities("TopologyOperationService",
{
+				site: topologyExecution.tags.site,
+				application: topologyExecution.tags.application,
+				topology: topologyExecution.tags.topology,
+				_pageSize: 10,
+				_duration: 1000 * 60 * 60 * 24 * 30
+			});
+		};
+
+		$scope.getStatusClass = function (status) {
+			switch (status) {
+				case "NEW":
+					return "info";
+				case "STARTING":
+				case "STOPPING":
+					return "warning";
+				case "STARTED":
+					return "success";
+				case "STOPPED":
+					return "danger";
+				default:
+					return "default";
+			}
+		};
+
+		// ==================== Initialization ====================
+		refreshExecutionList();
+		topologyRefreshInterval = $interval(refreshExecutionList, 10 * 1000);
+
+		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
+		$scope.topologyList._promise.then(function () {
+			$scope.topologyList = $.map($scope.topologyList, function (topology) {
+				return topology.tags.topology;
+			});
+		});
+
+		$scope.applicationList = $.map(Application.list, function (application) {
+			return application.tags.application;
+		});
+
+		$scope.siteList = $.map(Site.list, function (site) {
+			return site.tags.site;
+		});
+
+		// ================== Topology Execution ==================
+		$scope.newTopologyExecution = function () {
+			UI.createConfirm("Topology", {}, [
+				{field: "site", type: "select", valueList: $scope.siteList},
+				{field: "application", type: "select", valueList: $scope.applicationList},
+				{field: "topology", type: "select", valueList: $scope.topologyList}
+			], function (entity) {
+				for(var i = 0 ; i < $scope.topologyExecutionList.length; i += 1) {
+					var _entity = $scope.topologyExecutionList[i].tags;
+					if(_entity.site === entity.site && _entity.application === entity.application
&& _entity.topology === entity.topology) {
+						return "Topology already exist!";
+					}
+				}
+			}).then(null, null, function(holder) {
+				var _entity = {
+					tags: {
+						site: holder.entity.site,
+						application: holder.entity.application,
+						topology: holder.entity.topology
+					},
+					status: "NEW"
+				};
+				Entities.updateEntity("TopologyExecutionService", _entity)._promise.then(function() {
+					holder.closeFunc();
+					$scope.topologyExecutionList.push(_entity);
+					refreshExecutionList();
+				});
+			});
+		};
+
+		$scope.deleteTopologyExecution = function (topologyExecution) {
+			UI.deleteConfirm(topologyExecution.tags.topology).then(null, null, function(holder) {
+				Entities.deleteEntities("TopologyExecutionService", topologyExecution.tags)._promise.then(function()
{
+					holder.closeFunc();
+					common.array.remove(topologyExecution, $scope.topologyExecutionList);
+				});
+			});
+		};
+
+		// ================== Topology Operation ==================
+		$scope.doTopologyOperation = function (topologyExecution, operation) {
+			$.dialog({
+				title: operation + " Confirm",
+				content: "Do you want to " + operation + " '" + topologyExecution.tags.topology + "'?",
+				confirm: true
+			}, function (ret) {
+				if(!ret) return;
+
+				var list = Entities.queryEntities("TopologyOperationService", {
+					site: topologyExecution.tags.site,
+					application: topologyExecution.tags.application,
+					topology: topologyExecution.tags.topology,
+					_pageSize: 20
+				});
+
+				list._promise.then(function () {
+					var lastOperation = common.array.find(operation, list, "tags.operation");
+					if(lastOperation && (lastOperation.status === "INITIALIZED" || lastOperation.status
=== "PENDING")) {
+						refreshExecutionList();
+						return;
+					}
+
+					Entities.updateEntity("rest/app/operation", {
+						tags: {
+							site: topologyExecution.tags.site,
+							application: topologyExecution.tags.application,
+							topology: topologyExecution.tags.topology,
+							operation: operation
+						},
+						status: "INITIALIZED"
+					}, {timestamp: false, hook: true});
+				});
+			});
+		};
+
+		$scope.startTopologyOperation = function (topologyExecution) {
+			$scope.doTopologyOperation(topologyExecution, "START");
+		};
+		$scope.stopTopologyOperation = function (topologyExecution) {
+			$scope.doTopologyOperation(topologyExecution, "STOP");
+		};
+
+		// ======================= Clean Up =======================
+		$scope.$on('$destroy', function() {
+			$interval.cancel(topologyRefreshInterval);
+		});
+	});
+
+	// ========================= Management =========================
+	feature.configController('management', function(PageConfig, $scope, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		PageConfig.pageTitle = "Topology";
+
+		var typeList = ["CLASS", "DYNAMIC"];
+		var topologyDefineAttrs = [
+			{field: "topology", name: "name"},
+			{field: "type", type: "select", valueList: typeList},
+			{field: "exeClass", name: "execution entry", description: function (entity) {
+				if(entity.type === "CLASS") return "Class implements interface TopologyExecutable";
+				if(entity.type === "DYNAMIC") return "DSL based topology definition";
+			}, type: "blob", rows: 5},
+			{field: "version", optional: true},
+			{field: "description", optional: true, type: "blob"}
+		];
+		var topologyUpdateAttrs = $.extend(topologyDefineAttrs.concat(), [{field: "topology", name:
"name", readonly: true}]);
+
+		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
+
+		$scope.newTopology = function () {
+			UI.createConfirm("Topology", {}, topologyDefineAttrs, function (entity) {
+				if(common.array.find(entity.topology, $scope.topologyList, "tags.topology", false, false))
{
+					return "Topology name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				holder.entity.tags = {
+					topology: holder.entity.topology
+				};
+				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function()
{
+					holder.closeFunc();
+					$scope.topologyList.push(holder.entity);
+				});
+			});
+		};
+
+		$scope.updateTopology = function (topology) {
+			UI.updateConfirm("Topology", $.extend({}, topology, {topology: topology.tags.topology}),
topologyUpdateAttrs).then(null, null, function(holder) {
+				holder.entity.tags = {
+					topology: holder.entity.topology
+				};
+				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function()
{
+					holder.closeFunc();
+					$.extend(topology, holder.entity);
+				});
+			});
+		};
+
+		$scope.deleteTopology = function (topology) {
+			UI.deleteConfirm(topology.tags.topology).then(null, null, function(holder) {
+				Entities.deleteEntities("TopologyDescriptionService", {topology: topology.tags.topology})._promise.then(function()
{
+					holder.closeFunc();
+					common.array.remove(topology, $scope.topologyList);
+				});
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
new file mode 100644
index 0000000..9e22c84
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
@@ -0,0 +1,52 @@
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-cog"></i>
+		<a class="pull-right" href="#/config/topology/monitoring"><span class="fa fa-angle-right"></span>
Monitoring</a>
+		<h3 class="box-title">
+			Management
+		</h3>
+	</div>
+	<div class="box-body">
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th>Name</th>
+					<th width="20%">Execution Class</th>
+					<th>Type</th>
+					<th width="50%">Description</th>
+					<th>Version</th>
+					<th width="70"></th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr ng-repeat="item in topologyList">
+					<td>{{item.tags.topology}}</td>
+					<td><pre class="noWrap">{{item.exeClass}}</pre></td>
+					<td>{{item.type}}</td>
+					<td>{{item.description}}</td>
+					<td>{{item.version}}</td>
+					<td class="text-center">
+						<button class="rm fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false"
ng-click="updateTopology(item)"> </button>
+						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false"
ng-click="deleteTopology(item)"> </button>
+					</td>
+				</tr>
+				<tr ng-if="topologyList.length === 0">
+					<td colspan="6">
+						<span class="text-muted">Empty list</span>
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+
+	<div class="box-footer">
+		<button class="btn btn-primary pull-right" ng-click="newTopology()">
+			New Topology
+			<i class="fa fa-plus-circle"> </i>
+		</button>
+	</div>
+
+	<div class="overlay" ng-hide="topologyList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
new file mode 100644
index 0000000..0acb2c1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
@@ -0,0 +1,151 @@
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-eye"></i>
+		<a class="pull-right" href="#/config/topology/management"><span class="fa fa-angle-right"></span>
Management</a>
+		<h3 class="box-title">
+			Monitoring
+		</h3>
+	</div>
+	<div class="box-body">
+		<div sorttable source="topologyExecutionList">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+				<tr>
+					<th width="70" sortpath="status">Status</th>
+					<th width="90" sortpath="tags.topology">Topology</th>
+					<th width="60" sortpath="tags.site">Site</th>
+					<th width="100" sortpath="tags.application">Application</th>
+					<th width="60" sortpath="mode">Mode</th>
+					<th sortpath="description">Description</th>
+					<th width="70" style="min-width: 70px;"></th>
+				</tr>
+				</thead>
+				<tbody>
+				<tr>
+					<td class="text-center">
+						<span class="label label-{{_parent.getStatusClass(item.status)}}">{{item.status}}</span>
+					</td>
+					<td><a ng-click="_parent.showTopologyDetail(item)">{{item.tags.topology}}</a></td>
+					<td>{{item.tags.site}}</td>
+					<td>{{item.tags.application}}</td>
+					<td>{{item.mode}}</td>
+					<td>{{item.description}}</td>
+					<td class="text-center">
+						<button ng-if="item.status === 'NEW' || item.status === 'STOPPED'" class="fa fa-play
sm btn btn-default btn-xs" uib-tooltip="Start" tooltip-animation="false" ng-click="_parent.startTopologyOperation(item)">
</button>
+						<button ng-if="item.status === 'STARTED'" class="fa fa-stop sm btn btn-default btn-xs"
uib-tooltip="Stop" tooltip-animation="false" ng-click="_parent.stopTopologyOperation(item)">
</button>
+						<button ng-if="item.status !== 'NEW' && item.status !== 'STARTED' &&
item.status !== 'STOPPED'" class="fa fa-ban sm btn btn-default btn-xs" disabled="disabled">
</button>
+						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false"
ng-click="_parent.deleteTopologyExecution(item)"> </button>
+					</td>
+				</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+
+	<div class="box-footer">
+		<button class="btn btn-primary pull-right" ng-click="newTopologyExecution()">
+			New Topology Execution
+			<i class="fa fa-plus-circle"> </i>
+		</button>
+	</div>
+
+	<div class="overlay" ng-hide="topologyExecutionList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>
+
+
+
+
+<!-- Modal: Topology Detail -->
+<div class="modal fade" id="topologyMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Topology Detail</h4>
+			</div>
+			<div class="modal-body">
+				<h3>Detail</h3>
+				<table class="table">
+					<tbody>
+						<tr>
+							<th>Site</th>
+							<td>{{currentTopologyExecution.tags.site}}</td>
+						</tr>
+						<tr>
+							<th>Application</th>
+							<td>{{currentTopologyExecution.tags.application}}</td>
+						</tr>
+						<tr>
+							<th>Topology</th>
+							<td>{{currentTopologyExecution.tags.topology}}</td>
+						</tr>
+						<tr>
+							<th>Full Name</th>
+							<td>{{currentTopologyExecution.fullName || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Status</th>
+							<td>
+								<span class="label label-{{getStatusClass(currentTopologyExecution.status)}}">{{currentTopologyExecution.status}}</span>
+							</td>
+						</tr>
+						<tr>
+							<th>Mode</th>
+							<td>{{currentTopologyExecution.mode || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Environment</th>
+							<td>{{currentTopologyExecution.environment || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Url</th>
+							<td>
+								<a ng-if="currentTopologyExecution.url" href="{{currentTopologyExecution.url}}"
target="_blank">{{currentTopologyExecution.url}}</a>
+								<span ng-if="!currentTopologyExecution.url">-</span>
+							</td>
+						</tr>
+						<tr>
+							<th>Description</th>
+							<td>{{currentTopologyExecution.description || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Last Modified Date</th>
+							<td>{{common.format.date(currentTopologyExecution.lastModifiedDate) || "-"}}</td>
+						</tr>
+					</tbody>
+				</table>
+
+				<h3>Latest Operations</h3>
+				<div class="table-responsive">
+					<table class="table table-bordered table-sm margin-bottom-none">
+						<thead>
+							<tr>
+								<th>Date Time</th>
+								<th>Operation</th>
+								<th>Status</th>
+								<th>Message</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr ng-repeat="action in currentTopologyExecutionOptList track by $index">
+								<td>{{common.format.date(action.lastModifiedDate) || "-"}}</td>
+								<td>{{action.tags.operation}}</td>
+								<td>{{action.status}}</td>
+								<td><pre class="noWrap">{{action.message}}</pre></td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/app.config.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.config.js b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
index 4392f71..bd6d62c 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.config.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
@@ -55,10 +55,9 @@
 	// =                                   URLs                                   =
 	// ============================================================================
 	app.getURL = function(name, kvs) {
-		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
 		var _path = app.config.urls[name];
 		if(!_path) throw "URL:'" + name + "' not exist!";
-		var _url = (_host ? _host + "/" : '') + _path;
+		var _url = app.packageURL(_path);
 		if(kvs !== undefined) {
 			_url = common.template(_url, kvs);
 		}
@@ -69,9 +68,7 @@
 		var _path = app.config.urls[hookType][serviceName];
 		if(!_path) return null;
 
-		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
-		var _url = (_host ? _host + "/" : '') + _path;
-		return _url;
+		return app.packageURL(_path);
 	}
 
 	/***
@@ -90,6 +87,11 @@
 		return getHookURL('UPDATE_HOOK', serviceName);
 	};
 
+	app.packageURL = function (path) {
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		return (_host ? _host + "/" : '') + path;
+	};
+
 	app._Host = function(host) {
 		if(host) {
 			localStorage.setItem("HOST", host);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/common.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/common.js b/eagle-webservice/src/main/webapp/app/public/js/common.js
index 0e238b8..4c5e82f 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/common.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/common.js
@@ -147,6 +147,45 @@ common.format.date = function(val, type) {
 	}
 };
 
+// ===================== Property =====================
+common.properties = {};
+
+common.properties.parse = function (str, defaultValue) {
+	var regex = /\s*([\w\.]+)\s*=\s*(.*?)\s*([\r\n]+|$)/g;
+	var match, props = {};
+	var hasValue = false;
+	while((match = regex.exec(str)) !== null) {
+		props[match[1]] = match[2];
+		hasValue = true;
+	}
+	props = hasValue ? props : defaultValue;
+	props.getValueByPath = function (path) {
+		if(props[path] !== undefined) return props[path];
+		var subProps = {};
+		var prefixPath = path + ".";
+		$.each(props, function (key, value) {
+			if(typeof value === "string" && key.indexOf(prefixPath) === 0) {
+				subProps[key.replace(prefixPath, "")] = value;
+			}
+		});
+		return subProps;
+	};
+
+	return props;
+};
+
+common.properties.check = function (str) {
+	var pass = true;
+	var regex = /^\s*[\w\.]+\s*=(.*)$/;
+	$.each((str || "").trim().split(/[\r\n\s]+/g), function (i, line) {
+		if(!regex.test(line)) {
+			pass = false;
+			return false;
+		}
+	});
+	return pass;
+};
+
 // ====================== Array =======================
 common.array = {};
 
@@ -198,10 +237,16 @@ common.array.top = function(list, path, count) {
 	return !count ? _list : _list.slice(0, count);
 };
 
-common.array.find = function(val, list, path, findAll) {
+common.array.find = function(val, list, path, findAll, caseSensitive) {
 	path = path || "";
+	val = caseSensitive === false ? (val || "").toUpperCase() : val;
+
 	var _list = $.grep(list, function(unit) {
-		return val === common.getValueByPath(unit, path);
+		if(caseSensitive === false) {
+			return val === (common.getValueByPath(unit, path) || "").toUpperCase();
+		} else {
+			return val === common.getValueByPath(unit, path);
+		}
 	});
 	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
 };

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
b/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
index 300c6fd..a321ffe 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
@@ -342,7 +342,11 @@
 			var _oriConfig = application.config;
 			UI.updateConfirm("Application", {config: _oriConfig}, [
 				{name: "Configuration", field: "config", type: "blob"}
-			]).then(null, null, function(holder) {
+			], function(entity) {
+				if(entity.config !== "" && !common.properties.check(entity.config)) {
+					return "Invalid Properties format";
+				}
+			}).then(null, null, function(holder) {
 				application.config = holder.entity.config;
 				holder.closeFunc();
 				if(_oriConfig !== application.config) $scope.changed = true;

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
index b580f92..879eac0 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
@@ -28,7 +28,7 @@
 			kvs = kvs || {};
 			var _list = [];
 			var _condition = kvs._condition || {};
-			var _addtionalCondition = _condition.additionalCondition || {};
+			var _additionalCondition = _condition.additionalCondition || {};
 			var _startTime, _endTime;
 			var _startTimeStr, _endTimeStr;
 
@@ -52,37 +52,37 @@
 
 			// Fill special parameters
 			// > Query by time duration
-			if(_addtionalCondition._duration) {
+			if(_additionalCondition._duration) {
 				_endTime = app.time.now();
-				_startTime = _endTime.clone().subtract(_addtionalCondition._duration, "ms");
+				_startTime = _endTime.clone().subtract(_additionalCondition._duration, "ms");
 
 				// Debug usage. Extend more time duration for end time
-				if(_addtionalCondition.__ETD) {
-					_endTime.add(_addtionalCondition.__ETD, "ms");
+				if(_additionalCondition.__ETD) {
+					_endTime.add(_additionalCondition.__ETD, "ms");
 				}
 
-				_addtionalCondition._startTime = _startTime;
-				_addtionalCondition._endTime = _endTime;
+				_additionalCondition._startTime = _startTime;
+				_additionalCondition._endTime = _endTime;
 
 				_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
 				_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
 
 				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-			} else if(_addtionalCondition._startTime && _addtionalCondition._endTime) {
-				_startTimeStr = _addtionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
-				_endTimeStr = _addtionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+			} else if(_additionalCondition._startTime && _additionalCondition._endTime) {
+				_startTimeStr = _additionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
+				_endTimeStr = _additionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
 
 				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
 			}
 
 			// > Query contains metric name
-			if(_addtionalCondition._metricName) {
-				_url += "&metricName=" + _addtionalCondition._metricName;
+			if(_additionalCondition._metricName) {
+				_url += "&metricName=" + _additionalCondition._metricName;
 			}
 
 			// > Customize page size
-			if(_addtionalCondition._pageSize) {
-				_url = _url.replace(/pageSize=\d+/, "pageSize=" + _addtionalCondition._pageSize);
+			if(_additionalCondition._pageSize) {
+				_url = _url.replace(/pageSize=\d+/, "pageSize=" + _additionalCondition._pageSize);
 			}
 
 			// AJAX
@@ -190,7 +190,7 @@
 
 				// Check for url hook
 				if(config.hook) {
-					_url = app.getUpdateURL(serviceName);
+					_url = app.getUpdateURL(serviceName) || app.packageURL(serviceName);
 				} else {
 					_url = app.getURL("updateEntity", {serviceName: serviceName});
 				}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
index c97e9a1..fce64c0 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
@@ -125,7 +125,7 @@
 					} else if(!_application) {
 						console.warn("[Site] Application not found:", siteApplication.tags.site, "-", siteApplication.tags.application);
 					} else {
-						_configObj = common.parseJSON(siteApplication.config, {});
+						_configObj = common.properties.parse(siteApplication.config, {});
 						Object.defineProperties(siteApplication, {
 							application: {
 								get: function () {

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
index 9d4874b..882e179 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
@@ -31,7 +31,9 @@
 		function _bindShortcut($dialog) {
 			$dialog.on("keydown", function (event) {
 				if(event.which === 13) {
-					$dialog.find(".confirmBtn").click();
+					if(!$(":focus").is("textarea")) {
+						$dialog.find(".confirmBtn:enabled").click();
+					}
 				}
 			});
 		}
@@ -66,6 +68,13 @@
 			});
 
 			// Function
+			$scope.getFieldDescription = function (field) {
+				if(typeof field.description === "function") {
+					return field.description($scope.entity);
+				}
+				return field.description || ((field.name || field.field) + '...');
+			};
+
 			$scope.emptyFieldList = function() {
 				return $.map(fieldList, function(field) {
 					if(!field.optional && !entity[field.field]) {
@@ -96,7 +105,7 @@
 		 * Create a creation confirm modal.
 		 * @param name			Name title
 		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number),
description(optional), optional(optional), readonly(optional)}
+		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional:
number), description(optional), optional(optional), readonly(optional), valueList(optional)}
 		 * @param checkFunc	Check logic function. Return string will prevent access
 		 */
 		UI.createConfirm = function(name, entity, fieldList, checkFunc) {
@@ -107,7 +116,7 @@
 		 * Create a update confirm modal.
 		 * @param name			Name title
 		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number),
description(optional), optional(optional), readonly(optional)}
+		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional:
number), description(optional), optional(optional), readonly(optional), valueList(optional)}
 		 * @param checkFunc	Check logic function. Return string will prevent access
 		 */
 		UI.updateConfirm = function(name, entity, fieldList, checkFunc) {
@@ -122,7 +131,7 @@
 		 * 			@param config.confirm			Boolean. Display or not confirm button
 		 * 			@param config.confirmDesc		Confirm button display description
 		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: blob), rows(optional: number),
description(optional), optional(optional), readonly(optional)}
+		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional:
number), description(optional), optional(optional), readonly(optional), valueList(optional)}
 		 * @param checkFunc	Check logic function. Return string will prevent access
 		 */
 		UI.fieldConfirm = function(config, entity, fieldList, checkFunc) {
@@ -191,8 +200,11 @@
 								'<span ng-if="!field.optional">*</span> ' +
 								'{{field.name || field.field}}' +
 							'</label>' +
-							'<textarea class="form-control" placeholder="{{field.description || field.name
|| field.field + \'...\'}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly"
ng-disabled="lock" ng-switch-when="blob"></textarea>' +
-							'<input type="text" class="form-control" placeholder="{{field.description || field.name
|| field.field + \'...\'}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock"
ng-switch-default>' +
+							'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]"
rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>'
+
+							'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field]
= entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
+								'<option ng-repeat="value in field.valueList">{{value}}</option>' +
+							'</select>' +
+							'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}"
ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>'
+
 						'</div>' +
 					'</div>' +
 					'<div class="modal-footer">' +

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ecf75b28/mkdocs.yml
----------------------------------------------------------------------
diff --git a/mkdocs.yml b/mkdocs.yml
index 6e8d1af..c07ed4a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -55,5 +55,6 @@ pages:
     - Apache Eagle Development Environment Setup: development/eagle_development_setup.md
 - Tutorial:
    - Getting started with Eagle: tutorial/getting_started_with_eagle.md
+   - Application Manager Tutorial: tutorial/application_manager_tutorial.md
 - User Guide:
    - Installation: userguide/installation.md


Mime
View raw message