eagle-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ji...@apache.org
Subject [14/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api
Date Wed, 28 Sep 2016 05:38:55 GMT
[EAGLE-574] UI refactor for support 0.5 api

Eagle 0.5 updates the rest api interface and UI need also update for support the api.

* Remove feature
* Support app provider
* Create JPM UI application
* Redesign site logic
* Widget support

Author: jiljiang <jiljiang@ebay.com>

Closes #460 from zombieJ/ui.


Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/afb89794
Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/afb89794
Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/afb89794

Branch: refs/heads/master
Commit: afb897940021caec79982e29f7194f2b6c733b49
Parents: 1fa490e
Author: zombiej <jilin@apache.org>
Authored: Wed Sep 28 13:38:17 2016 +0800
Committer: jiljiang <jiljiang@ebay.com>
Committed: Wed Sep 28 13:38:17 2016 +0800

----------------------------------------------------------------------
 .../webapp/app/apps/jpm/ctrl/compareCtrl.js     |  380 ++++++
 .../main/webapp/app/apps/jpm/ctrl/detailCtrl.js |  192 +++
 .../webapp/app/apps/jpm/ctrl/jobTaskCtrl.js     |  551 ++++++++
 .../main/webapp/app/apps/jpm/ctrl/listCtrl.js   |  239 ++++
 .../webapp/app/apps/jpm/ctrl/overviewCtrl.js    |  140 ++
 .../webapp/app/apps/jpm/ctrl/statisticCtrl.js   |  386 ++++++
 .../src/main/webapp/app/apps/jpm/index.js       |  489 +++++++
 .../app/apps/jpm/partials/job/compare.html      |  274 ++++
 .../app/apps/jpm/partials/job/detail.html       |  256 ++++
 .../webapp/app/apps/jpm/partials/job/list.html  |  131 ++
 .../app/apps/jpm/partials/job/overview.html     |  347 +++++
 .../app/apps/jpm/partials/job/statistic.html    |  120 ++
 .../webapp/app/apps/jpm/partials/job/task.html  |  149 +++
 .../main/webapp/app/apps/jpm/style/index.css    |   76 ++
 .../webapp/app/apps/jpm/widget/jobStatistic.js  |  108 ++
 eagle-server/.gitignore                         |    7 +
 eagle-server/pom.xml                            |   23 +-
 eagle-server/src/main/webapp/app/.editorconfig  |   27 +
 eagle-server/src/main/webapp/app/Gruntfile.js   |  190 +++
 eagle-server/src/main/webapp/app/README.md      |    4 +
 eagle-server/src/main/webapp/app/build/index.js |  144 +++
 eagle-server/src/main/webapp/app/dev/index.html |  250 ++++
 .../webapp/app/dev/partials/alert/list.html     |   21 +
 .../webapp/app/dev/partials/alert/main.html     |   29 +
 .../app/dev/partials/alert/policyEdit.back.html |  108 ++
 .../app/dev/partials/alert/policyEdit.html      |   29 +
 .../app/dev/partials/alert/policyList.html      |   63 +
 .../src/main/webapp/app/dev/partials/home.html  |   60 +
 .../partials/integration/applicationList.html   |   80 ++
 .../app/dev/partials/integration/main.html      |   29 +
 .../app/dev/partials/integration/site.html      |   95 ++
 .../app/dev/partials/integration/siteList.html  |   60 +
 .../dev/partials/integration/streamList.html    |   52 +
 .../src/main/webapp/app/dev/partials/setup.html |   46 +
 .../webapp/app/dev/public/css/animation.css     |   47 +
 .../src/main/webapp/app/dev/public/css/main.css |  317 +++++
 .../webapp/app/dev/public/css/sortTable.css     |   61 +
 .../webapp/app/dev/public/images/favicon.png    |  Bin 0 -> 4209 bytes
 .../app/dev/public/images/favicon_white.png     |  Bin 0 -> 1621 bytes
 .../src/main/webapp/app/dev/public/js/app.js    |  311 +++++
 .../src/main/webapp/app/dev/public/js/common.js |  387 ++++++
 .../app/dev/public/js/components/chart.js       |  188 +++
 .../webapp/app/dev/public/js/components/main.js |   24 +
 .../app/dev/public/js/components/sortTable.js   |  231 ++++
 .../app/dev/public/js/components/widget.js      |   52 +
 .../webapp/app/dev/public/js/ctrls/alertCtrl.js |  119 ++
 .../app/dev/public/js/ctrls/integrationCtrl.js  |  226 ++++
 .../main/webapp/app/dev/public/js/ctrls/main.js |   27 +
 .../webapp/app/dev/public/js/ctrls/mainCtrl.js  |   62 +
 .../webapp/app/dev/public/js/ctrls/siteCtrl.js  |   33 +
 .../src/main/webapp/app/dev/public/js/index.js  |  326 +++++
 .../dev/public/js/services/applicationSrv.js    |   71 +
 .../app/dev/public/js/services/entitySrv.js     |  135 ++
 .../webapp/app/dev/public/js/services/main.js   |   23 +
 .../app/dev/public/js/services/pageSrv.js       |  137 ++
 .../app/dev/public/js/services/siteSrv.js       |  111 ++
 .../app/dev/public/js/services/timeSrv.js       |  277 ++++
 .../webapp/app/dev/public/js/services/uiSrv.js  |  276 ++++
 .../app/dev/public/js/services/widgetSrv.js     |   79 ++
 .../app/dev/public/js/services/wrapStateSrv.js  |  119 ++
 .../app/dev/public/js/worker/sortTableFunc.js   |   93 ++
 .../app/dev/public/js/worker/sortTableWorker.js |   32 +
 eagle-server/src/main/webapp/app/index.html     |   11 +-
 eagle-server/src/main/webapp/app/package.json   |   47 +
 eagle-server/src/main/webapp/package.json       |    0
 eagle-server/ui-build.sh                        |   40 +
 eagle-webservice/src/main/webapp/Gruntfile.js   |  175 ---
 eagle-webservice/src/main/webapp/README.md      |    4 -
 .../src/main/webapp/_app/index.html             |  281 ++++
 .../_app/partials/config/application.html       |  124 ++
 .../webapp/_app/partials/config/feature.html    |   85 ++
 .../main/webapp/_app/partials/config/site.html  |  115 ++
 .../src/main/webapp/_app/partials/landing.html  |   30 +
 .../src/main/webapp/_app/partials/login.html    |   54 +
 .../main/webapp/_app/public/css/animation.css   |   46 +
 .../src/main/webapp/_app/public/css/main.css    |  805 ++++++++++++
 .../public/feature/classification/controller.js |  358 +++++
 .../classification/page/sensitivity.html        |   40 +
 .../classification/page/sensitivity/folder.html |  110 ++
 .../classification/page/sensitivity/job.html    |   92 ++
 .../classification/page/sensitivity/table.html  |  150 +++
 .../_app/public/feature/common/controller.js    | 1224 ++++++++++++++++++
 .../public/feature/common/page/alertDetail.html |   67 +
 .../public/feature/common/page/alertList.html   |   67 +
 .../feature/common/page/policyDetail.html       |  173 +++
 .../public/feature/common/page/policyEdit.html  |  346 +++++
 .../public/feature/common/page/policyList.html  |   84 ++
 .../_app/public/feature/metadata/controller.js  |   66 +
 .../feature/metadata/page/streamList.html       |   84 ++
 .../_app/public/feature/metrics/controller.js   |  571 ++++++++
 .../public/feature/metrics/page/dashboard.html  |  250 ++++
 .../_app/public/feature/topology/controller.js  |  257 ++++
 .../feature/topology/page/management.html       |   52 +
 .../feature/topology/page/monitoring.html       |  151 +++
 .../public/feature/userProfile/controller.js    |  268 ++++
 .../public/feature/userProfile/page/detail.html |   87 ++
 .../public/feature/userProfile/page/list.html   |  138 ++
 .../main/webapp/_app/public/images/favicon.png  |  Bin 0 -> 4209 bytes
 .../webapp/_app/public/images/favicon_white.png |  Bin 0 -> 1621 bytes
 .../main/webapp/_app/public/js/app.config.js    |  126 ++
 .../src/main/webapp/_app/public/js/app.js       |  499 +++++++
 .../src/main/webapp/_app/public/js/app.time.js  |   70 +
 .../src/main/webapp/_app/public/js/app.ui.js    |   76 ++
 .../src/main/webapp/_app/public/js/common.js    |  304 +++++
 .../_app/public/js/components/charts/line3d.js  |  348 +++++
 .../webapp/_app/public/js/components/file.js    |   50 +
 .../webapp/_app/public/js/components/main.js    |   19 +
 .../webapp/_app/public/js/components/nvd3.js    |  418 ++++++
 .../_app/public/js/components/sortTable.js      |  113 ++
 .../_app/public/js/components/sortable.js       |  166 +++
 .../webapp/_app/public/js/components/tabs.js    |  247 ++++
 .../_app/public/js/ctrl/authController.js       |   91 ++
 .../public/js/ctrl/configurationController.js   |  377 ++++++
 .../src/main/webapp/_app/public/js/ctrl/main.js |   42 +
 .../webapp/_app/public/js/srv/applicationSrv.js |  170 +++
 .../_app/public/js/srv/authorizationSrv.js      |  143 ++
 .../webapp/_app/public/js/srv/entitiesSrv.js    |  301 +++++
 .../src/main/webapp/_app/public/js/srv/main.js  |   72 ++
 .../main/webapp/_app/public/js/srv/pageSrv.js   |  131 ++
 .../main/webapp/_app/public/js/srv/siteSrv.js   |  193 +++
 .../src/main/webapp/_app/public/js/srv/uiSrv.js |  247 ++++
 .../webapp/_app/public/js/srv/wrapStateSrv.js   |  109 ++
 eagle-webservice/src/main/webapp/app/index.html |  281 ----
 .../webapp/app/partials/config/application.html |  124 --
 .../webapp/app/partials/config/feature.html     |   85 --
 .../main/webapp/app/partials/config/site.html   |  115 --
 .../src/main/webapp/app/partials/landing.html   |   30 -
 .../src/main/webapp/app/partials/login.html     |   54 -
 .../main/webapp/app/public/css/animation.css    |   46 -
 .../src/main/webapp/app/public/css/main.css     |  805 ------------
 .../public/feature/classification/controller.js |  358 -----
 .../classification/page/sensitivity.html        |   40 -
 .../classification/page/sensitivity/folder.html |  110 --
 .../classification/page/sensitivity/job.html    |   92 --
 .../classification/page/sensitivity/table.html  |  150 ---
 .../app/public/feature/common/controller.js     | 1224 ------------------
 .../public/feature/common/page/alertDetail.html |   67 -
 .../public/feature/common/page/alertList.html   |   67 -
 .../feature/common/page/policyDetail.html       |  173 ---
 .../public/feature/common/page/policyEdit.html  |  346 -----
 .../public/feature/common/page/policyList.html  |   84 --
 .../app/public/feature/metadata/controller.js   |   66 -
 .../feature/metadata/page/streamList.html       |   84 --
 .../app/public/feature/metrics/controller.js    |  571 --------
 .../public/feature/metrics/page/dashboard.html  |  250 ----
 .../app/public/feature/topology/controller.js   |  257 ----
 .../feature/topology/page/management.html       |   52 -
 .../feature/topology/page/monitoring.html       |  151 ---
 .../public/feature/userProfile/controller.js    |  268 ----
 .../public/feature/userProfile/page/detail.html |   87 --
 .../public/feature/userProfile/page/list.html   |  138 --
 .../main/webapp/app/public/images/favicon.png   |  Bin 4209 -> 0 bytes
 .../webapp/app/public/images/favicon_white.png  |  Bin 1621 -> 0 bytes
 .../src/main/webapp/app/public/js/app.config.js |  126 --
 .../src/main/webapp/app/public/js/app.js        |  499 -------
 .../src/main/webapp/app/public/js/app.time.js   |   70 -
 .../src/main/webapp/app/public/js/app.ui.js     |   76 --
 .../src/main/webapp/app/public/js/common.js     |  304 -----
 .../app/public/js/components/charts/line3d.js   |  348 -----
 .../webapp/app/public/js/components/file.js     |   50 -
 .../webapp/app/public/js/components/main.js     |   19 -
 .../webapp/app/public/js/components/nvd3.js     |  418 ------
 .../app/public/js/components/sortTable.js       |  113 --
 .../webapp/app/public/js/components/sortable.js |  166 ---
 .../webapp/app/public/js/components/tabs.js     |  247 ----
 .../webapp/app/public/js/ctrl/authController.js |   91 --
 .../public/js/ctrl/configurationController.js   |  377 ------
 .../src/main/webapp/app/public/js/ctrl/main.js  |   42 -
 .../webapp/app/public/js/srv/applicationSrv.js  |  170 ---
 .../app/public/js/srv/authorizationSrv.js       |  143 --
 .../webapp/app/public/js/srv/entitiesSrv.js     |  301 -----
 .../src/main/webapp/app/public/js/srv/main.js   |   72 --
 .../main/webapp/app/public/js/srv/pageSrv.js    |  131 --
 .../main/webapp/app/public/js/srv/siteSrv.js    |  193 ---
 .../src/main/webapp/app/public/js/srv/uiSrv.js  |  240 ----
 .../webapp/app/public/js/srv/wrapStateSrv.js    |  109 --
 eagle-webservice/src/main/webapp/grunt.json     |   42 -
 eagle-webservice/src/main/webapp/index.html     |   28 -
 eagle-webservice/src/main/webapp/package.json   |   48 -
 eagle-webservice/ui-build.sh                    |   23 +-
 180 files changed, 19503 insertions(+), 10801 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
new file mode 100644
index 0000000..121e80e
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
@@ -0,0 +1,380 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		/**
+		 * @param {{}} $scope
+		 * @param {{}} $scope.trendChart
+		 */
+		jpmApp.controller("compareCtrl", function ($q, $wrapState, $scope, PageConfig, Time, Entity, JPM) {
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobDefId = $wrapState.param.jobDefId;
+
+			$scope.jobTrendCategory = [];
+
+			$scope.fromJob = null;
+			$scope.toJob = null;
+
+			PageConfig.title = "Job History";
+			PageConfig.subTitle = $scope.jobDefId;
+
+			$scope.getStateClass = JPM.getStateClass;
+
+			var browserAction = true;
+
+			// ==================================================================
+			// =                         Fetch Job List                         =
+			// ==================================================================
+			var jobList = $scope.jobList = JPM.findMRJobs($scope.site, $scope.jobDefId);
+			jobList._promise.then(function () {
+				if(jobList.length <= 1) {
+					$.dialog({
+						title: "No statistic info",
+						content: "Current job do not have enough statistic info. Please check 'jobDefId' if your job have run many times."
+					});
+				}
+
+				function findJob(jobId) {
+					return common.array.find(jobId, jobList, "tags.jobId");
+				}
+
+				var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000];
+				function taskDistribution(jobId) {
+					return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then(
+						/**
+						 * @param {{}} res
+						 * @param {{}} res.data
+						 * @param {[]} res.data.finishedTaskCount
+						 * @param {[]} res.data.runningTaskCount
+						 */
+						function (res) {
+							var result = {};
+							var data = res.data;
+							var finishedTaskCount = data.finishedTaskCount;
+							var runningTaskCount = data.runningTaskCount;
+
+							/**
+							 * @param {number} item.taskCount
+							 */
+							var finishedTaskData = $.map(finishedTaskCount, function (item) {
+								return item.taskCount;
+							});
+							/**
+							 * @param {number} item.taskCount
+							 */
+							var runningTaskData = $.map(runningTaskCount, function (item) {
+								return item.taskCount;
+							});
+
+							result.taskSeries = [{
+								name: "Finished Tasks",
+								type: "bar",
+								stack: jobId,
+								data: finishedTaskData
+							}, {
+								name: "Running Tasks",
+								type: "bar",
+								stack: jobId,
+								data: runningTaskData
+							}];
+
+							result.finishedTaskCount = finishedTaskCount;
+							result.runningTaskCount = runningTaskCount;
+
+							return result;
+						});
+				}
+
+				var taskDistributionCategory = $.map(TASK_BUCKET_TIMES, function (current, i) {
+					var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000);
+					var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000);
+
+					if(!curDes && nextDes) {
+						return "<" + nextDes;
+					} else if(nextDes) {
+						return curDes + "\n~\n" + nextDes;
+					}
+					return ">" + curDes;
+				});
+
+				// ========================= Job Trend ==========================
+				function refreshParam() {
+					browserAction = false;
+					$wrapState.go(".", {
+						from: common.getValueByPath($scope.fromJob, "tags.jobId"),
+						to: common.getValueByPath($scope.toJob, "tags.jobId")
+					});
+					setTimeout(function () {
+						browserAction = true;
+					}, 0);
+				}
+
+				function getMarkPoint(name, x, y, color) {
+					return {
+						name: name,
+						silent: true,
+						coord: [x, y],
+						symbolSize: 20,
+						label: {
+							normal: { show: false },
+							emphasis: { show: false }
+						}, itemStyle: {
+							normal: { color: color }
+						}
+					};
+				}
+
+				function refreshTrendMarkPoint() {
+					var fromX = null, fromY = null;
+					var toX = null, toY = null;
+					$.each(jobList, function (index, job) {
+						if($scope.fromJob && $scope.fromJob.tags.jobId === job.tags.jobId) {
+							fromX = index;
+							fromY = job.durationTime;
+						}
+						if($scope.toJob && $scope.toJob.tags.jobId === job.tags.jobId) {
+							toX = index;
+							toY = job.durationTime;
+						}
+					});
+
+					markPoint.data = [];
+					if(!common.isEmpty(fromX)) {
+						markPoint.data.push(getMarkPoint("<From Job>", fromX, fromY, "#00c0ef"));
+					}
+					if(!common.isEmpty(toX)) {
+						markPoint.data.push(getMarkPoint("<To Job>", toX, toY, "#3c8dbc"));
+					}
+
+					$scope.trendChart.refresh();
+				}
+
+				var jobListTrend = $.map(jobList, function (job) {
+					var time = Time.format(job.startTime);
+					$scope.jobTrendCategory.push(time);
+					return job.durationTime;
+				});
+
+				$scope.jobTrendOption = {
+					yAxis: [{
+						axisLabel: {
+							formatter: function (value) {
+								return Time.diffStr(value);
+							}
+						}
+					}],
+					tooltip: {
+						formatter: function (points) {
+							var point = points[0];
+							return point.name + "<br/>" +
+								'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+								point.seriesName + ": " + Time.diffStr(point.value);
+						}
+					}
+				};
+
+				var markPoint = {
+					data: [],
+					silent: true
+				};
+
+				$scope.jobTrendSeries = [{
+					name: "Job Duration",
+					type: "line",
+					data: jobListTrend,
+					symbolSize: 10,
+					showSymbol: false,
+					markPoint: markPoint
+				}];
+
+				$scope.compareJobSelect = function (e, job) {
+					var index = e.dataIndex;
+					job = job || jobList[index];
+					if(!job) return;
+
+					var event = e.event ? e.event.event : e;
+
+					if(event.ctrlKey) {
+						$scope.fromJob = job;
+					} else if(event.shiftKey) {
+						$scope.toJob = job;
+					} else {
+						if ($scope.fromJob) {
+							$scope.toJob = $scope.fromJob;
+						}
+						$scope.fromJob = job;
+					}
+
+					refreshTrendMarkPoint();
+					refreshParam();
+					refreshComparisonDashboard();
+				};
+
+				$scope.exchangeJobs = function () {
+					var tmpJob = $scope.fromJob;
+					$scope.fromJob = $scope.toJob;
+					$scope.toJob = tmpJob;
+					refreshTrendMarkPoint();
+					refreshParam();
+					refreshComparisonDashboard();
+				};
+
+				// ======================= Job Comparison =======================
+				$scope.taskOption = {
+					xAxis: {axisTick: { show: true }}
+				};
+
+				function getComparedValue(path) {
+					var val1 = common.getValueByPath($scope.fromJob, path);
+					var val2 = common.getValueByPath($scope.toJob, path);
+					return common.number.compare(val1, val2);
+				}
+
+				$scope.jobCompareClass = function (path) {
+					var diff = getComparedValue(path);
+					if(typeof diff !== "number" || Math.abs(diff) < 0.01) return "hide";
+					if(diff < 0.05) return "label label-success";
+					if(diff < 0.15) return "label label-warning";
+					return "label label-danger";
+				};
+
+				$scope.jobCompareValue = function (path) {
+					var diff = getComparedValue(path);
+					return (diff >= 0 ? "+" : "") + Math.floor(diff * 100) + "%";
+				};
+
+				/**
+				 * get 2 interval data list category. (minutes level)
+				 * @param {[]} list1
+				 * @param {[]} list2
+				 * @return {Array}
+				 */
+				function intervalCategories(list1, list2) {
+					var len = Math.max(list1.length, list2.length);
+					var category = [];
+					for(var i = 0 ; i < len ; i += 1) {
+						category.push((i + 1) + "min");
+					}
+					return category;
+				}
+
+				function refreshComparisonDashboard() {
+					if(!$scope.fromJob || !$scope.toJob) return;
+
+					var fromJobCond = {
+						jobId: $scope.fromJob.tags.jobId,
+						site: $scope.site
+					};
+					var toJobCond = {
+						jobId: $scope.toJob.tags.jobId,
+						site: $scope.site
+					};
+
+					var from_startTime = Time($scope.fromJob.startTime).subtract(1, "hour");
+					var from_endTime = ($scope.fromJob.endTime ? Time($scope.fromJob.endTime) : Time()).add(1, "hour");
+					var to_startTime = Time($scope.toJob.startTime).subtract(1, "hour");
+					var to_endTime = ($scope.toJob.endTime ? Time($scope.toJob.endTime) : Time()).add(1, "hour");
+
+					$scope.fromJob._cache = $scope.fromJob._cache || {};
+					$scope.toJob._cache = $scope.toJob._cache || {};
+
+					/**
+					 * Generate metric level chart series
+					 * @param {string} metric
+					 * @param {string} seriesName
+					 */
+					function metricComparison(metric, seriesName) {
+						var from_metric = $scope.fromJob._cache[metric] =
+							$scope.fromJob._cache[metric] || JPM.metrics(fromJobCond, metric, from_startTime, from_endTime);
+						var to_metric = $scope.toJob._cache[metric] =
+							$scope.toJob._cache[metric] || JPM.metrics(toJobCond, metric, to_startTime, to_endTime);
+
+						var holder = {};
+
+						$q.all([from_metric._promise, to_metric._promise]).then(function () {
+							from_metric = JPM.metricsToInterval(from_metric, 1000 * 60);
+							to_metric = JPM.metricsToInterval(to_metric, 1000 * 60);
+
+							var series_from = JPM.metricsToSeries("from job " + seriesName, from_metric, true);
+							var series_to = JPM.metricsToSeries("to job " + seriesName, to_metric, true);
+
+							holder.categories = intervalCategories(from_metric, to_metric);
+							holder.series = [series_from, series_to];
+						});
+
+						return holder;
+					}
+
+					// Dashboard1: Containers metrics
+					$scope.comparisonChart_Container = metricComparison("hadoop.job.runningcontainers", "running containers");
+
+					// Dashboard 2: Allocated
+					$scope.comparisonChart_allocatedMB = metricComparison("hadoop.job.allocatedmb", "allocated MB");
+
+					// Dashboard 3: vCores
+					$scope.comparisonChart_vCores = metricComparison("hadoop.job.allocatedvcores", "vCores");
+
+					// Dashboard 4: Task distribution
+					var from_distributionPromise = $scope.fromJob._cache.distributionPromise =
+						$scope.fromJob._cache.distributionPromise || taskDistribution($scope.fromJob.tags.jobId);
+					var to_distributionPromise = $scope.toJob._cache.distributionPromise =
+						$scope.toJob._cache.distributionPromise || taskDistribution($scope.toJob.tags.jobId);
+					var comparisonChart_taskDistribution = $scope.comparisonChart_taskDistribution = {
+						categories: taskDistributionCategory
+					};
+
+					$q.all([from_distributionPromise, to_distributionPromise]).then(function (args) {
+						var from_data = args[0];
+						var to_data = args[1];
+
+						var from_taskSeries = $.map(from_data.taskSeries, function (series) {
+							return $.extend({}, series, {name: "From " + series.name});
+						});
+						var to_taskSeries = $.map(to_data.taskSeries, function (series) {
+							return $.extend({}, series, {name: "To " + series.name});
+						});
+
+						comparisonChart_taskDistribution.series = from_taskSeries.concat(to_taskSeries);
+					});
+				}
+
+				// ======================== Job Refresh =========================
+				function jobRefresh() {
+					$scope.fromJob = findJob($wrapState.param.from);
+					$scope.toJob = findJob($wrapState.param.to);
+					$(window).resize();
+					refreshTrendMarkPoint();
+					refreshComparisonDashboard();
+				}
+
+				// ======================= Initialization =======================
+				jobRefresh();
+
+				$scope.$on('$locationChangeSuccess', function() {
+					if(browserAction) {
+						jobRefresh();
+					}
+				});
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
new file mode 100644
index 0000000..be7631f
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
@@ -0,0 +1,192 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		jpmApp.controller("detailCtrl", function ($q, $wrapState, $element, $scope, PageConfig, Time, Entity, JPM) {
+			var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000];
+			var i;
+			var startTime, endTime;
+			var metric_allocatedMB, metric_allocatedVCores, metric_runningContainers;
+			var nodeTaskCountList;
+
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobId = $wrapState.param.jobId;
+
+			PageConfig.title = "Job";
+			PageConfig.subTitle = $scope.jobId;
+
+			$scope.getStateClass = JPM.getStateClass;
+			$scope.compareChart = null;
+
+			var jobCond = {
+				jobId: $scope.jobId,
+				site: $scope.site
+			};
+
+			function taskDistribution(jobId) {
+				return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then(
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.finishedTaskCount
+					 * @param {[]} res.data.runningTaskCount
+					 */
+					function (res) {
+						var result = {};
+						var data = res.data;
+						var finishedTaskCount = data.finishedTaskCount;
+						var runningTaskCount = data.runningTaskCount;
+
+						/**
+						 * @param {number} item.taskCount
+						 */
+						var finishedTaskData = $.map(finishedTaskCount, function (item) {
+							return item.taskCount;
+						});
+						/**
+						 * @param {number} item.taskCount
+						 */
+						var runningTaskData = $.map(runningTaskCount, function (item) {
+							return item.taskCount;
+						});
+
+						result.taskSeries = [{
+							name: "Finished Tasks",
+							type: "bar",
+							stack: jobId,
+							data: finishedTaskData
+						}, {
+							name: "Running Tasks",
+							type: "bar",
+							stack: jobId,
+							data: runningTaskData
+						}];
+
+						result.finishedTaskCount = finishedTaskCount;
+						result.runningTaskCount = runningTaskCount;
+
+						return result;
+					});
+			}
+
+			$scope.taskCategory = $.map(TASK_BUCKET_TIMES, function (current, i) {
+				var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000);
+				var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000);
+
+				if(!curDes && nextDes) {
+					return "<" + nextDes;
+				} else if(nextDes) {
+					return curDes + "\n~\n" + nextDes;
+				}
+				return ">" + curDes;
+			});
+
+			// =========================================================================
+			// =                               Fetch Job                               =
+			// =========================================================================
+			JPM.findMRJobs($scope.site, undefined, $scope.jobId)._promise.then(function (list) {
+				$scope.job = list[list.length - 1];
+				console.log("[JPM] Fetch job:", $scope.job);
+
+				if(!$scope.job) {
+					$.dialog({
+						title: "OPS!",
+						content: "Job not found!"
+					}, function () {
+						$wrapState.go("jpmList", {siteId: $scope.site});
+					});
+					return;
+				}
+
+				startTime = Time($scope.job.startTime).subtract(1, "hour");
+				endTime = Time().add(1, "hour");
+				$scope.startTimestamp = $scope.job.startTime;
+				$scope.endTimestamp = $scope.job.endTime;
+				$scope.isRunning = !$scope.job.currentState || ($scope.job.currentState || "").toUpperCase() === 'RUNNING';
+
+				// Dashboard 1: Allocated MB
+				metric_allocatedMB = JPM.metrics(jobCond, "hadoop.job.allocatedmb", startTime, endTime);
+
+				metric_allocatedMB._promise.then(function () {
+					var series_allocatedMB = JPM.metricsToSeries("allocated MB", metric_allocatedMB);
+					$scope.allocatedSeries = [series_allocatedMB];
+				});
+
+				// Dashboard 2: vCores & Containers metrics
+				metric_allocatedVCores = JPM.metrics(jobCond, "hadoop.job.allocatedvcores", startTime, endTime);
+				metric_runningContainers = JPM.metrics(jobCond, "hadoop.job.runningcontainers", startTime, endTime);
+
+				$q.all([metric_allocatedVCores._promise, metric_runningContainers._promise]).then(function () {
+					var series_allocatedVCores = JPM.metricsToSeries("vCores", metric_allocatedVCores);
+					var series_runningContainers = JPM.metricsToSeries("running containers", metric_runningContainers);
+					$scope.vCoresSeries = [series_allocatedVCores, series_runningContainers];
+				});
+
+				// Dashboard 3: Task duration
+				taskDistribution($scope.job.tags.jobId).then(function (data) {
+					$scope.taskSeries = data.taskSeries;
+
+					$scope.taskSeriesClick = function (e) {
+						var taskCount = e.seriesIndex === 0 ? data.finishedTaskCount : data.runningTaskCount;
+						$scope.taskBucket = taskCount[e.dataIndex];
+					};
+
+					$scope.backToTaskSeries = function () {
+						$scope.taskBucket = null;
+						setTimeout(function () {
+							$(window).resize();
+						}, 0);
+					};
+				});
+
+				// Dashboard 4: Running task
+				nodeTaskCountList = JPM.groups(
+					$scope.isRunning ? "RunningTaskExecutionService" : "TaskExecutionService",
+					jobCond, ["hostname"], "count", null, startTime, endTime, null, 1000000);
+				nodeTaskCountList._promise.then(function () {
+					var nodeTaskCountMap = [];
+
+					$.each(nodeTaskCountList, function (i, obj) {
+						var count = obj.value[0];
+						nodeTaskCountMap[count] = (nodeTaskCountMap[count] || 0) + 1;
+					});
+
+					$scope.nodeTaskCountCategory = [];
+					for(i = 0 ; i < nodeTaskCountMap.length ; i += 1) {
+						nodeTaskCountMap[i] = nodeTaskCountMap[i] || 0;
+						$scope.nodeTaskCountCategory.push(i + " tasks");
+					}
+
+					nodeTaskCountMap.splice(0, 1);
+					$scope.nodeTaskCountCategory.splice(0, 1);
+
+					$scope.nodeTaskCountSeries = [{
+						name: "node count",
+						type: "bar",
+						data: nodeTaskCountMap
+					}];
+				});
+
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
new file mode 100644
index 0000000..9f0e7f4
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
@@ -0,0 +1,551 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var TREND_INTERVAL = 60;
+		var SCHEDULE_BUCKET_COUNT = 30;
+		var TASK_STATUS = ["SUCCEEDED", "FAILED", "KILLED"];
+		var TASK_TYPE = ["MAP", "REDUCE"];
+		var DURATION_BUCKETS = [0, 30 * 1000, 60 * 1000, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000, 2 * 3600 * 1000, 3 * 3600 * 1000];
+		var TASK_FIELDS = [
+			"rack",
+			"hostname",
+			"taskType",
+			"taskId",
+			"taskStatus",
+			"startTime",
+			"endTime",
+			"jobCounters"
+		];
+
+		function getCommonHeatMapOption(categoryList, maxCount) {
+			return {
+				animation: false,
+				tooltip: {
+					trigger: 'item'
+				},
+				xAxis: {splitArea: {show: true}},
+				yAxis: [{
+					type: 'category',
+					data: categoryList,
+					splitArea: {show: true},
+					axisTick: {show: false}
+				}],
+				grid: { bottom: "50" },
+				visualMap: {
+					min: 0,
+					max: maxCount,
+					calculable: true,
+					orient: 'horizontal',
+					left: 'right',
+					inRange: {
+						color: ["#00a65a", "#ffdc62", "#dd4b39"]
+					}
+				}
+			};
+		}
+
+		function getCommonHeatMapSeries(name, data) {
+			return {
+				name: name,
+				type: "heatmap",
+				data: data,
+				itemStyle: {
+					normal: {
+						borderColor: "#FFF"
+					}
+				}
+			};
+		}
+
+		/**
+		 * @typedef {{}} Task
+		 * @property {string} taskStatus
+		 * @property {number} startTime
+		 * @property {number} endTime
+		 * @property {{}} jobCounters
+		 * @property {{}} jobCounters.counters
+		 * @property {{}} tags
+		 * @property {string} tags.taskType
+		 * @property {number} _bucket
+		 * @property {number} _bucketStart
+		 * @property {number} _bucketEnd
+		 * @property {number} _duration
+		 * @property {number} _durationBucket
+		 */
+
+		jpmApp.controller("jobTaskCtrl", function ($wrapState, $scope, PageConfig, Time, JPM) {
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobId = $wrapState.param.jobId;
+
+			var startTime = Number($wrapState.param.startTime);
+			var endTime = Number($wrapState.param.endTime);
+
+			PageConfig.title = "Task";
+			PageConfig.subTitle = $scope.jobId;
+
+			var timeDiff = endTime - startTime;
+			var timeDes = Math.ceil(timeDiff / SCHEDULE_BUCKET_COUNT);
+
+			$scope.bucketScheduleCategory = [];
+			for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) {
+				$scope.bucketScheduleCategory.push(Time.format(startTime + i * timeDes, "HH:mm:SS") + "\n~\n" + Time.format(startTime + (i + 1) * timeDes, "HH:mm:SS"));
+			}
+
+			$scope.bucketDurationCategory = [];
+			$.each(DURATION_BUCKETS, function (i, start) {
+				var end = DURATION_BUCKETS[i + 1];
+				if(!start) {
+					$scope.bucketDurationCategory.push("<" + Time.diffStr(end));
+				} else if(!end) {
+					$scope.bucketDurationCategory.push(">" + Time.diffStr(start));
+				} else {
+					$scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end));
+				}
+			});
+
+			// ============================================================================
+			// ============================================================================
+			// ==                               Fetch Task                               ==
+			// ============================================================================
+			// ============================================================================
+			$scope.list = JPM.list("TaskExecutionService", {site: $scope.site, jobId: $scope.jobId}, startTime, endTime, TASK_FIELDS, 1000000);
+			$scope.list._promise.then(function () {
+				var i;
+
+				// ========================= Schedule Trend =========================
+				var trend_map_countList = [];
+				var trend_reduce_countList = [];
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var _task = {
+							_bucketStart: Math.floor((task.startTime - startTime) / TREND_INTERVAL),
+							_bucketEnd: Math.floor((task.endTime - startTime) / TREND_INTERVAL)
+						};
+
+						switch (task.tags.taskType) {
+							case "MAP":
+								fillBucket(trend_map_countList, _task);
+								break;
+							case "REDUCE":
+								fillBucket(trend_reduce_countList, _task);
+								break;
+							default:
+								console.warn("Task type not match:", task.tags.taskType, task);
+						}
+					});
+
+				$scope.scheduleCategory = [];
+				for(i = 0 ; i < Math.max(trend_map_countList.length, trend_reduce_countList.length) ; i += 1) {
+					$scope.scheduleCategory.push(Time.format(startTime + i * TREND_INTERVAL).replace(" ", "\n"));
+				}
+
+				$scope.scheduleSeries = [{
+					name: "Map Task Count",
+					type: "line",
+					showSymbol: false,
+					areaStyle: {normal: {}},
+					data: trend_map_countList
+				}, {
+					name: "Reduce Task Count",
+					type: "line",
+					showSymbol: false,
+					areaStyle: {normal: {}},
+					data: trend_reduce_countList
+				}];
+
+				// ======================= Bucket Distribution ======================
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						task._bucketStart = Math.floor((task.startTime - startTime) / timeDes);
+						task._bucketEnd = Math.floor((task.endTime - startTime) / timeDes);
+						task._duration = task.endTime - task.startTime;
+						task._durationBucket = common.number.inRange(DURATION_BUCKETS, task._duration);
+					});
+
+				// ==================================================================
+				// =                      Schedule Distribution                     =
+				// ==================================================================
+				function fillBucket(countList, task, maxCount) {
+					for(var bucketId = task._bucketStart ; bucketId <= task._bucketEnd ; bucketId += 1) {
+						var count = countList[bucketId] = (countList[bucketId] || 0) + 1;
+						maxCount = Math.max(maxCount, count);
+					}
+					return maxCount;
+				}
+
+				function getHeatMapOption(categoryList, maxCount) {
+					var option = getCommonHeatMapOption(categoryList, maxCount);
+					return common.merge(option, {
+						tooltip: {
+							formatter: function (point) {
+								if(point.data) {
+									return categoryList[point.data[1]] + ":<br/>" +
+										'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+										$scope.bucketScheduleCategory[point.data[0]] + ": " +
+										point.data[2];
+								}
+								return "";
+							}
+						}
+					});
+				}
+
+				function bucketToSeries(categoryList, buckets, name) {
+					var bucket_data = $.map(categoryList, function (category, index) {
+						var list = [];
+						var dataList = buckets[category] || [];
+						for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) {
+							list.push([i, index, dataList[i] || 0]);
+						}
+						return list;
+					});
+
+					return [common.merge(getCommonHeatMapSeries(name, bucket_data), {
+						label: {
+							normal: {
+								show: true,
+								formatter: function (point) {
+									if(point.data[2] === 0) return "-";
+									return " ";
+								}
+							}
+						}
+					})];
+				}
+
+				// ======================== Status Statistic ========================
+				var bucket_status = {};
+				var bucket_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = bucket_status[task.taskStatus] = (bucket_status[task.taskStatus] || []);
+
+						bucket_status_maxCount = fillBucket(countList, task, bucket_status_maxCount);
+					});
+
+				$scope.statusSeries = bucketToSeries(TASK_STATUS, bucket_status, "Task Status");
+				$scope.statusOption = getHeatMapOption(TASK_STATUS, bucket_status_maxCount);
+
+				// ======================= Duration Statistic =======================
+				var TASK_DURATION = [0, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000];
+				var bucket_durations = {};
+				var bucket_durations_maxCount = 0;
+
+				var TASK_DURATION_DISTRIBUTION = $.map(TASK_DURATION, function (start, i) {
+					var end = TASK_DURATION[i + 1];
+					if(i === 0) {
+						return "<" + Time.diffStr(end);
+					} else if(end) {
+						return Time.diffStr(start) + "~" + Time.diffStr(end);
+					}
+					return ">" + Time.diffStr(start);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_DURATION_DISTRIBUTION[common.number.inRange(TASK_DURATION, task._duration)];
+						var countList = bucket_durations[durationBucket] = (bucket_durations[durationBucket] || []);
+
+						bucket_durations_maxCount = fillBucket(countList, task, bucket_durations_maxCount);
+					});
+
+				$scope.durationSeries = bucketToSeries(TASK_DURATION_DISTRIBUTION, bucket_durations, "Task Duration Distribution");
+				$scope.durationOption = getHeatMapOption(TASK_DURATION_DISTRIBUTION, bucket_durations_maxCount);
+
+				// ======================= HDFS Read Statistic ======================
+				var TASK_HDFS_BYTES = [0, 5 * 1024 * 1024, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024];
+				var bucket_hdfs_reads = {};
+				var bucket_hdfs_reads_maxCount = 0;
+
+				var TASK_HDFS_DISTRIBUTION = $.map(TASK_HDFS_BYTES, function (start, i) {
+					var end = TASK_HDFS_BYTES[i + 1];
+					if(i === 0) {
+						return "<" + common.number.abbr(end, true);
+					} else if(end) {
+						return common.number.abbr(start, true) + "~" + common.number.abbr(end, true);
+					}
+					return ">" + common.number.abbr(start, true);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)];
+						var countList = bucket_hdfs_reads[durationBucket] = (bucket_hdfs_reads[durationBucket] || []);
+
+						bucket_hdfs_reads_maxCount = fillBucket(countList, task, bucket_hdfs_reads_maxCount);
+					});
+
+				$scope.hdfsReadSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads, "Task HDFS Read Distribution");
+				$scope.hdfsReadOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads_maxCount);
+
+				// ====================== HDFS Write Statistic ======================
+				var bucket_hdfs_writes = {};
+				var bucket_hdfs_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)];
+						var countList = bucket_hdfs_writes[durationBucket] = (bucket_hdfs_writes[durationBucket] || []);
+
+						bucket_hdfs_writes_maxCount = fillBucket(countList, task, bucket_hdfs_writes_maxCount);
+					});
+
+				$scope.hdfsWriteSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes, "Task HDFS Write Distribution");
+				$scope.hdfsWriteOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes_maxCount);
+
+				// ====================== Local Read Statistic ======================
+				var TASK_LOCAL_BYTES = [0, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024];
+				var bucket_local_reads = {};
+				var bucket_local_reads_maxCount = 0;
+
+				var TASK_LOCAL_DISTRIBUTION = $.map(TASK_LOCAL_BYTES, function (start, i) {
+					var end = TASK_LOCAL_BYTES[i + 1];
+					if(i === 0) {
+						return "<" + common.number.abbr(end, true);
+					} else if(end) {
+						return common.number.abbr(start, true) + "~" + common.number.abbr(end, true);
+					}
+					return ">" + common.number.abbr(start, true);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)];
+						var countList = bucket_local_reads[durationBucket] = (bucket_local_reads[durationBucket] || []);
+
+						bucket_local_reads_maxCount = fillBucket(countList, task, bucket_local_reads_maxCount);
+					});
+
+				$scope.localReadSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_reads, "Task Local Read Distribution");
+				$scope.localReadOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_reads_maxCount);
+
+				// ====================== Local Write Statistic =====================
+				var bucket_local_writes = {};
+				var bucket_local_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)];
+						var countList = bucket_local_writes[durationBucket] = (bucket_local_writes[durationBucket] || []);
+
+						bucket_local_writes_maxCount = fillBucket(countList, task, bucket_local_writes_maxCount);
+					});
+
+				$scope.localWriteSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_writes, "Task Local Write Distribution");
+				$scope.localWriteOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_writes_maxCount);
+
+				// ==================================================================
+				// =                      Duration Distribution                     =
+				// ==================================================================
+				function fillDurationBucket(countList, task, maxCount) {
+					var count = countList[task._durationBucket] = (countList[task._durationBucket] || 0) + 1;
+					maxCount = Math.max(maxCount, count);
+					return maxCount;
+				}
+
+				function getDurationHeatMapOption(categoryList, maxCount) {
+					var option = getCommonHeatMapOption(categoryList, maxCount);
+					return common.merge(option, {
+						tooltip: {
+							formatter: function (point) {
+								if(point.data) {
+									return categoryList[point.data[1]] + ":<br/>" +
+										'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+										$scope.bucketDurationCategory[point.data[0]] + ": " +
+										point.data[2];
+								}
+								return "";
+							}
+						}
+					});
+				}
+
+				function bucketToDurationSeries(categoryList, buckets, name) {
+					var bucket_data = $.map(categoryList, function (category, index) {
+						var list = [];
+						var dataList = buckets[category] || [];
+						for(var i = 0 ; i < DURATION_BUCKETS.length ; i += 1) {
+							list.push([i, index, dataList[i] || 0]);
+						}
+						return list;
+					});
+
+					return [common.merge(getCommonHeatMapSeries(name, bucket_data), {
+						label: {
+							normal: {
+								show: true,
+								formatter: function (point) {
+									if(point.data[2] === 0) return "-";
+									return point.data[2] + "";
+								}
+							}
+						}
+					})];
+				}
+
+				// ======================== Status Statistic ========================
+				var duration_status = {};
+				var duration_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = duration_status[task.taskStatus] = (duration_status[task.taskStatus] || []);
+
+						duration_status_maxCount = fillDurationBucket(countList, task, duration_status_maxCount);
+					});
+
+				$scope.durationStatusSeries = bucketToDurationSeries(TASK_STATUS, duration_status, "Task Status");
+				$scope.durationStatusOption = getDurationHeatMapOption(TASK_STATUS, duration_status_maxCount);
+
+				// ===================== Map / Reduce Statistic =====================
+				var mapReduce_status = {};
+				var mapReduce_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = mapReduce_status[task.tags.taskType] = (mapReduce_status[task.tags.taskType] || []);
+
+						mapReduce_status_maxCount = fillDurationBucket(countList, task, mapReduce_status_maxCount);
+					});
+
+				$scope.durationMapReduceSeries = bucketToDurationSeries(TASK_TYPE, mapReduce_status, "Task Type");
+				$scope.durationMapReduceOption = getDurationHeatMapOption(TASK_TYPE, mapReduce_status_maxCount);
+
+				// ======================= HDFS Read Statistic ======================
+				var duration_hdfs_reads = {};
+				var duration_hdfs_reads_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)];
+						var countList = duration_hdfs_reads[durationBucket] = (duration_hdfs_reads[durationBucket] || []);
+
+						duration_hdfs_reads_maxCount = fillDurationBucket(countList, task, duration_hdfs_reads_maxCount);
+					});
+
+				$scope.durationHdfsReadSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads, "Task HDFS Read Distribution");
+				$scope.durationHdfsReadOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads_maxCount);
+
+				// ====================== HDFS Write Statistic ======================
+				var duration_hdfs_writes = {};
+				var duration_hdfs_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)];
+						var countList = duration_hdfs_writes[durationBucket] = (duration_hdfs_writes[durationBucket] || []);
+
+						duration_hdfs_writes_maxCount = fillDurationBucket(countList, task, duration_hdfs_writes_maxCount);
+					});
+
+				$scope.durationHdfsWriteSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes, "Task HDFS Write Distribution");
+				$scope.durationHdfsWriteOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes_maxCount);
+
+				// ====================== Local Read Statistic ======================
+				var duration_local_reads = {};
+				var duration_local_reads_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)];
+						var countList = duration_local_reads[durationBucket] = (duration_local_reads[durationBucket] || []);
+
+						duration_local_reads_maxCount = fillDurationBucket(countList, task, duration_local_reads_maxCount);
+					});
+
+				$scope.durationLocalReadSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_reads, "Task Local Read Distribution");
+				$scope.durationLocalReadOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_reads_maxCount);
+
+				// ====================== Local Write Statistic =====================
+				var duration_local_writes = {};
+				var duration_local_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)];
+						var countList = duration_local_writes[durationBucket] = (duration_local_writes[durationBucket] || []);
+
+						duration_local_writes_maxCount = fillDurationBucket(countList, task, duration_local_writes_maxCount);
+					});
+
+				$scope.durationLocalWriteSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_writes, "Task Local Write Distribution");
+				$scope.durationLocalWriteOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_writes_maxCount);
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
new file mode 100644
index 0000000..ff9ed5e
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
@@ -0,0 +1,239 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var JOB_STATES = ["NEW", "NEW_SAVING", "SUBMITTED", "ACCEPTED", "RUNNING", "FINISHED", "SUCCEEDED", "FAILED", "KILLED"];
+
+		jpmApp.controller("listCtrl", function ($wrapState, $element, $scope, $q, PageConfig, Time, Entity, JPM) {
+			// Initialization
+			PageConfig.title = "YARN Jobs";
+			$scope.getStateClass = JPM.getStateClass;
+			$scope.tableScope = {};
+
+			$scope.site = $wrapState.param.siteId;
+			$scope.searchPathList = [["tags", "jobId"], ["tags", "user"], ["tags", "queue"], ["currentState"]];
+
+			function getCommonOption(left) {
+				return {
+					grid: {
+						left: left,
+						bottom: 20,
+						containLabel: false
+					}
+				};
+			}
+
+			$scope.chartLeftOption = getCommonOption(45);
+			$scope.chartRightOption = getCommonOption(80);
+
+			$scope.fillSearch = function (key) {
+				$("#jobList").find(".search-box input").val(key).trigger('input');
+			};
+
+			$scope.refreshList = function () {
+				var startTime = Time.startTime();
+				var endTime = Time.endTime();
+
+				// ==========================================================
+				// =                        Job List                        =
+				// ==========================================================
+
+				/**
+				 * @namespace
+				 * @property {[]} jobList
+				 * @property {{}} jobList.tags						unique job key
+				 * @property {string} jobList.tags.jobId			Job Id
+				 * @property {string} jobList.tags.user				Submit user
+				 * @property {string} jobList.tags.queue			Queue
+				 * @property {string} jobList.currentState			Job state
+				 * @property {string} jobList.submissionTime		Submission time
+				 * @property {string} jobList.startTime				Start time
+				 * @property {string} jobList.endTime				End time
+				 * @property {string} jobList.numTotalMaps			Maps count
+				 * @property {string} jobList.numTotalReduces		Reduce count
+				 * @property {string} jobList.runningContainers		Running container count
+				 */
+
+				$scope.jobList = Entity.merge($scope.jobList, JPM.jobList({site: $scope.site}, startTime, endTime, [
+					"jobId",
+					"jobDefId",
+					"jobName",
+					"jobExecId",
+					"currentState",
+					"user",
+					"queue",
+					"submissionTime",
+					"startTime",
+					"endTime",
+					"numTotalMaps",
+					"numTotalReduces",
+					"runningContainers"
+				], 100000));
+				$scope.jobStateList = [];
+
+				$scope.jobList._then(function () {
+					var now = Time();
+					var jobStates = {};
+					$.each($scope.jobList, function (i, job) {
+						jobStates[job.currentState] = (jobStates[job.currentState] || 0) + 1;
+						job.duration = Time.diff(job.startTime, job.endTime || now);
+					});
+
+					$scope.jobStateList = $.map(JOB_STATES, function (state) {
+						var value = jobStates[state];
+						delete  jobStates[state];
+						if(!value) return null;
+						return {
+							key: state,
+							value: value
+						};
+					});
+
+					$.each(jobStates, function (key, value) {
+						$scope.jobStateList.push({
+							key: key,
+							value: value
+						});
+					});
+				});
+
+				// ===========================================================
+				// =                     Statistic Trend                     =
+				// ===========================================================
+				var interval = Time.diffInterval(startTime, endTime);
+				var intervalMin = interval / 1000 / 60;
+				var trendStartTime = Time.align(startTime, interval);
+				var trendEndTime = Time.align(endTime, interval);
+				var trendStartTimestamp = trendStartTime.valueOf();
+
+				// ==================== Running Job Trend ====================
+				JPM.get(JPM.getQuery("MR_JOB_COUNT"), {
+					site: $scope.site,
+					intervalInSecs: interval / 1000,
+					durationBegin: Time.format(trendStartTime),
+					durationEnd: Time.format(trendEndTime)
+				}).then(
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.jobCounts
+					 */
+					function (res) {
+						var data = res.data;
+						var jobCounts = data.jobCounts;
+						var jobTypesData = {};
+						$.each(jobCounts,
+							/**
+							 * @param index
+							 * @param {{}} jobCount
+							 * @param {{}} jobCount.timeBucket
+							 * @param {{}} jobCount.jobCountByType
+							 */
+							function (index, jobCount) {
+								$.each(jobCount.jobCountByType, function (type, count) {
+									var countList = jobTypesData[type] = jobTypesData[type] || [];
+									countList[index] = count;
+								});
+							});
+
+						$scope.runningTrendSeries = $.map(jobTypesData, function (countList, type) {
+							var dataList = [];
+							for(var i = 0 ; i < jobCounts.length ; i += 1) {
+								dataList[i] = {
+									x: trendStartTimestamp + i * interval,
+									y: countList[i] || 0
+								};
+							}
+
+							return {
+								name: type,
+								type: "line",
+								stack: "job",
+								showSymbol: false,
+								areaStyle: {normal: {}},
+								data: dataList
+							};
+						});
+					});
+
+				// ================= Running Container Trend =================
+				JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.runningcontainers", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true)._promise.then(function (list) {
+					$scope.runningContainersSeries = [JPM.metricsToSeries("Running Containers", list, {areaStyle: {normal: {}}})];
+				});
+
+				// ================= Allocated vCores Trend ==================
+				JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedvcores", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true)._promise.then(function (list) {
+					$scope.allocatedvcoresSeries = [JPM.metricsToSeries("Allocated vCores", list, {areaStyle: {normal: {}}})];
+				});
+
+				// ==================== AllocatedMB Trend ====================
+				var allocatedMBEntities = JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedmb", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true);
+
+				var totalMemoryEntities = JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.totalmemory", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true);
+
+				$q.all([allocatedMBEntities._promise, totalMemoryEntities._promise]).then(function (args) {
+					var allocatedMBList = args[0];
+					var totalMemoryList = args[1];
+
+					var mergedList = $.map(allocatedMBList, function (obj, index) {
+						var value = obj.value[0] / totalMemoryList[index].value[0] * 100 || 0;
+						return $.extend({}, obj, {
+							value: [value]
+						});
+					});
+
+					$scope.allocatedMBSeries = [JPM.metricsToSeries("Allocated GB", mergedList, {areaStyle: {normal: {}}})];
+					$scope.allocatedMBOption = $.extend({}, $scope.chartRightOption, {
+						yAxis: [{
+							axisLabel: {
+								formatter: "{value}%"
+							},
+							max: 100
+						}],
+						tooltip: {
+							formatter: function (points) {
+								var point = points[0];
+								var index = point.dataIndex;
+								return point.name + "<br/>" +
+									'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+									point.seriesName + ": " + common.number.format(allocatedMBList[index].value[0] / 1024, 2);
+							}
+						}
+					});
+				});
+			};
+
+			Time.onReload($scope.refreshList, $scope);
+
+			// Load list
+			$scope.refreshList();
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
new file mode 100644
index 0000000..7d7b949
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
@@ -0,0 +1,140 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		jpmApp.controller("overviewCtrl", function ($q, $wrapState, $element, $scope, $timeout, PageConfig, Time, Entity, JPM) {
+			var cache = {};
+			$scope.aggregationMap = {
+				job: "jobId",
+				user: "user",
+				jobType: "jobType"
+			};
+
+			$scope.site = $wrapState.param.siteId;
+
+			PageConfig.title = "Overview";
+
+			$scope.type = "job";
+
+			$scope.commonOption = {
+				animation: false,
+				tooltip: {
+					formatter: function (points) {
+						return points[0].name + "<br/>" +
+								$.map(points, function (point) {
+									return '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+											point.seriesName + ": " +
+										common.number.format(point.value, true);
+								}).reverse().join("<br/>");
+					}
+				},
+				grid: {
+					top: 70
+				},
+				yAxis: [{
+					axisLabel: {formatter: function (value) {
+						return common.number.abbr(value, true);
+					}}
+				}]
+			};
+
+			// ======================================================================
+			// =                          Refresh Overview                          =
+			// ======================================================================
+			$scope.typeChange = function () {
+				$timeout($scope.refresh, 1);
+			};
+
+			// TODO: Optimize the chart count
+			// TODO: ECharts dynamic refresh series bug: https://github.com/ecomfe/echarts/issues/4033
+			$scope.refresh = function () {
+				var startTime = Time.startTime();
+				var endTime = Time.endTime();
+				var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60;
+
+				function getTopList(metric, scopeVariable) {
+					var deferred = $q.defer();
+
+					metric = common.template(metric, {
+						type: $scope.type.toLocaleLowerCase()
+					});
+
+					if(scopeVariable) {
+						$scope[scopeVariable] = [];
+						$scope[scopeVariable]._done = false;
+						$scope[scopeVariable + "List"] = [];
+					}
+
+					var aggregation = $scope.aggregationMap[$scope.type];
+
+					var aggPromise = cache[metric] = cache[metric] || JPM.aggMetricsToEntities(
+						JPM.aggMetrics({site: $scope.site}, metric, [aggregation], "avg(value), sum(value) desc", intervalMin, startTime, endTime, 10)
+					)._promise.then(function (list) {
+						var series = $.map(list, function (metrics) {
+							return JPM.metricsToSeries(metrics[0].tags[aggregation], metrics, {
+								stack: "stack",
+								areaStyle: {normal: {}}
+							});
+						});
+
+						var topList = $.map(series, function (series) {
+							return {
+								name: series.name,
+								total: common.number.sum(series.data, "y") * intervalMin
+							};
+						}).sort(function (a, b) {
+							return b.total - a.total;
+						});
+
+						return [series, topList];
+					});
+
+					aggPromise.then(function (args) {
+						if(scopeVariable) {
+							$scope[scopeVariable] = args[0];
+							$scope[scopeVariable]._done = true;
+							$scope[scopeVariable + "List"] = args[1];
+						}
+					});
+
+					return deferred.promise;
+				}
+
+				getTopList("hadoop.${type}.history.minute.cpu_milliseconds", "cpuUsageSeries");
+				getTopList("hadoop.${type}.history.minute.physical_memory_bytes", "physicalMemorySeries");
+				getTopList("hadoop.${type}.history.minute.virtual_memory_bytes", "virtualMemorySeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_bytes_read", "hdfsBtyesReadSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_bytes_written", "hdfsBtyesWrittenSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_read_ops", "hdfsReadOpsSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_write_ops", "hdfsWriteOpsSeries");
+				getTopList("hadoop.${type}.history.minute.file_bytes_read", "fileBytesReadSeries");
+				getTopList("hadoop.${type}.history.minute.file_bytes_written", "fileBytesWrittenSeries");
+			};
+
+			Time.onReload(function () {
+				cache = {};
+				$scope.refresh();
+			}, $scope);
+			$scope.refresh();
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
new file mode 100644
index 0000000..6dff7a1
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
@@ -0,0 +1,386 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var colorMap = {
+			"SUCCEEDED": "#00a65a",
+			"FAILED": "#dd4b39",
+			"KILLED": "#CCCCCC",
+			"ERROR": "#f39c12"
+		};
+
+		var DURATION_BUCKETS = [0, 30, 60, 120, 300, 600, 1800, 3600, 2 * 3600, 3 * 3600];
+
+		jpmApp.controller("statisticCtrl", function ($wrapState, $element, $scope, PageConfig, Time, Entity, JPM, Chart) {
+			$scope.site = $wrapState.param.siteId;
+
+			PageConfig.title = "Job Statistics";
+
+			$scope.type = "hourly";
+
+			$scope.switchType = function (type) {
+				$scope.type = type;
+				$scope.refreshDistribution();
+			};
+
+			// ===============================================================
+			// =                   Time Level Distribution                   =
+			// ===============================================================
+			function parseDayBuckets(startTime, endTime) {
+				startTime = startTime.clone().hour(0).minute(0).second(0);
+				endTime = endTime.clone().hour(0).minute(0).second(0);
+
+				var _buckets = [];
+				var _start = startTime.clone();
+
+				do {
+					var _end = _start.clone().date(1).add(1, "month").date(0);
+					if (_end.isAfter(endTime)) {
+						_end = endTime.clone();
+					}
+					var _dayDes = moment.duration(_end.diff(_start)).asDays() + 1;
+					_buckets.push(_dayDes);
+
+					_start = _end.clone().add(1, "day");
+				} while (!_start.isAfter(endTime));
+
+				return _buckets;
+			}
+
+			var distributionCache = {};
+			$scope.distributionSelectedType = "";
+			$scope.distributionSelectedIndex = -1;
+
+			$scope.jobDistributionSeriesOption = {};
+
+			$scope.refreshDistribution = function () {
+				var type = $scope.type;
+				var startTime, endTime;
+				var metric, minInterval, field;
+				$scope.distributionSelectedIndex = -1;
+
+				switch (type) {
+					case "monthly":
+						endTime = Time("monthEnd");
+						startTime = endTime.clone().subtract(365, "day").date(1).hours(0).minutes(0).seconds(0);
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440;
+						field = "max(value)";
+						break;
+					case "weekly":
+						endTime = Time("weekEnd");
+						startTime = Time("week").subtract(7 * 12, "day");
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440 * 7;
+						field = "sum(value)";
+						break;
+					case "daily":
+						endTime = Time("dayEnd");
+						startTime = Time("day").subtract(31, "day");
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440;
+						field = "max(value)";
+						break;
+					case "hourly":
+						endTime = Time("hourEnd");
+						startTime = Time("day").subtract(2, "day");
+						metric = "hadoop.job.history.hour.count";
+						minInterval = 60;
+						field = "sum(value)";
+						break;
+				}
+
+				$scope.jobDistributionSeries = [];
+				$scope.jobDistributionCategoryFunc = function (value) {
+					if(type === "hourly") {
+						return Time.format(value, "HH:mm");
+					}
+					return Time.format(value, "MM-DD");
+				};
+				var promise = distributionCache[type] = distributionCache[type] || JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, metric, ["jobStatus"], field, minInterval, startTime, endTime)
+				)._promise.then(function (list) {
+						if(type === "monthly") {
+							var buckets = parseDayBuckets(startTime, endTime);
+							list = $.map(list, function (units) {
+								// Merge by day buckets
+								units = units.concat();
+								return [$.map(buckets, function (dayCount) {
+									var subUnits = units.splice(0, dayCount);
+									var sum = common.number.sum(subUnits, ["value", 0]);
+
+									return $.extend({}, subUnits[0], {
+										value: [sum]
+									});
+								})];
+							});
+						}
+						return list;
+				}).then(function(list) {
+					/**
+					 * @param {Object[]}	metrics
+					 * @param {{}}			metrics[].tags
+					 * @param {string}		metrics[].tags.jobStatus
+					 */
+					var series = $.map(list, function (metrics) {
+						return JPM.metricsToSeries(metrics[0].tags.jobStatus, metrics, {
+							stack: "stack",
+							type: "bar",
+							itemStyle: {
+								normal: {
+									borderWidth: 2
+								}
+							}
+						});
+					});
+					common.array.doSort(series, "name", true, ["SUCCEEDED", "FAILED", "KILLED", "ERROR"]);
+					$scope.jobDistributionSeriesOption.color = $.map(series, function (series) {
+						return colorMap[series.name];
+					});
+
+					return series;
+				});
+
+				promise.then(function(series) {
+					$scope.jobDistributionSeries = series;
+				});
+			};
+
+			// ==============================================================
+			// =                         Drill Down                         =
+			// ==============================================================
+			$scope.commonChartOption = {
+				grid: {
+					left: 42,
+					bottom: 60,
+					containLabel: false
+				},
+				yAxis: [{
+					minInterval: 1
+				}],
+				xAxis: {
+					axisLabel: {
+						interval: 0
+					}
+				}
+			};
+			$scope.commonTrendChartOption = {
+				yAxis: [{
+					minInterval: 1
+				}],
+				grid: {
+					top: 60,
+					left: 42,
+					bottom: 20,
+					containLabel: false
+				}
+			};
+
+			$scope.topUserJobCountSeries = [];
+			$scope.topTypeJobCountSeries = [];
+
+			$scope.drillDownCategoryFunc = function (value) {
+				switch ($scope.type) {
+					case "monthly":
+						return Time.format(value, "MM-DD");
+					case "weekly":
+					case "daily":
+						return Time.format(value, "MM-DD HH:mm");
+					default:
+						return Time.format(value, "HH:mm");
+				}
+			};
+
+			$scope.bucketDurationCategory = [];
+			$.each(DURATION_BUCKETS, function (i, start) {
+				var end = DURATION_BUCKETS[i + 1];
+
+				start *= 1000;
+				end *= 1000;
+
+				if(!start) {
+					$scope.bucketDurationCategory.push("<" + Time.diffStr(end));
+				} else if(!end) {
+					$scope.bucketDurationCategory.push(">" + Time.diffStr(start));
+				} else {
+					$scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end));
+				}
+			});
+
+			function flattenTrendSeries(name, series) {
+				var len = series.length;
+				var category = [];
+				var data = [];
+				var needBreakLine = series.length > 6;
+				$.each(series, function (i, series) {
+					category.push((needBreakLine && i % 2 !== 0 ? "\n" : "") + series.name);
+					data.push(common.number.sum(series.data, ["y"]));
+				});
+				return {
+					category: category.reverse(),
+					series: [{
+						name: name,
+						data: data.reverse(),
+						type: "bar",
+						itemStyle: {
+							normal: {
+								color: function (point) {
+									return Chart.color[len - point.dataIndex - 1];
+								}
+							}
+						}
+					}]
+				};
+			}
+
+			$scope.distributionClick = function (event) {
+				if(event.componentType !== "series") return;
+
+				$scope.distributionSelectedType = event.seriesName;
+				$scope.distributionSelectedIndex = event.dataIndex;
+				var timestamp = 0;
+
+				// Highlight logic
+				$.each($scope.jobDistributionSeries, function (i, series) {
+					timestamp = series.data[$scope.distributionSelectedIndex].x;
+
+					common.merge(series, {
+						itemStyle: {
+							normal: {
+								color: function (point) {
+									if(point.seriesName === $scope.distributionSelectedType && point.dataIndex === $scope.distributionSelectedIndex) {
+										return "#60C0DD";
+									}
+									return colorMap[point.seriesName];
+								}
+							}
+						}
+					});
+				});
+
+				// Data fetch
+				var startTime = Time(timestamp);
+				var endTime;
+
+				switch ($scope.type) {
+					case "monthly":
+						endTime = startTime.clone().add(1, "month").subtract(1, "s");
+						break;
+					case "weekly":
+						endTime = startTime.clone().add(7, "day").subtract(1, "s");
+						break;
+					case "daily":
+						endTime = startTime.clone().add(1, "day").subtract(1, "s");
+						break;
+					case "hourly":
+						endTime = startTime.clone().add(1, "hour").subtract(1, "s");
+						break;
+				}
+
+				var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60;
+
+				// ===================== Top User Job Count =====================
+				$scope.topUserJobCountSeries = [];
+				$scope.topUserJobCountTrendSeries = [];
+				JPM.aggMetricsToEntities(
+					JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["user"], "count desc", intervalMin, startTime, endTime, 10, 1000000)
+				)._promise.then(function (list) {
+					$scope.topUserJobCountTrendSeries = $.map(list, function (subList) {
+						return JPM.metricsToSeries(subList[0].tags.user, subList, {
+							stack: "user",
+							areaStyle: {normal: {}}
+						});
+					});
+
+					var flatten = flattenTrendSeries("User", $scope.topUserJobCountTrendSeries);
+					$scope.topUserJobCountSeries = flatten.series;
+					$scope.topUserJobCountSeriesCategory = flatten.category;
+				});
+
+				// ===================== Top Type Job Count =====================
+				$scope.topTypeJobCountSeries = [];
+				$scope.topTypeJobCountTrendSeries = [];
+				JPM.aggMetricsToEntities(
+					JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["jobType"], "count desc", intervalMin, startTime, endTime, 10, 1000000)
+				)._promise.then(function (list) {
+					$scope.topTypeJobCountTrendSeries = $.map(list, function (subList) {
+						return JPM.metricsToSeries(subList[0].tags.jobType, subList, {
+							stack: "type",
+							areaStyle: {normal: {}}
+						});
+					});
+
+					var flatten = flattenTrendSeries("Job Type", $scope.topTypeJobCountTrendSeries);
+					$scope.topTypeJobCountSeries = flatten.series;
+					$scope.topTypeJobCountSeriesCategory = flatten.category;
+				});
+
+				if($scope.distributionSelectedType === "FAILED") {
+					// ====================== Failure Job List ======================
+					$scope.jobList = JPM.jobList({site: $scope.site, currentState: "FAILED"}, startTime, endTime, [
+						"jobId",
+						"jobName",
+						"user",
+						"startTime",
+						"jobType"
+					]);
+				} else {
+					// ============== Job Duration Distribution Count ===============
+					$scope.jobList = null;
+
+					$scope.jobDurationDistributionSeries = [];
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.jobTypes
+					 * @param {[]} res.data.jobCounts
+					 */
+					JPM.jobDistribution($scope.site, $scope.type, DURATION_BUCKETS.join(","), startTime, endTime).then(function (res) {
+						var data = res.data;
+						var jobTypes = {};
+						$.each(data.jobTypes, function (i, type) {
+							jobTypes[type] = [];
+						});
+						$.each(data.jobCounts, function (index, statistic) {
+							$.each(statistic.jobCountByType, function (type, value) {
+								jobTypes[type][index] = value;
+							});
+						});
+
+						$scope.jobDurationDistributionSeries = $.map(jobTypes, function (list, type) {
+							return {
+								name: type,
+								data: list,
+								type: "bar",
+								stack: "jobType"
+							};
+						});
+					});
+				}
+
+				return true;
+			};
+
+			$scope.refreshDistribution();
+		});
+	});
+})();


Mime
View raw message