[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 <jilji...@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 <ji...@apache.org>
Authored: Wed Sep 28 13:38:17 2016 +0800
Committer: jiljiang <jilji...@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();
+               });
+       });
+})();

Reply via email to