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() + }) + }); + }); + }); +})();