http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
new file mode 100644
index 0000000..fafe699
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
@@ -0,0 +1,489 @@
+/*
+ * 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` is global function that for application to set up 
'controller', 'service', 'directive', 'route' in Eagle
+        */
+       var jpmApp = register(['ngRoute', 'ngAnimate', 'ui.router', 
'eagle.service']);
+
+       jpmApp.route("jpmList", {
+               url: "/jpm/list?startTime&endTime",
+               site: true,
+               templateUrl: "partials/job/list.html",
+               controller: "listCtrl",
+               resolve: { time: true }
+       }).route("jpmOverview", {
+               url: "/jpm/overview?startTime&endTime",
+               site: true,
+               templateUrl: "partials/job/overview.html",
+               controller: "overviewCtrl",
+               resolve: { time: true }
+       }).route("jpmStatistics", {
+               url: "/jpm/statistics",
+               site: true,
+               templateUrl: "partials/job/statistic.html",
+               controller: "statisticCtrl"
+       }).route("jpmDetail", {
+               url: "/jpm/detail/:jobId",
+               site: true,
+               templateUrl: "partials/job/detail.html",
+               controller: "detailCtrl"
+       }).route("jpmJobTask", {
+               url: "/jpm/jobTask/:jobId?startTime&endTime",
+               site: true,
+               templateUrl: "partials/job/task.html",
+               controller: "jobTaskCtrl"
+       }).route("jpmCompare", {
+               url: "/jpm/compare/:jobDefId?from&to",
+               site: true,
+               reloadOnSearch: false,
+               templateUrl: "partials/job/compare.html",
+               controller: "compareCtrl"
+       });
+
+       jpmApp.portal({name: "YARN Jobs", icon: "taxi", list: [
+               {name: "Overview", path: "jpm/overview"},
+               {name: "Job Statistics", path: "jpm/statistics"},
+               {name: "Job List", path: "jpm/list"}
+       ]}, true);
+
+       jpmApp.service("JPM", function ($q, $http, Time, Site, Application) {
+               var JPM = window._JPM = {};
+
+               // TODO: timestamp support
+               JPM.QUERY_LIST = 
'${baseURL}/rest/entities?query=${query}[${condition}]{${fields}}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+               JPM.QUERY_GROUPS = 
'${baseURL}/rest/entities?query=${query}[${condition}]<${groups}>{${field}}${order}${top}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+               JPM.QUERY_GROUPS_INTERVAL = 
'${baseURL}/rest/entities?query=${query}[${condition}]<${groups}>{${field}}${order}${top}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}&intervalmin=${intervalMin}&timeSeries=true';
+               JPM.QUERY_METRICS = 
'${baseURL}/rest/entities?query=GenericMetricService[${condition}]{*}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+               JPM.QUERY_METRICS_AGG = 
'${baseURL}/rest/entities?query=GenericMetricService[${condition}]<${groups}>{${field}}${order}${top}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+               JPM.QUERY_METRICS_INTERVAL = 
'${baseURL}/rest/entities?query=GenericMetricService[${condition}]<${groups}>{${field}}${order}${top}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}&intervalmin=${intervalMin}&timeSeries=true';
+               JPM.QUERY_MR_JOBS = '${baseURL}/rest/mrJobs/search';
+               JPM.QUERY_JOB_LIST = 
'${baseURL}/rest/mrJobs?query=%s[${condition}]{${fields}}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+               JPM.QUERY_JOB_STATISTIC = 
'${baseURL}/rest/mrJobs/jobCountsByDuration?site=${site}&timeDistInSecs=${times}&startTime=${startTime}&endTime=${endTime}&jobType=${jobType}';
+               JPM.QUERY_TASK_STATISTIC = 
'${baseURL}/rest/mrTasks/taskCountsByDuration?jobId=${jobId}&site=${site}&timeDistInSecs=${times}&top=${top}';
+
+               JPM.QUERY_MR_JOB_COUNT = 
'${baseURL}/rest/mrJobs/runningJobCounts';
+               //JPM.QUERY_MR_JOB_METRIC_TOP = 
'${baseURL}eagle-service/rest/mrJobs/jobMetrics/entities';
+
+               /**
+                * Fetch query content with current site application 
configuration
+                * @param {string} queryName
+                */
+               var getQuery = JPM.getQuery = function(queryName, siteId) {
+                       var baseURL;
+                       siteId = siteId || Site.current().siteId;
+                       var app = Application.find("JPM_WEB_APP", siteId)[0];
+                       var host = app.configuration["service.host"];
+                       var port = app.configuration["service.port"];
+
+                       if(!host && !port) {
+                               baseURL = "";
+                       } else {
+                               if(host === "localhost" || !host) {
+                                       host = location.hostname;
+                               }
+                               if(!port) {
+                                       port = location.port;
+                               }
+                               baseURL = "http://"; + host + ":" + port;
+                       }
+
+                       return common.template(JPM["QUERY_" + queryName], 
{baseURL: baseURL});
+               };
+
+
+               function wrapList(promise) {
+                       var _list = [];
+                       _list._done = false;
+
+                       _list._promise = promise.then(
+                               /**
+                                * @param {{}} res
+                                * @param {{}} res.data
+                                * @param {{}} res.data.obj
+                                */
+                               function (res) {
+                               _list.splice(0);
+                               Array.prototype.push.apply(_list, res.data.obj);
+                               _list._done = true;
+                               return _list;
+                       });
+                       return _list;
+               }
+
+               function toFields(fields) {
+                       return (fields || []).length > 0 ? $.map(fields, 
function (field) {
+                               return "@" + field;
+                       }).join(",") : "*";
+               }
+
+               JPM.get = function (url, params) {
+                       return $http({
+                               url: url,
+                               method: "GET",
+                               params: params
+                       });
+               };
+
+               JPM.condition = function (condition) {
+                       return $.map(condition, function (value, key) {
+                               return "@" + key + '="' + value + '"';
+                       }).join(" AND ");
+               };
+
+               /**
+                * Fetch eagle query list
+                * @param query
+                * @param condition
+                * @param {[]?} groups
+                * @param {string} field
+                * @param {number|null} intervalMin
+                * @param startTime
+                * @param endTime
+                * @param {(number|null)?} top
+                * @param {number?} limit
+                * @return {[]}
+                */
+               JPM.groups = function (query, condition, groups, field, 
intervalMin, startTime, endTime, top, limit) {
+                       var fields = field.split(/\s*,\s*/);
+                       var orderId = -1;
+                       var fieldStr = $.map(fields, function (field, index) {
+                               var matches = field.match(/^([^\s]*)(\s+.*)?$/);
+                               if(matches[2]) {
+                                       orderId = index;
+                               }
+                               return matches[1];
+                       }).join(", ");
+
+                       var config = {
+                               query: query,
+                               condition: JPM.condition(condition),
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime),
+                               groups: toFields(groups),
+                               field: fieldStr,
+                               order: orderId === -1 ? "" : ".{" + 
fields[orderId] + "}",
+                               top: top ? "&top=" + top : "",
+                               intervalMin: intervalMin,
+                               limit: limit || 100000
+                       };
+
+                       var metrics_url = common.template(intervalMin ? 
getQuery("GROUPS_INTERVAL") : getQuery("GROUPS"), config);
+                       var _list = wrapList(JPM.get(metrics_url));
+                       _list._aggInfo = {
+                               groups: groups,
+                               startTime: Time(startTime).valueOf(),
+                               interval: intervalMin * 60 * 1000
+                       };
+                       _list._promise.then(function () {
+                               if(top) _list.reverse();
+                       });
+                       return _list;
+               };
+
+               /**
+                * Fetch eagle query list
+                * @param {string} query
+                * @param {{}?} condition
+                * @param {(string|number|{})?} startTime
+                * @param {(string|number|{})?} endTime
+                * @param {[]?} fields
+                * @param {number?} limit
+                * @return {[]}
+                */
+               JPM.list = function (query, condition, startTime, endTime, 
fields, limit) {
+                       var config = {
+                               query: query,
+                               condition: JPM.condition(condition),
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime),
+                               fields: toFields(fields),
+                               limit: limit || 10000
+                       };
+
+                       return 
wrapList(JPM.get(common.template(getQuery("LIST"), config)));
+               };
+
+               /**
+                * Fetch job list
+                * @param condition
+                * @param startTime
+                * @param endTime
+                * @param {[]?} fields
+                * @param {number?} limit
+                * @return {[]}
+                */
+               JPM.jobList = function (condition, startTime, endTime, fields, 
limit) {
+                       var config = {
+                               condition: JPM.condition(condition),
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime),
+                               fields: toFields(fields),
+                               limit: limit || 10000
+                       };
+
+                       var jobList_url = common.template(getQuery("JOB_LIST"), 
config);
+                       return wrapList(JPM.get(jobList_url));
+               };
+
+               /**
+                * Fetch job metric list
+                * @param condition
+                * @param metric
+                * @param startTime
+                * @param endTime
+                * @param {number?} limit
+                * @return {[]}
+                */
+               JPM.metrics = function (condition, metric, startTime, endTime, 
limit) {
+                       var config = {
+                               condition: JPM.condition(condition),
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime),
+                               metric: metric,
+                               limit: limit || 10000
+                       };
+
+                       var metrics_url = common.template(getQuery("METRICS"), 
config);
+                       var _list = wrapList(JPM.get(metrics_url));
+                       _list._promise.then(function () {
+                               _list.reverse();
+                       });
+                       return _list;
+               };
+
+               /**
+                * Fetch job metric list
+                * @param {{}} condition
+                * @param {string} metric
+                * @param {[]} groups
+                * @param {string} field
+                * @param {number|null|false} intervalMin
+                * @param startTime
+                * @param endTime
+                * @param {number?} top
+                * @param {number?} limit
+                * @return {[]}
+                */
+               JPM.aggMetrics = function (condition, metric, groups, field, 
intervalMin, startTime, endTime, top, limit) {
+                       var fields = field.split(/\s*,\s*/);
+                       var orderId = -1;
+                       var fieldStr = $.map(fields, function (field, index) {
+                               var matches = field.match(/^([^\s]*)(\s+.*)?$/);
+                               if(matches[2]) {
+                                       orderId = index;
+                               }
+                               return matches[1];
+                       }).join(", ");
+
+                       var config = {
+                               condition: JPM.condition(condition),
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime),
+                               metric: metric,
+                               groups: toFields(groups),
+                               field: fieldStr,
+                               order: orderId === -1 ? "" : ".{" + 
fields[orderId] + "}",
+                               top: top ? "&top=" + top : "",
+                               intervalMin: intervalMin,
+                               limit: limit || 100000
+                       };
+
+                       var metrics_url = common.template(intervalMin ? 
getQuery("METRICS_INTERVAL") : getQuery("METRICS_AGG"), config);
+                       var _list = wrapList(JPM.get(metrics_url));
+                       _list._aggInfo = {
+                               groups: groups,
+                               startTime: Time(startTime).valueOf(),
+                               interval: intervalMin * 60 * 1000
+                       };
+                       _list._promise.then(function () {
+                               _list.reverse();
+                       });
+                       return _list;
+               };
+
+               JPM.aggMetricsToEntities = function (list, flatten) {
+                       var _list = [];
+                       _list.done = false;
+                       _list._promise = list._promise.then(function () {
+                               var _startTime = list._aggInfo.startTime;
+                               var _interval = list._aggInfo.interval;
+
+                               $.each(list, function (i, obj) {
+                                       var tags = {};
+                                       $.each(list._aggInfo.groups, function 
(j, group) {
+                                               tags[group] = obj.key[j];
+                                       });
+
+                                       var _subList = $.map(obj.value[0], 
function (value, index) {
+                                               return {
+                                                       timestamp: _startTime + 
index * _interval,
+                                                       value: [value],
+                                                       tags: tags
+                                               };
+                                       });
+
+                                       if(flatten) {
+                                               _list.push.apply(_list, 
_subList);
+                                       } else {
+                                               _list.push(_subList);
+                                       }
+                               });
+                               _list.done = true;
+                               return _list;
+                       });
+                       return _list;
+               };
+
+               /**
+                * Fetch job duration distribution
+                * @param {string} site
+                * @param {string} jobType
+                * @param {string} times
+                * @param {{}} startTime
+                * @param {{}} endTime
+                */
+               JPM.jobDistribution = function (site, jobType, times, 
startTime, endTime) {
+                       var url = common.template(getQuery("JOB_STATISTIC"), {
+                               site: site,
+                               jobType: jobType,
+                               times: times,
+                               startTime: Time.format(startTime),
+                               endTime: Time.format(endTime)
+                       });
+                       return JPM.get(url);
+               };
+
+               JPM.taskDistribution = function (site, jobId, times, top) {
+                       var url = common.template(getQuery("TASK_STATISTIC"), {
+                               site: site,
+                               jobId: jobId,
+                               times: times,
+                               top: top || 10
+                       });
+                       return JPM.get(url);
+               };
+
+               /**
+                * Get job list by sam jobDefId
+                * @param {string} site
+                * @param {string|undefined?} jobDefId
+                * @param {string|undefined?} jobId
+                * @return {[]}
+                */
+               JPM.findMRJobs = function (site, jobDefId, jobId) {
+                       return wrapList(JPM.get(getQuery("MR_JOBS"), {
+                               site: site,
+                               jobDefId: jobDefId,
+                               jobId: jobId
+                       }));
+               };
+
+               /**
+                * Convert Entity list data to Chart supported series
+                * @param name
+                * @param metrics
+                * @param {{}|boolean?} rawData
+                * @param {{}?} option
+                * @return {{name: *, symbol: string, type: string, data: *}}
+                */
+               JPM.metricsToSeries = function(name, metrics, rawData, option) {
+                       if(arguments.length === 3 && typeof rawData === 
"object") {
+                               option = rawData;
+                               rawData = false;
+                       }
+
+                       var data = $.map(metrics, function (metric) {
+                               return rawData ? metric.value[0] : {
+                                       x: metric.timestamp,
+                                       y: metric.value[0]
+                               };
+                       });
+                       return $.extend({
+                               name: name,
+                               symbol: 'none',
+                               type: "line",
+                               data: data
+                       }, option || {});
+               };
+
+               JPM.metricsToInterval = function (metricList, interval) {
+                       if(metricList.length === 0) return [];
+
+                       var list = $.map(metricList, function (metric) {
+                               var timestamp = Math.floor(metric.timestamp / 
interval) * interval;
+                               var remainderPtg = (metric.timestamp % 
interval) / interval;
+                               return {
+                                       timestamp: remainderPtg < 0.5 ? 
timestamp : timestamp + interval,
+                                       value: [metric.value[0]]
+                               };
+                       });
+
+                       var resultList = [list[0]];
+                       for(var i = 1 ; i < list.length ; i += 1) {
+                               var start = list[i - 1];
+                               var end = list[i];
+
+                               var distance = (end.timestamp - 
start.timestamp);
+                               if(distance > 0) {
+                                       var steps = distance / interval;
+                                       var des = (end.value[0] - 
start.value[0]) / steps;
+                                       for (var j = 1; j <= steps; j += 1) {
+                                               resultList.push({
+                                                       timestamp: 
start.timestamp + j * interval,
+                                                       value: [start.value[0] 
+ des * j]
+                                               });
+                                       }
+                               }
+                       }
+                       return resultList;
+               };
+
+               JPM.getStateClass = function (state) {
+                       switch ((state || "").toUpperCase()) {
+                               case "NEW":
+                               case "NEW_SAVING":
+                               case "SUBMITTED":
+                               case "ACCEPTED":
+                                       return "warning";
+                               case "RUNNING":
+                                       return "info";
+                               case "SUCCESS":
+                               case "SUCCEEDED":
+                                       return "success";
+                               case "FINISHED":
+                                       return "primary";
+                               case "FAILED":
+                                       return "danger";
+                       }
+                       return "default";
+               };
+
+               return JPM;
+       });
+
+       jpmApp.requireCSS("style/index.css");
+       jpmApp.require("widget/jobStatistic.js");
+       jpmApp.require("ctrl/overviewCtrl.js");
+       jpmApp.require("ctrl/statisticCtrl.js");
+       jpmApp.require("ctrl/listCtrl.js");
+       jpmApp.require("ctrl/detailCtrl.js");
+       jpmApp.require("ctrl/jobTaskCtrl.js");
+       jpmApp.require("ctrl/compareCtrl.js");
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
new file mode 100644
index 0000000..4ab8140
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
@@ -0,0 +1,274 @@
+<!--
+  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.
+  -->
+
+<div class="row flex">
+       <div class="col-sm-12 col-md-3">
+               <div class="box box-primary">
+                       <div class="box-header with-border">
+                               <h3 class="box-title">
+                                       Summary
+                               </h3>
+                       </div>
+                       <div class="box-body">
+                               <table class="table table-striped">
+                                       <tbody>
+                                               <tr>
+                                                       <th>Def Id</th>
+                                                       <td 
class="text-break">{{jobDefId}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Type</th>
+                                                       
<td>{{jobList[0].tags.jobType}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Site</th>
+                                                       
<td>{{jobList[0].tags.site}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Owner</th>
+                                                       
<td>{{jobList[0].tags.user}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Queue</th>
+                                                       
<td>{{jobList[0].tags.queue}}</td>
+                                               </tr>
+                                       </tbody>
+                               </table>
+                       </div>
+               </div>
+       </div>
+       <div class="col-sm-12 col-md-9">
+               <div class="box box-primary">
+                       <div class="box-header with-border">
+                               <h3 class="box-title">
+                                       Comparison
+                                       <small>
+                                               Click to compare job
+                                               (ctrl + click: set <strong>from 
Job</strong>, shift + click: set <strong>to Job</strong>)
+                                       </small>
+                               </h3>
+                       </div>
+                       <div class="box-body">
+                               <div class="jpm-chart">
+                                       <div chart="trendChart" 
class="jpm-chart-container" series="jobTrendSeries" category="jobTrendCategory"
+                                                ng-click="compareJobSelect" 
option="jobTrendOption"></div>
+                                       <div ng-if="(jobTrendSeries || 
[]).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>
+
+<div class="box box-primary" ng-if="fromJob && toJob">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       Comparison
+               </h3>
+               <div class="box-tools pull-right">
+                       <button type="button" class="btn btn-box-tool" 
data-widget="collapse">
+                               <i class="fa fa-minus"></i>
+                       </button>
+               </div>
+       </div>
+       <div class="box-body">
+               <table class="table table-striped">
+                       <thead>
+                       <tr>
+                               <th>Field</th>
+                               <th>From</th>
+                               <th>To</th>
+                               <th>Field</th>
+                               <th>From</th>
+                               <th>To</th>
+                       </tr>
+                       </thead>
+                       <tbody>
+                       <tr>
+                               <th>
+                                       Job Id
+                                       <a class="fa fa-retweet" 
ng-click="exchangeJobs()"></a>
+                               </th>
+                               <td><a ui-sref="jpmDetail({siteId: site, jobId: 
fromJob.tags.jobId})">{{fromJob.tags.jobId}}</a></td>
+                               <td><a ui-sref="jpmDetail({siteId: site, jobId: 
toJob.tags.jobId})">{{toJob.tags.jobId}}</a></td>
+                               <th>Duration</th>
+                               <td>{{Time.diffStr(fromJob.durationTime)}}</td>
+                               <td>
+                                       {{Time.diffStr(toJob.durationTime)}}
+                                       <span 
class="{{jobCompareClass('durationTime')}}">{{jobCompareValue('durationTime')}}</span>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th>Total Maps</th>
+                               
<td>{{common.number.toFixed(fromJob.numTotalMaps)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.numTotalMaps)}}
+                                       <span 
class="{{jobCompareClass('numTotalMaps')}}">{{jobCompareValue('numTotalMaps')}}</span>
+                               </td>
+                               <th>Total Reduces</th>
+                               
<td>{{common.number.toFixed(fromJob.numTotalReduces)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.numTotalReduces)}}
+                                       <span 
class="{{jobCompareClass('numTotalReduces')}}">{{jobCompareValue('numTotalReduces')}}</span>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th>HDFS Read Bytes</th>
+                               
<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}
+                                       <span 
class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_READ'])}}">
+                                                       
{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_READ'])}}
+                                               </span>
+                               </td>
+                               <th>HDFS Write Bytes</th>
+                               
<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}
+                                       <span 
class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_WRITTEN'])}}">
+                                                       
{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_WRITTEN'])}}
+                                               </span>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th>Local Read Bytes</th>
+                               
<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}
+                                       <span 
class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_READ'])}}">
+                                                       
{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_READ'])}}
+                                               </span>
+                               </td>
+                               <th>Local Write Bytes</th>
+                               
<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}
+                                       <span 
class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_WRITTEN'])}}">
+                                                       
{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_WRITTEN'])}}
+                                               </span>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th>Last Map Duration</th>
+                               
<td>{{common.number.toFixed(fromJob.lastMapDuration)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.lastMapDuration)}}
+                                       <span 
class="{{jobCompareClass('lastMapDuration')}}">{{jobCompareValue('lastMapDuration')}}</span>
+                               </td>
+                               <th>Last Reduce Duration</th>
+                               
<td>{{common.number.toFixed(fromJob.lastReduceDuration)}}</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.lastReduceDuration)}}
+                                       <span 
class="{{jobCompareClass('lastReduceDuration')}}">{{jobCompareValue('lastReduceDuration')}}</span>
+                               </td>
+                       </tr>
+                       <tr>
+                               <th>Data Local Maps</th>
+                               
<td>{{common.number.toFixed(fromJob.dataLocalMapsPercentage * 100)}}%</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.dataLocalMapsPercentage * 100)}}%
+                                       <span 
class="{{jobCompareClass('dataLocalMapsPercentage')}}">{{jobCompareValue('dataLocalMapsPercentage')}}</span>
+                               </td>
+                               <th>Rack Local Maps</th>
+                               
<td>{{common.number.toFixed(fromJob.rackLocalMapsPercentage * 100)}}%</td>
+                               <td>
+                                       
{{common.number.toFixed(toJob.rackLocalMapsPercentage * 100)}}%
+                                       <span 
class="{{jobCompareClass('rackLocalMapsPercentage')}}">{{jobCompareValue('rackLocalMapsPercentage')}}</span>
+                               </td>
+                       </tr>
+                       </tbody>
+               </table>
+
+               <div class="row">
+                       <div class="col-lg-6 col-md-12">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="comparisonChart_Container.series"
+                                                
category="comparisonChart_Container.categories"></div>
+                                       <div 
ng-if="(comparisonChart_Container.series || []).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-lg-6 col-md-12">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="comparisonChart_allocatedMB.series"
+                                                
category="comparisonChart_allocatedMB.categories"></div>
+                                       <div 
ng-if="(comparisonChart_allocatedMB.series || []).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-lg-6 col-md-12">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="comparisonChart_vCores.series"
+                                                
category="comparisonChart_vCores.categories"></div>
+                                       <div 
ng-if="(comparisonChart_vCores.series || []).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-lg-6 col-md-12">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="comparisonChart_taskDistribution.series"
+                                                
category="comparisonChart_taskDistribution.categories"></div>
+                                       <div 
ng-if="(comparisonChart_taskDistribution.series || []).length === 0" 
class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>
+
+<div class="box box-primary" ng-if="jobList.length">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       History Jobs
+               </h3>
+       </div>
+       <div class="box-body">
+               <div sort-table="jobList" sortpath="-startTime">
+                       <table class="table table-bordered table-striped">
+                               <thead>
+                                       <tr>
+                                               <th width="10" 
sortpath="currentState">Status</th>
+                                               <th 
sortpath="tags.jobId">Id</th>
+                                               <th 
sortpath="tags.jobName">Name</th>
+                                               <th width="140" 
sortpath="startTime">Start Time</th>
+                                               <th width="140" 
sortpath="durationTime">Duration</th>
+                                       </tr>
+                               </thead>
+                               <tbody>
+                                       <tr>
+                                               <td><span class="label 
label-{{getStateClass(item.currentState)}}">{{item.currentState}}</span></td>
+                                               <td class="text-no-break">
+                                                       <span 
ng-if="item.tags.jobId === fromJob.tags.jobId">[From]</span>
+                                                       <span 
ng-if="item.tags.jobId === toJob.tags.jobId">[To]</span>
+                                                       <a 
ng-click="compareJobSelect($event, item)">{{item.tags.jobId}}</a>
+                                                       <a class="fa fa-link" 
ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" target="_blank"></a>
+                                               </td>
+                                               <td 
class="text-break">{{item.tags.jobName}}</td>
+                                               
<td>{{Time.format(item.startTime)}}</td>
+                                               
<td>{{Time.diffStr(item.durationTime)}}</td>
+                                       </tr>
+                               </tbody>
+                       </table>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
new file mode 100644
index 0000000..57561ba
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
@@ -0,0 +1,256 @@
+<!--
+  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.
+  -->
+
+<div class="row flex">
+       <div class="col-lg-6 col-md-12">
+               <div class="box box-primary">
+                       <div class="box-header with-border">
+                               <h3 class="box-title">
+                                       Job Info
+                                       <span class="label 
label-{{getStateClass(job.currentState)}}">{{job.currentState}}</span>
+                               </h3>
+                               <div class="pull-right box-tools">
+                                       <a ui-sref="jpmCompare({siteId: site, 
jobDefId: job.tags.jobDefId, to: job.tags.jobId})" class="btn btn-primary 
btn-xs">
+                                               <span class="fa 
fa-code-fork"></span>
+                                               Compare
+                                       </a>
+                               </div>
+                       </div>
+                       <div class="box-body">
+                               <table class="table table-striped">
+                                       <tbody>
+                                               <tr>
+                                                       <th>Job Name</th>
+                                                       <td 
class="text-break">{{job.tags.jobName}}</td>
+                                                       <th>Job Def Id</th>
+                                                       <td 
class="text-break">{{job.tags.jobDefId}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Job Id</th>
+                                                       <td class="text-break">
+                                                               
{{job.tags.jobId}}
+                                                               <a class="fa 
fa-link" href="{{job.trackingUrl}}" target="_blank" ng-if="job.trackingUrl"></a>
+                                                       </td>
+                                                       <th>Job Exec Id</th>
+                                                       <td 
class="text-break">{{job.tags.jobExecId}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>User</th>
+                                                       
<td>{{job.tags.user}}</td>
+                                                       <th>Queue</th>
+                                                       
<td>{{job.tags.queue}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Site</th>
+                                                       
<td>{{job.tags.site}}</td>
+                                                       <th>Job Type</th>
+                                                       
<td>{{job.tags.jobType}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Submission Time</th>
+                                                       
<td>{{Time.format(job.submissionTime)}}</td>
+                                                       <th>Duration</th>
+                                                       <td 
class="text-light-blue">{{Time.diffStr(job.durationTime)}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Start Time</th>
+                                                       
<td>{{Time.format(job.startTime)}}</td>
+                                                       <th>End Time</th>
+                                                       
<td>{{Time.format(job.endTime)}}</td>
+                                               </tr>
+                                       </tbody>
+                               </table>
+                       </div>
+
+                       <div ng-if="!job" class="overlay">
+                               <i class="fa fa-refresh fa-spin"></i>
+                       </div>
+               </div>
+       </div>
+
+       <div class="col-lg-6 col-md-12">
+               <div class="box box-primary">
+                       <div class="box-header with-border">
+                               <h3 class="box-title">
+                                       Map Reduce
+                               </h3>
+                       </div>
+                       <div class="box-body">
+                               <table class="table table-striped">
+                                       <tbody>
+                                               <tr>
+                                                       <th>Finished Maps</th>
+                                                       <td 
class="text-success">{{common.number.toFixed(job.numFinishedMaps)}}</td>
+                                                       <th>Failed Maps</th>
+                                                       <td 
class="text-danger">{{common.number.toFixed(job.numFailedMaps)}}</td>
+                                                       <th>Total Maps</th>
+                                                       
<td>{{common.number.toFixed(job.numTotalMaps)}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Finished 
Reduces</th>
+                                                       <td 
class="text-success">{{common.number.toFixed(job.numFinishedReduces)}}</td>
+                                                       <th>Failed Reduces</th>
+                                                       <td 
class="text-danger">{{common.number.toFixed(job.numFailedReduces)}}</td>
+                                                       <th>Total Reduces</th>
+                                                       
<td>{{common.number.toFixed(job.numTotalReduces)}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Data Local Maps</th>
+                                                       <td>
+                                                               
{{common.number.toFixed(job.dataLocalMaps)}}
+                                                               
({{common.number.toFixed(job.dataLocalMapsPercentage * 100)}}%)
+                                                       </td>
+                                                       <th>Rack Local Maps</th>
+                                                       <td>
+                                                               
{{common.number.toFixed(job.rackLocalMaps)}}
+                                                               
({{common.number.toFixed(job.rackLocalMapsPercentage * 100)}}%)
+                                                       </td>
+                                                       <th>Total Launched 
Maps</th>
+                                                       
<td>{{common.number.toFixed(job.totalLaunchedMaps)}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Map vCores</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.JobCounter"].VCORES_MILLIS_MAPS)}}</td>
+                                                       <th>Map CPU</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters.MapTaskAttemptCounter.CPU_MILLISECONDS)}}</td>
+                                                       <th>HDFS Read Bytes</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+                                               </tr>
+                                               <tr>
+                                                       <th>Reduce vCores</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.JobCounter"].VCORES_MILLIS_REDUCES)}}</td>
+                                                       <th>Map CPU</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters.ReduceTaskAttemptCounter.CPU_MILLISECONDS)}}</td>
+                                                       <th>HDFS Write 
Bytes</th>
+                                                       
<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+                                               </tr>
+                                               <tr ng-if="!isRunning">
+                                                       <th>Last Map 
Duration</th>
+                                                       
<td>{{Time.diffStr(job.lastMapDuration)}}</td>
+                                                       <th>Last Reduce 
Duration</th>
+                                                       
<td>{{Time.diffStr(job.lastReduceDuration)}}</td>
+                                                       <th></th>
+                                                       <td></td>
+                                               </tr>
+                                               <tr ng-if="isRunning">
+                                                       <th>Map Progress</th>
+                                                       
<td>{{common.number.toFixed(job.mapProgress)}}%</td>
+                                                       <th>Reduce Progress</th>
+                                                       
<td>{{common.number.toFixed(job.reduceProgress)}}%</td>
+                                                       <th></th>
+                                                       <td></td>
+                                               </tr>
+                                       </tbody>
+                               </table>
+                       </div>
+
+                       <div ng-if="!job" class="overlay">
+                               <i class="fa fa-refresh fa-spin"></i>
+                       </div>
+               </div>
+       </div>
+</div>
+
+<div class="box box-primary">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       Dashboards
+               </h3>
+               <div class="pull-right box-tools">
+                       <a ui-sref="jpmJobTask({siteId: site, jobId: 
job.tags.jobId, startTime: startTimestamp, endTime: endTimestamp})"
+                          class="btn btn-primary btn-xs" target="_blank" 
ng-if="!isRunning">
+                               <span class="fa fa-map"></span>
+                               Task Statistic
+                       </a>
+               </div>
+       </div>
+       <div class="box-body">
+               <div class="row">
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="allocatedSeries"></div>
+                                       <div ng-if="(allocatedSeries || 
[]).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="vCoresSeries"></div>
+                                       <div ng-if="(vCoresSeries || []).length 
=== 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+
+                       <div class="col-sm-12 col-md-6" ng-hide="taskBucket">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="taskSeries" category="taskCategory" ng-click="taskSeriesClick"></div>
+                                       <div ng-if="(taskSeries || []).length 
=== 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+
+                       <div class="col-sm-12 col-md-6" ng-show="taskBucket">
+                               <div class="jpm-chart">
+                                       <div class="jpm-chart-container scroll">
+                                               <h3>
+                                                       <a class="fa 
fa-arrow-circle-o-left" ng-click="backToTaskSeries()"></a>
+                                                       Top Tasks
+                                               </h3>
+
+                                               <table class="table table-sm 
table-bordered no-margin">
+                                                       <thead>
+                                                               <tr>
+                                                                       
<!--th>Task</th-->
+                                                                       
<th>Host</th>
+                                                                       
<th>HDFS Read</th>
+                                                                       
<th>HDFS Write</th>
+                                                                       
<th>Local Read</th>
+                                                                       
<th>Local Write</th>
+                                                               </tr>
+                                                       </thead>
+                                                       <tbody>
+                                                               <tr 
ng-repeat="task in taskBucket.topEntities track by $index">
+                                                                       
<!--td>{{task.tags.taskId}}</td-->
+                                                                       
<td>{{task.host || "[" + task.tags.taskId + "]"}}</td>
+                                                                       
<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+                                                                       
<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+                                                                       
<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}</td>
+                                                                       
<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}</td>
+                                                               </tr>
+                                                       </tbody>
+                                               </table>
+                                       </div>
+                               </div>
+                       </div>
+
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <div chart class="jpm-chart-container" 
series="nodeTaskCountSeries" category="nodeTaskCountCategory"
+                                       ></div>
+                                       <div ng-if="(nodeTaskCountSeries || 
[]).length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
new file mode 100644
index 0000000..d64afe3
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
@@ -0,0 +1,131 @@
+<!--
+  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.
+  -->
+
+<style>
+       .box .box-header .box-title small a {
+               cursor: pointer;
+               padding: 0 5px;
+               border-right: 1px solid #999;
+       }
+       .box .box-header .box-title small a:last-child {
+               border-right: none;
+       }
+       .box .box-header .box-title small a.text-default {
+               color: #999;
+       }
+
+       .box .box-header .box-title small a.active {
+               font-weight: bolder;
+               text-decoration: underline;
+       }
+</style>
+
+<div class="box box-primary">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       Job List
+                       <small>
+                               <a class="no-select 
text-{{getStateClass(state.key)}}" ng-class="{active: (tableScope.search || 
'').toUpperCase() === state.key}"
+                                         ng-repeat="state in jobStateList" 
ng-click="fillSearch(state.key)">
+                                       {{state.key}}: {{state.value}}
+                               </a>
+                       </small>
+                       <span ng-show="!jobList._done || isSorting" class="fa 
fa-refresh fa-spin no-animate"></span>
+               </h3>
+       </div>
+       <div class="box-body">
+               <div id="jobList" sort-table="jobList" is-sorting="isSorting" 
search-path-list="searchPathList" scope="tableScope">
+                       <table class="table table-bordered">
+                               <thead>
+                                       <tr>
+                                               <th sortpath="tags.jobId">Job 
ID</th>
+                                               <th 
sortpath="currentState">Status</th>
+                                               <th sortpath="tags.user" 
width="10">User</th>
+                                               <th 
sortpath="tags.queue">Queue</th>
+                                               <th 
sortpath="submissionTime">Submission Time</th>
+                                               <th sortpath="startTime">Start 
Time</th>
+                                               <th sortpath="endTime">End 
Time</th>
+                                               <th 
sortpath="duration">Duration</th>
+                                               <th sortpath="numTotalMaps">Map 
Tasks</th>
+                                               <th 
sortpath="numTotalReduces">Reduce Tasks</th>
+                                               <th 
sortpath="runningContainers">Containers</th>
+                                       </tr>
+                               </thead>
+                               <tbody>
+                                       <tr ng-repeat="item in jobList">
+                                               <td>
+                                                       <a 
ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" 
target="_blank">{{item.tags.jobId}}</a>
+                                               </td>
+                                               <td class="text-center">
+                                                       <span class="label 
label-sm label-{{getStateClass(item.currentState)}}">
+                                                               
{{item.currentState}}
+                                                       </span>
+                                               </td>
+                                               <td>{{item.tags.user}}</td>
+                                               <td>{{item.tags.queue}}</td>
+                                               
<td>{{Time.format(item.submissionTime)}}</td>
+                                               
<td>{{Time.format(item.startTime)}}</td>
+                                               
<td>{{Time.format(item.endTime)}}</td>
+                                               
<td>{{Time.diffStr(item.duration)}}</td>
+                                               <td>{{item.numTotalMaps}}</td>
+                                               
<td>{{item.numTotalReduces}}</td>
+                                               <td>{{item.runningContainers || 
"-"}}</td>
+                                       </tr>
+                               </tbody>
+                       </table>
+               </div>
+       </div>
+</div>
+
+<div class="box box-primary">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       Running Metrics
+               </h3>
+       </div>
+       <div class="box-body no-padding">
+               <div class="row border-split">
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <h3 class="text-center">Number of 
Running Jobs</h3>
+                                       <div chart class="jpm-chart-container" 
series="runningTrendSeries" option="chartLeftOption"></div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <h3 class="text-center">Running 
Containers</h3>
+                                       <div chart class="jpm-chart-container" 
series="runningContainersSeries" option="chartRightOption"></div>
+                               </div>
+                       </div>
+               </div>
+               <div class="row border-split">
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <h3 class="text-center">Allocated 
vCores</h3>
+                                       <div chart class="jpm-chart-container" 
series="allocatedvcoresSeries" option="chartLeftOption"></div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart">
+                                       <h3 class="text-center">Allocated 
Memory (GB)</h3>
+                                       <div chart class="jpm-chart-container" 
series="allocatedMBSeries" option="allocatedMBOption"></div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
new file mode 100644
index 0000000..06e85ea
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
@@ -0,0 +1,347 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+       <ul class="nav nav-tabs">
+               <li class="active"><a href="#hdfsBytes" data-toggle="tab">HDFS 
IO Bytes</a></li>
+               <li><a href="#hdfsOPs" data-toggle="tab">HDFS IO OPs</a></li>
+               <li><a href="#diskIO" data-toggle="tab">Disk IO</a></li>
+               <li><a href="#cpu" data-toggle="tab">CPU Usage</a></li>
+               <li><a href="#memory" data-toggle="tab">Memory Usage</a></li>
+               <li class="pull-right">
+                       <select class="form-control" ng-model="type" 
ng-change="typeChange()">
+                               <option ng-repeat="(type, value) in 
aggregationMap track by $index" value="{{type}}">By 
{{common.string.capitalize(type)}}</option>
+                       </select>
+               </li>
+       </ul>
+       <div class="tab-content keepContent with-border">
+               <div class="tab-pane active" id="hdfsBytes">
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
HDFS Bytes Read</h3>
+                                               <div chart 
class="jpm-chart-container" series="hdfsBtyesReadSeries" 
option="commonOption"></div>
+                                               <div 
ng-if="!hdfsBtyesReadSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                                       <tr>
+                                                               <th>Name</th>
+                                                               <th>Total</th>
+                                                       </tr>
+                                               </thead>
+                                               <tbody>
+                                                       <tr ng-repeat="item in 
hdfsBtyesReadSeriesList track by $index">
+                                                               <td 
class="text-break">
+                                                                       <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                               
{{item.name}}
+                                                                       </a>
+                                                                       <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                               </td>
+                                                               <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                                       </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+
+                       <hr/>
+
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
HDFS Bytes Written</h3>
+                                               <div chart 
class="jpm-chart-container" series="hdfsBtyesWrittenSeries"
+                                                        
option="commonOption"></div>
+                                               <div 
ng-if="!hdfsBtyesWrittenSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
hdfsBtyesWrittenSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="tab-pane" id="hdfsOPs">
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
HDFS Read OPs</h3>
+                                               <div chart 
class="jpm-chart-container" series="hdfsReadOpsSeries" 
option="commonOption"></div>
+                                               <div 
ng-if="!hdfsReadOpsSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
hdfsReadOpsSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+
+                       <hr/>
+
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
HDFS Write OPs</h3>
+                                               <div chart 
class="jpm-chart-container" series="hdfsWriteOpsSeries" 
option="commonOption"></div>
+                                               <div 
ng-if="!hdfsWriteOpsSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
hdfsWriteOpsSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="tab-pane" id="diskIO">
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
File Bytes Read</h3>
+                                               <div chart 
class="jpm-chart-container" series="fileBytesReadSeries" 
option="commonOption"></div>
+                                               <div 
ng-if="!fileBytesReadSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
fileBytesReadSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+
+                       <hr/>
+
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
File Bytes Written</h3>
+                                               <div chart 
class="jpm-chart-container" series="fileBytesWrittenSeries"
+                                                        
option="commonOption"></div>
+                                               <div 
ng-if="!fileBytesWrittenSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
fileBytesWrittenSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="tab-pane" id="cpu">
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top CPU 
Usage</h3>
+                                               <div chart 
class="jpm-chart-container" series="cpuUsageSeries" option="commonOption"></div>
+                                               <div 
ng-if="!cpuUsageSeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
cpuUsageSeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+               <div class="tab-pane" id="memory">
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
Physical Memory Usage</h3>
+                                               <div chart 
class="jpm-chart-container" series="physicalMemorySeries"
+                                                        
option="commonOption"></div>
+                                               <div 
ng-if="!physicalMemorySeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
physicalMemorySeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+
+                       <hr/>
+
+                       <div class="row">
+                               <div class="col-sm-6 col-md-8 col-lg-9">
+                                       <div class="jpm-chart chart-lg 
overlay-wrapper">
+                                               <h3 class="text-center">Top 
Virtual Memory Usage</h3>
+                                               <div chart 
class="jpm-chart-container" series="virtualMemorySeries" 
option="commonOption"></div>
+                                               <div 
ng-if="!virtualMemorySeries._done" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-6 col-md-4 col-lg-3">
+                                       <table class="table table-striped">
+                                               <thead>
+                                               <tr>
+                                                       <th>Name</th>
+                                                       <th>Total</th>
+                                               </tr>
+                                               </thead>
+                                               <tbody>
+                                               <tr ng-repeat="item in 
virtualMemorySeriesList track by $index">
+                                                       <td class="text-break">
+                                                               <a 
ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" 
target="_blank">
+                                                                       
{{item.name}}
+                                                               </a>
+                                                               <span 
ng-if="type !== 'job'">{{item.name}}</span>
+                                                       </td>
+                                                       <td 
title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+                                               </tr>
+                                               </tbody>
+                                       </table>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
new file mode 100644
index 0000000..9ce721a
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
@@ -0,0 +1,120 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+       <ul class="nav nav-tabs">
+               <li ng-class="{active: type === 'hourly'}"><a 
ng-click="switchType('hourly')">Hourly</a></li>
+               <li ng-class="{active: type === 'daily'}"><a 
ng-click="switchType('daily')">Daily</a></li>
+               <li ng-class="{active: type === 'weekly'}"><a 
ng-click="switchType('weekly')">Weekly</a></li>
+               <li ng-class="{active: type === 'monthly'}"><a 
ng-click="switchType('monthly')">Monthly</a></li>
+       </ul>
+       <div class="tab-content">
+               <div class="jpm-chart">
+                       <h3 class="text-center">Number of Submitted Jobs</h3>
+                       <div chart class="jpm-chart-container"
+                                series="jobDistributionSeries"
+                                category-func="jobDistributionCategoryFunc"
+                                option="jobDistributionSeriesOption"
+                                ng-click="distributionClick"></div>
+                       <div ng-if="(jobDistributionSeries || []).length === 0" 
class="overlay">
+                               <i class="fa fa-refresh fa-spin"></i>
+                       </div>
+               </div>
+       </div>
+       <div class="box-body no-padding" ng-show="distributionSelectedIndex !== 
-1">
+               <div class="row border-split">
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart overlay-wrapper">
+                                       <h3 
class="text-center">[{{distributionSelectedType}}] Top Job Count By User</h3>
+                                       <div chart class="jpm-chart-container" 
series="topUserJobCountSeries" category="topUserJobCountSeriesCategory" 
option="commonChartOption"></div>
+                                       <div 
ng-if="topUserJobCountSeries.length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart overlay-wrapper">
+                                       <h3 
class="text-center">[{{distributionSelectedType}}] Top Job Count By Type</h3>
+                                       <div chart class="jpm-chart-container" 
series="topTypeJobCountSeries" category="topTypeJobCountSeriesCategory" 
option="commonChartOption"></div>
+                                       <div 
ng-if="topUserJobCountSeries.length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart overlay-wrapper">
+                                       <h3 
class="text-center">[{{distributionSelectedType}}] Top Job Count Trend By 
User</h3>
+                                       <div chart class="jpm-chart-container" 
series="topUserJobCountTrendSeries" category-func="drillDownCategoryFunc" 
option="commonTrendChartOption"></div>
+                                       <div 
ng-if="topUserJobCountTrendSeries.length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6">
+                               <div class="jpm-chart overlay-wrapper">
+                                       <h3 
class="text-center">[{{distributionSelectedType}}] Top Job Count Trend By 
Type</h3>
+                                       <div chart class="jpm-chart-container" 
series="topTypeJobCountTrendSeries" category-func="drillDownCategoryFunc" 
option="commonTrendChartOption"></div>
+                                       <div 
ng-if="topUserJobCountTrendSeries.length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-6" ng-show="!jobList">
+                               <div class="jpm-chart overlay-wrapper">
+                                       <h3 
class="text-center">[{{distributionSelectedType}}] Job Duration 
Distribution</h3>
+                                       <div chart class="jpm-chart-container" 
series="jobDurationDistributionSeries" category="bucketDurationCategory" 
option="commonChartOption"></div>
+                                       <div 
ng-if="jobDurationDistributionSeries.length === 0" class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+                       <div class="col-sm-12 col-md-12" ng-show="jobList">
+                               <div class="overlay-wrapper">
+                                       <div sort-table="jobList" 
style="margin-top: 10px;">
+                                               <table class="table 
table-bordered table-striped">
+                                                       <thead>
+                                                               <tr>
+                                                                       <th>Job 
Id</th>
+                                                                       <th>Job 
Name</th>
+                                                                       
<th>Type</th>
+                                                                       
<th>User</th>
+                                                                       <th 
width="135">Start Time</th>
+                                                               </tr>
+                                                       </thead>
+                                                       <tbody>
+                                                               <tr>
+                                                                       <td>
+                                                                               
<a ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" 
target="_blank">{{item.tags.jobId}}</a>
+                                                                       </td>
+                                                                       
<td>{{item.tags.jobName}}</td>
+                                                                       
<td>{{item.tags.jobType}}</td>
+                                                                       
<td>{{item.tags.user}}</td>
+                                                                       
<td>{{Time.format(item.startTime)}}</td>
+                                                               </tr>
+                                                       </tbody>
+                                               </table>
+                                       </div>
+
+                                       <div ng-if="!jobList._done" 
class="overlay">
+                                               <i class="fa fa-refresh 
fa-spin"></i>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
new file mode 100644
index 0000000..9460db6
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
@@ -0,0 +1,149 @@
+<!--
+  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.
+  -->
+
+<div class="box box-primary">
+       <div class="box-header with-border">
+               <h3 class="box-title">
+                       Task Schedule Trend
+               </h3>
+       </div>
+       <div class="box-body">
+               <div class="jpm-chart">
+                       <div chart class="jpm-chart-container" 
series="scheduleSeries" category="scheduleCategory"></div>
+                       <div ng-if="(scheduleSeries || []).length === 0" 
class="overlay">
+                               <i class="fa fa-refresh fa-spin"></i>
+                       </div>
+               </div>
+       </div>
+</div>
+
+<div class="nav-tabs-custom">
+       <ul class="nav nav-tabs">
+               <li class="active"><a href="#scheduleDistribution" 
data-toggle="tab">Schedule Distribution</a></li>
+               <li><a href="#durationDistribution" data-toggle="tab">Duration 
Distribution</a></li>
+       </ul>
+       <div class="tab-content keepContent">
+               <!-- By Schedule Distribution -->
+               <div class="tab-pane fade in active" id="scheduleDistribution">
+                       <div class="row">
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="statusSeries" 
category="bucketScheduleCategory" option="statusOption"></div>
+                                               <div ng-if="(statusSeries || 
[]).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationSeries" 
category="bucketScheduleCategory" option="durationOption"></div>
+                                               <div ng-if="(durationSeries || 
[]).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="hdfsReadSeries" 
category="bucketScheduleCategory" option="hdfsReadOption"></div>
+                                               <div ng-if="(hdfsReadSeries || 
[]).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="hdfsWriteSeries" 
category="bucketScheduleCategory" option="hdfsWriteOption"></div>
+                                               <div ng-if="(hdfsWriteSeries || 
[]).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="localReadSeries" 
category="bucketScheduleCategory" option="localReadOption"></div>
+                                               <div ng-if="(localReadSeries || 
[]).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="localWriteSeries" 
category="bucketScheduleCategory" option="localWriteOption"></div>
+                                               <div ng-if="(localWriteSeries 
|| []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+
+               <!-- By Duration Distribution -->
+               <div class="tab-pane fade" id="durationDistribution">
+                       <div class="row">
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationStatusSeries" 
category="bucketDurationCategory" option="durationStatusOption"></div>
+                                               <div 
ng-if="(durationStatusSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationMapReduceSeries" 
category="bucketDurationCategory" option="durationMapReduceOption"></div>
+                                               <div 
ng-if="(durationMapReduceSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationHdfsReadSeries" 
category="bucketDurationCategory" option="durationHdfsReadOption"></div>
+                                               <div 
ng-if="(durationHdfsReadSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationHdfsWriteSeries" 
category="bucketDurationCategory" option="durationHdfsWriteOption"></div>
+                                               <div 
ng-if="(durationHdfsWriteSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationLocalReadSeries" 
category="bucketDurationCategory" option="durationLocalReadOption"></div>
+                                               <div 
ng-if="(durationLocalReadSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                               <div class="col-sm-12 col-md-6">
+                                       <div class="jpm-chart">
+                                               <div chart 
class="jpm-chart-container" series="durationLocalWriteSeries" 
category="bucketDurationCategory" option="durationLocalWriteOption"></div>
+                                               <div 
ng-if="(durationLocalWriteSeries || []).length === 0" class="overlay">
+                                                       <i class="fa fa-refresh 
fa-spin"></i>
+                                               </div>
+                                       </div>
+                               </div>
+                       </div>
+               </div>
+       </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
new file mode 100644
index 0000000..fbe238f
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
@@ -0,0 +1,76 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+.jpm-chart {
+       position: relative;
+       margin-bottom: 15px;
+}
+
+.jpm-chart h3 {
+       margin: 10px 0 10px 0;
+}
+
+.jpm-chart .jpm-chart-container {
+       height: 300px;
+       position: relative;
+}
+
+.jpm-chart .jpm-chart-container.scroll {
+       overflow-y: auto;
+}
+
+.jpm-chart.chart-lg .jpm-chart-container {
+       height: 350px;
+}
+
+.with-border .jpm-chart {
+       padding-bottom: 15px;
+       margin-bottom: 15px;
+       border-bottom: 1px solid #f4f4f4;
+}
+
+.with-border .jpm-chart:last-child {
+       padding-bottom: 0;
+       margin-bottom: 0;
+       border-bottom: 0;
+}
+
+.jpm-chart .overlay {
+       top: 0;
+       bottom: 0;
+       position: absolute;
+       width: 100%;
+       background: rgba(255,255,255,0.7);
+}
+
+.jpm-chart .overlay > .fa {
+       position: absolute;
+       top: 50%;
+       left: 50%;
+       margin-left: -15px;
+       margin-top: -15px;
+       color: #000;
+       font-size: 30px;
+}
+
+.small-box.jpm {
+       margin: 0;
+       height: 100%;
+       min-height: 110px;
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
----------------------------------------------------------------------
diff --git 
a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
new file mode 100644
index 0000000..1572d5e
--- /dev/null
+++ 
b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
@@ -0,0 +1,108 @@
+/*
+ * 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.directive("jpmWidget", function () {
+                       return {
+                               restrict: 'AE',
+                               controller: function($scope, $interval, 
Application, JPM, Time) {
+                                       var site = $scope.site;
+                                       var refreshInterval;
+
+                                       if(!site) {
+                                               $scope.list = 
$.map(Application.find("JPM_WEB_APP"), function (app) {
+                                                       return {
+                                                               siteId: 
app.site.siteId,
+                                                               siteName: 
app.site.siteName || app.site.siteId,
+                                                               count: -1
+                                                       };
+                                               });
+                                       } else {
+                                               $scope.list = [{
+                                                       siteId: site.siteId,
+                                                       siteName: site.siteName 
|| site.siteId,
+                                                       count: -1
+                                               }];
+                                       }
+
+                                       function refresh() {
+                                               $.each($scope.list, function 
(i, site) {
+                                                       var query = 
JPM.getQuery("GROUPS", site.siteId);
+                                                       var url = 
common.template(query, {
+                                                               query: 
"RunningJobExecutionService",
+                                                               condition: 
'@site="' + site.siteId + '" AND @internalState="RUNNING"',
+                                                               groups: "@site",
+                                                               field: "count",
+                                                               order: "",
+                                                               top: "",
+                                                               limit: 100000,
+                                                               startTime: 
Time.format(Time().subtract(3, "d")),
+                                                               endTime: 
Time.format(Time().add(1, "d"))
+                                                       });
+                                                       
JPM.get(url).then(function (res) {
+                                                               site.count = 
common.getValueByPath(res, ["data", "obj", 0, "value", 0]);
+                                                       });
+                                               });
+                                       }
+
+                                       refresh();
+                                       refreshInterval = $interval(refresh, 30 
* 1000);
+
+                                       $scope.$on('$destroy', function() {
+                                               
$interval.cancel(refreshInterval);
+                                       });
+                               },
+                               template:
+                               '<div class="small-box bg-aqua jpm">' +
+                                       '<div class="inner">' +
+                                               '<h3>JPM</h3>' +
+                                               '<p ng-repeat="site in list 
track by $index">' +
+                                                       '<a 
ui-sref="jpmList({siteId: site.siteId})">' +
+                                                               
'<strong>{{site.siteName}}</strong>: ' +
+                                                               '<span 
ng-show="site.count === -1" class="fa fa-refresh fa-spin no-animate"></span>' +
+                                                               '<span 
ng-show="site.count !== -1">{{site.count}}</span> Running Jobs' +
+                                                       '</a>' +
+                                               '</p>' +
+                                       '</div>' +
+                                       '<div class="icon">' +
+                                               '<i class="fa fa-taxi"></i>' +
+                                       '</div>' +
+                               '</div>',
+                               replace: true
+                       };
+               });
+
+               /**
+                * Customize the widget content. Return false will prevent auto 
compile.
+                * @param {{}} $element
+                * @param {function} $element.append
+                */
+               function registerWidget($element) {
+                       $element.append(
+                               $("<div jpm-widget data-site='site'>")
+                       );
+               }
+
+               jpmApp.widget("jobStatistic", registerWidget);
+               jpmApp.widget("jobStatistic", registerWidget, true);
+       });
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/.gitignore
----------------------------------------------------------------------
diff --git a/eagle-server/.gitignore b/eagle-server/.gitignore
new file mode 100644
index 0000000..f3d085a
--- /dev/null
+++ b/eagle-server/.gitignore
@@ -0,0 +1,7 @@
+/bin/
+/target/
+/src/main/webapp/app/dev/apps
+grunt.json
+node_modules
+ui
+tmp

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/pom.xml
----------------------------------------------------------------------
diff --git a/eagle-server/pom.xml b/eagle-server/pom.xml
index 31b219d..13cdff6 100644
--- a/eagle-server/pom.xml
+++ b/eagle-server/pom.xml
@@ -223,9 +223,30 @@
         </profile>
     </profiles>
     <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>exec-ui-install</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                        <configuration>
+                            <executable>bash</executable>
+                            <arguments>
+                                <argument>${basedir}/ui-build.sh</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
         <resources>
             <resource>
-                <directory>src/main/webapp/app</directory>
+                <directory>src/main/webapp/app/ui</directory>
                 <targetPath>assets</targetPath>
             </resource>
             <resource>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/.editorconfig
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/.editorconfig 
b/eagle-server/src/main/webapp/app/.editorconfig
new file mode 100644
index 0000000..42a9b69
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/.editorconfig
@@ -0,0 +1,27 @@
+# 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.
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = tab
+indent_size = 4
+
+[*.md]
+trim_trailing_whitespace = false

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/Gruntfile.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/Gruntfile.js 
b/eagle-server/src/main/webapp/app/Gruntfile.js
new file mode 100644
index 0000000..3606d84
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/Gruntfile.js
@@ -0,0 +1,190 @@
+/*
+ * 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.
+*/
+'use strict';
+
+module.exports = function (grunt) {
+       // ==========================================================
+       // =                     Parse Resource                     =
+       // ==========================================================
+       /*console.log('Generating resource tree...');
+
+       var env = require('jsdom').env;
+       var fs = require('fs');
+
+       var html = fs.readFileSync('dev/index.html', 'utf8');
+
+       console.log("1111", env);
+       env(html, function (err, window) {
+               console.log(">>>!!!");
+               if (err) console.log(err);
+
+               var $ = require('jquery')(window);
+               var $cssList = $('link[href][rel="stylesheet"]');
+               var cssList = $.map($cssList, function (ele) {
+                       return $(ele).attr("href");
+               });
+
+               console.log(">>>", cssList);
+       });
+       console.log(">>>222");*/
+
+       // ==========================================================
+       // =                      Grunt Config                      =
+       // ==========================================================
+       grunt.initConfig({
+               config: grunt.file.readJSON('grunt.json'),
+
+               jshint: {
+                       options: {
+                               browser: true,
+                               globals: {
+                                       $: true,
+                                       jQuery: true,
+                                       moment: true
+                               }
+                       },
+                       all: [
+                               'dev/**/*.js'
+                       ]
+               },
+
+               clean: {
+                       build: ['ui/', 'tmp/'],
+                       tmp: ['tmp/'],
+                       ui: ['ui/']
+               },
+
+               copy: {
+                       worker: {
+                               files: [
+                                       {expand: true, cwd: 'dev/', src: '<%= 
config.copy.js.worker %>', dest: 'tmp'}
+                               ]
+                       },
+                       ui: {
+                               files: [
+                                       {expand: true, cwd: 'tmp/', src: 
['**'], dest: 'ui'},
+                                       {expand: true, cwd: 'dev/', src: 
['public/images/**', 'partials/**'], dest: 'ui'},
+                                       {expand: true, cwd: 
'node_modules/font-awesome/', src: ['fonts/**'], dest: 'ui/public'},
+                                       {expand: true, cwd: 
'node_modules/bootstrap/', src: ['fonts/**'], dest: 'ui/public'}
+                               ]
+                       }
+               },
+
+               concat: {
+                       js_project: '<%= config.concat.js.project %>',
+                       js_require: '<%= config.concat.js.require %>',
+                       css_require: {
+                               options: {
+                                       separator: '\n',
+                                       process: function(src) {
+                                               return "@import 
url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);"
 +
+                                                       src.replace('@import 
url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);',
 '');
+                                       }
+                               },
+                               src: '<%= config.concat.css.require.src %>',
+                               dest: '<%= config.concat.css.require.dest %>'
+                       }
+               },
+
+               'regex-replace': {
+                       strict: {
+                               src: ['tmp/public/js/project.js'],
+                               actions: [
+                                       {
+                                               name: 'use strict',
+                                               search: '\\\'use strict\\\';?',
+                                               replace: '',
+                                               flags: 'gmi'
+                                       },
+                                       {
+                                               name: 'build timestamp',
+                                               search: '\\/\\/ GRUNT 
REPLACEMENT\\: Module\\.buildTimestamp \\= TIMESTAMP',
+                                               replace: 'Module.buildTimestamp 
= ' + (+new Date()) + ';',
+                                               flags: 'gmi'
+                                       }
+                               ]
+                       }
+               },
+
+               uglify: {
+                       project: {
+                               options: {
+                                       mangle: false,
+                                       sourceMap: true,
+                                       sourceMapIncludeSources: true
+                               },
+                               files: [
+                                       {
+                                               src: 'tmp/public/js/doc.js',
+                                               dest: 'tmp/public/js/doc.min.js'
+                                       }
+                               ]
+                       }
+               },
+
+               cssmin: {
+                       project: {
+                               files: {
+                                       'tmp/public/css/project.min.css': '<%= 
config.concat.css.project.src %>',
+                               }
+                       }
+               },
+
+               htmlrefs: {
+                       project: {
+                               src: 'dev/index.html',
+                               dest: "tmp/index.html"
+                       }
+               },
+       });
+
+       grunt.loadNpmTasks('grunt-contrib-jshint');
+       grunt.loadNpmTasks('grunt-contrib-clean');
+       grunt.loadNpmTasks('grunt-contrib-concat');
+       grunt.loadNpmTasks('grunt-contrib-uglify');
+       grunt.loadNpmTasks('grunt-contrib-cssmin');
+       grunt.loadNpmTasks('grunt-htmlrefs');
+       grunt.loadNpmTasks('grunt-regex-replace');
+       grunt.loadNpmTasks('grunt-contrib-copy');
+
+       grunt.registerTask('default', [
+               // jshint
+               'jshint:all',
+
+               // Clean Env
+               'clean:build',
+
+               // Compress JS
+               'concat:js_require',
+               'copy:worker',
+               'concat:js_project',
+               'regex-replace:strict',
+               'uglify',
+
+               // Compress CSS
+               'cssmin:project',
+               'concat:css_require',
+
+               // Pass HTML Resources
+               'htmlrefs',
+               'copy:ui',
+
+               // Clean Env
+               'clean:tmp'
+       ]);
+};

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/README.md
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/README.md 
b/eagle-server/src/main/webapp/app/README.md
new file mode 100644
index 0000000..b4168d5
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/README.md
@@ -0,0 +1,4 @@
+Apache Eagle Web APP
+==
+
+Web client for Apache Eagle
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/build/index.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/build/index.js 
b/eagle-server/src/main/webapp/app/build/index.js
new file mode 100644
index 0000000..bacbf53
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/build/index.js
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+(function () {
+       'use strict';
+       console.log('Generating resource tree...');
+
+       var env = require('jsdom').env;
+       var fs = require('fs');
+
+       // Parse tree
+       fs.readFile('dev/index.html', 'utf8', function (err, html) {
+               if (err) return console.log(err);
+
+               env(html, function (err, window) {
+                       if (err) console.log(err);
+
+                       // Get js / css resource
+                       var $ = require('jquery')(window);
+                       function getResList(match, attr) {
+                               var $eleList = $(match);
+                               var requireList = [];
+                               var projectList = [];
+                               var list = [];
+
+                               $.each($eleList, function (i, ele) {
+                                       var path = $(ele).attr(attr);
+
+                                       if(path.match(/^apps/)) return;
+
+                                       if(path.match(/node_modules/)) {
+                                               
requireList.push(path.replace(/\.\.\//, ""));
+                                               
list.push(path.replace(/\.\.\//, ""));
+                                       } else {
+                                               projectList.push("dev/" + path);
+                                               list.push("dev/" + path);
+                                       }
+                               });
+
+                               return {
+                                       list: list,
+                                       requireList: requireList,
+                                       projectList: projectList
+                               };
+                       }
+
+                       var cssList = 
getResList('link[href][rel="stylesheet"]', 'href');
+                       var jsList = getResList('script[src]', 'src');
+
+                       // JS Worker process
+                       var workerFolderPath = 'dev/public/js/worker/';
+                       var workerList = fs.readdirSync(workerFolderPath);
+                       var workerRequireList = [];
+
+                       workerList = workerList.map(function (path) {
+                               if(!/\w+Worker\.js/.test(path)) return;
+
+                               var workerPath = workerFolderPath + path;
+                               var content = fs.readFileSync(workerPath, 
'utf8');
+                               var regex = 
/self\.importScripts\(["']([^"']*)["']\)/g;
+                               var match;
+                               while ((match = regex.exec(content)) !== null) {
+                                       var modulePath = match[1];
+                                       
workerRequireList.push((workerFolderPath + modulePath).replace(/^dev\//, ""));
+                               }
+
+                               return workerPath.replace(/^dev\//, "");
+                       }).filter(function (path) {
+                               return !!path;
+                       });
+
+                       // Parse grunt config
+                       var resJson = {
+                               concat: {
+                                       js: {
+                                               require: {
+                                                       options: {
+                                                               separator: '\n'
+                                                       },
+                                                       src: jsList.requireList,
+                                                       dest: 
'tmp/public/js/modules.js'
+                                               },
+                                               project: {
+                                                       options: {
+                                                               separator: '\n',
+                                                               sourceMap :true
+                                                       },
+                                                       src: jsList.projectList,
+                                                       dest: 
'tmp/public/js/doc.js'
+                                               }
+                                       },
+                                       css: {
+                                               require: {
+                                                       src: 
cssList.requireList.concat('tmp/public/css/project.min.css'),
+                                                       dest: 
'tmp/public/css/doc.css'
+                                               },
+                                               project: {
+                                                       options: {
+                                                               separator: '\n'
+                                                       },
+                                                       src: 
cssList.projectList,
+                                                       dest: 
'tmp/public/js/project.min.css'
+                                               }
+                                       }
+                               },
+                               copy: {
+                                       js: {
+                                               worker: 
workerList.concat(workerRequireList)
+                                       }
+                               }
+                       };
+
+                       // Save tree & call grunt
+                       fs.writeFile('grunt.json', JSON.stringify(resJson, 
null, '\t'), 'utf8', function (err) {
+                               if(err) return console.log(err);
+
+                               console.log("Grunt packaging...");
+                               var exec = require('child_process').exec;
+                               var grunt = exec('npm run grunt');
+
+                               grunt.stdout.pipe(process.stdout);
+                               grunt.stderr.pipe(process.stdout);
+                               grunt.on('exit', function() {
+                                       process.exit()
+                               })
+                       });
+               });
+       });
+})();

Reply via email to