You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mesos.apache.org by bm...@apache.org on 2018/04/11 22:37:32 UTC

[20/22] mesos git commit: Restructured Web UI.

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/services.js
----------------------------------------------------------------------
diff --git a/src/webui/app/services.js b/src/webui/app/services.js
new file mode 100644
index 0000000..5e83996
--- /dev/null
+++ b/src/webui/app/services.js
@@ -0,0 +1,309 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function() {
+  'use strict';
+
+  var mesosServices = angular.module('mesos.services', []);
+
+  mesosServices.service('$alert', ['$rootScope', function($rootScope) {
+    // Types taken from Bootstraps v3's "Alerts"[1] so the type can be used
+    // as the class name.
+    //
+    // [1] http://getbootstrap.com/components/#alerts
+    var TYPE_DANGER = 'danger';
+    var TYPE_INFO = 'info';
+    var TYPE_SUCCESS = 'success';
+    var TYPE_WARNING = 'warning';
+
+    var nextId = 1;
+
+    var nextAlerts = [];
+    var currentAlerts = $rootScope.currentAlerts = [];
+
+    // Creates an alert to be rendered on the next page view.
+    //
+    // messageOrOptions - Either a String or an Object that will be used to
+    //   render an alert on the next view. If a String, it will be the
+    //   message in the alert. If an Object, "title" will be bolded, "message"
+    //   will be normal font weight, and "bullets" will be rendered as a list.
+    function alert(type, messageOrOptions) {
+      var alertObject;
+
+      if (angular.isObject(messageOrOptions)) {
+        alertObject = angular.copy(messageOrOptions);
+        alertObject.type = type;
+      } else {
+        alertObject = {
+          message: messageOrOptions,
+          type: type
+        };
+      }
+
+      alertObject.id = nextId;
+      nextId += 1;
+      return nextAlerts.push(alertObject);
+    }
+
+    this.danger = function(messageOrOptions) {
+      return alert(TYPE_DANGER, messageOrOptions);
+    };
+    this.info = function(messageOrOptions) {
+      return alert(TYPE_INFO, messageOrOptions);
+    };
+    this.success = function(messageOrOptions) {
+      return alert(TYPE_SUCCESS, messageOrOptions);
+    };
+    this.warning = function(messageOrOptions) {
+      return alert(TYPE_WARNING, messageOrOptions);
+    };
+
+    // Rotate alerts each time the user navigates.
+    $rootScope.$on('$locationChangeSuccess', function() {
+      if (nextAlerts.length > 0) {
+        // If there are alerts to be shown next, they become the current alerts.
+        currentAlerts = $rootScope.currentAlerts = nextAlerts;
+        nextAlerts = [];
+      } else if (currentAlerts.length > 0) {
+        // If there are no next alerts, the current alerts still need to expire
+        // if there are any so they won't display again.
+        currentAlerts = $rootScope.currentAlerts = [];
+      }
+    });
+  }]);
+
+  var uiModalDialog = angular.module('ui.bootstrap.dialog', ['ui.bootstrap']);
+  uiModalDialog
+    .factory('$dialog', ['$rootScope', '$modal', function ($rootScope, $modal) {
+
+      var prompt = function(title, message, buttons) {
+
+        if (typeof buttons === 'undefined') {
+          buttons = [
+            {result:'cancel', label: 'Cancel'},
+            {result:'yes', label: 'Yes', cssClass: 'btn-primary'}
+          ];
+        }
+
+        var ModalCtrl = function($scope, _$modalInstance) {
+          $scope.title = title;
+          $scope.message = message;
+          $scope.buttons = buttons;
+        };
+
+        return $modal.open({
+          templateUrl: 'template/dialog/message.html',
+          controller: ModalCtrl
+        }).result;
+      };
+
+      return {
+        prompt: prompt,
+        messageBox: function(title, message, buttons) {
+          return {
+            open: function() {
+              return prompt(title, message, buttons);
+            }
+          };
+        }
+      };
+    }]);
+
+  function Statistics() {
+    this.cpus_user_time_secs = 0.0;
+    this.cpus_system_time_secs = 0.0;
+    this.cpus_limit = 0.0;
+    this.cpus_total_usage = 0.0;
+    this.mem_rss_bytes = 0.0;
+    this.mem_limit_bytes = 0.0;
+    this.disk_used_bytes = 0.0;
+    this.disk_limit_bytes = 0.0;
+    this.timestamp = 0.0;
+  }
+
+  Statistics.prototype.add = function(statistics) {
+    this.cpus_user_time_secs += statistics.cpus_user_time_secs;
+    this.cpus_system_time_secs += statistics.cpus_system_time_secs;
+    this.cpus_total_usage += statistics.cpus_total_usage;
+    this.cpus_limit += statistics.cpus_limit;
+    this.mem_rss_bytes += statistics.mem_rss_bytes;
+    this.mem_limit_bytes += statistics.mem_limit_bytes;
+    this.disk_used_bytes += statistics.disk_used_bytes;
+    this.disk_limit_bytes += statistics.disk_limit_bytes;
+
+    // Set instead of add the timestamp since this is an instantaneous view of
+    // CPU usage since the last poll.
+    this.timestamp = statistics.timestamp;
+  };
+
+  Statistics.prototype.diffUsage = function(statistics) {
+    var cpus_user_usage =
+      (this.cpus_user_time_secs - statistics.cpus_user_time_secs) /
+      (this.timestamp - statistics.timestamp);
+    var cpus_system_usage =
+      (this.cpus_system_time_secs - statistics.cpus_system_time_secs) /
+      (this.timestamp - statistics.timestamp);
+    this.cpus_total_usage = cpus_user_usage + cpus_system_usage;
+  };
+
+  Statistics.parseJSON = function(json) {
+    var statistics = new Statistics();
+    statistics.add(json);
+    return statistics;
+  };
+
+  // Top is an abstraction for polling an agent's monitoring endpoint to
+  // periodically update the monitoring data. It also computes CPU usage.
+  // This places the following data in scope.monitor:
+  //
+  //   $scope.monitor = {
+  //     "statistics": <stats>,
+  //     "frameworks": {
+  //       <framework_id>: {
+  //         "statistics": <stats>,
+  //         "executors": {
+  //           <executor_id>: {
+  //             "executor_id": <executor_id>,
+  //             "framework_id": <framework_id>,
+  //             "executor_name: <executor_name>,
+  //             "source": <source>,
+  //             "statistics": <stats>,
+  //           }
+  //         }
+  //       }
+  //     }
+  //    }
+  //
+  // To obtain agent statistics:
+  //   $scope.monitor.statistics
+  //
+  // To obtain a framework's statistics:
+  //   $scope.monitor.frameworks[<framework_id>].statistics
+  //
+  // To obtain an executor's statistics:
+  //   $scope.monitor.frameworks[<framework_id>].executors[<executor_id>].statistics
+  //
+  // In the above,  <stats> is the following object:
+  //
+  //   {
+  //     cpus_user_time_secs: value,
+  //     cpus_system_time_secs: value,
+  //     cpus_total_usage: value, // Once computed.
+  //     mem_limit_bytes: value,
+  //     mem_rss_bytes: value,
+  //   }
+  //
+  // TODO(bmahler): The complexity of the monitor object is mostly in place
+  // until we have path-params on the monitoring endpoint to request
+  // statistics for the agent, or for a specific framework / executor.
+  //
+  // Arguments:
+  //   http: $http service from Angular.
+  //   timeout: $timeout service from Angular.
+  function Top($http, $timeout) {
+    this.http = $http;
+    this.timeout = $timeout;
+  }
+
+  Top.prototype.poll = function() {
+    this.http.jsonp(this.endpoint)
+
+      // Success! Parse the response.
+      .success(angular.bind(this, this.parseResponse))
+
+      // Do not continue polling on error.
+      .error(angular.noop);
+  };
+
+  Top.prototype.parseResponse = function(response) {
+    var this_ = this;
+    var monitor = {
+      frameworks: {},
+      statistics: new Statistics()
+    };
+
+    response.forEach(function(executor) {
+      var executor_id = executor.executor_id;
+      var framework_id = executor.framework_id;
+      var current = executor.statistics =
+        Statistics.parseJSON(executor.statistics);
+
+      // Compute CPU usage if possible.
+      if (this_.scope.monitor &&
+          this_.scope.monitor.frameworks[framework_id] &&
+          this_.scope.monitor.frameworks[framework_id].executors[executor_id]) {
+        var previous = this_.scope.monitor.frameworks[framework_id].executors[executor_id].statistics;
+        current.diffUsage(previous);
+      }
+
+      // Index the data.
+      if (!monitor.frameworks[executor.framework_id]) {
+        monitor.frameworks[executor.framework_id] = {
+          executors: {},
+          statistics: new Statistics()
+        };
+      }
+
+      // Aggregate these statistics into the agent and framework statistics.
+      monitor.statistics.add(current);
+      monitor.frameworks[executor.framework_id].statistics.add(current);
+      monitor.frameworks[executor.framework_id].executors[executor.executor_id] = {
+        statistics: current
+      };
+    });
+
+    if (this.scope.monitor) {
+      // Continue polling.
+      this.polling = this.timeout(angular.bind(this, this.poll), 3000);
+    } else {
+      // Try to compute initial CPU usage more quickly than 3 seconds.
+      this.polling = this.timeout(angular.bind(this, this.poll), 500);
+    }
+
+    // Update the monitoring data.
+    this.scope.monitor = monitor;
+  };
+
+  // Arguments:
+  //   url: the URL of the Agent's container statistics endpoint.
+  //   scope: $scope service from Angular.
+  Top.prototype.start = function(url, scope) {
+    if (this.started()) {
+      // TODO(bmahler): Consider logging a warning here.
+      return;
+    }
+
+    this.endpoint = url;
+    this.scope = scope;
+
+    // Initial poll is immediate.
+    this.polling = this.timeout(angular.bind(this, this.poll), 0);
+
+    // Stop when we leave the page.
+    scope.$on('$routeChangeStart', angular.bind(this, this.stop));
+  };
+
+  Top.prototype.started = function() {
+    return this.polling != null;
+  };
+
+  Top.prototype.stop = function() {
+    this.timeout.cancel(this.polling);
+    this.polling = null;
+  };
+
+  mesosServices.service('top', ['$http', '$timeout', Top]);
+})();

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/shared/pagination.html
----------------------------------------------------------------------
diff --git a/src/webui/app/shared/pagination.html b/src/webui/app/shared/pagination.html
new file mode 100644
index 0000000..ffa16b7
--- /dev/null
+++ b/src/webui/app/shared/pagination.html
@@ -0,0 +1,6 @@
+<pagination
+  data-ng-show="filteredData.length > pageLength"
+  max-size="5"
+  items-per-page="pageLength"
+  page="pgNum"
+  total-items="filteredData.length"></pagination>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/shared/pailer.html
----------------------------------------------------------------------
diff --git a/src/webui/app/shared/pailer.html b/src/webui/app/shared/pailer.html
new file mode 100644
index 0000000..0da1306
--- /dev/null
+++ b/src/webui/app/shared/pailer.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title></title>
+    <style>
+      body {
+        line-height: 16px;
+        font-size: 14px;
+      }
+
+      .indicator {
+        background: #000000;
+        color: #FFFFFF;
+        left: 5px;
+        position: absolute;
+        text-decoration: none;
+        top: 5px;
+      }
+    </style>
+  </head>
+
+  <body style="overflow: hidden;">
+    <pre id="data"></pre>
+    <div class="indicator" id="indicator"></div>
+
+    <script src="../../assets/libs/jquery-3.2.1.min.js"></script>
+    <script src="../../assets/libs/underscore-1.4.3.min.js"></script>
+    <script src="../../assets/libs/jquery.pailer.js"></script>
+
+    <script>
+      var $body = $('body');
+      var $data = $('#data');
+
+      function resize() {
+        var margin_left = parseInt($body.css('margin-left'));
+        var margin_top = parseInt($body.css('margin-top'));
+        var margin_bottom = parseInt($body.css('margin-bottom'));
+        $data
+          .width($(window).width() - margin_left)
+          .height($(window).height() - margin_top - margin_bottom);
+      }
+
+      $(window).resize(resize);
+
+      // Set target URL in `sessionStorage` and clean it in `localStorage`.
+      (function() {
+        // Avoid fetching the target URL if reloading the pailer windoow.
+        if (sessionStorage.getItem('isReloaded') !== 'true') {
+          var storageKey = window.name;
+          sessionStorage.setItem(storageKey, localStorage.getItem(storageKey));
+          localStorage.removeItem(storageKey);
+
+          sessionStorage.setItem('isReloaded', 'true');
+        }
+      })();
+
+      $(document).ready(function() {
+        resize();
+
+        var storageKey = window.name;
+
+        $data.pailer({
+          read: function(options) {
+            var settings = $.extend({
+              'offset': -1,
+              'length': -1
+            }, options);
+            var url = sessionStorage.getItem(storageKey)
+              + '&offset=' + settings.offset
+              + '&length=' + settings.length
+              + '&jsonp=?';
+            return $.getJSON(url);
+          },
+          'indicator': $('#indicator')
+        });
+      });
+    </script>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/shared/table-header.html
----------------------------------------------------------------------
diff --git a/src/webui/app/shared/table-header.html b/src/webui/app/shared/table-header.html
new file mode 100644
index 0000000..448f67e
--- /dev/null
+++ b/src/webui/app/shared/table-header.html
@@ -0,0 +1,20 @@
+<div class="row">
+  <div class="col-md-8">
+    <h3 id="frameworks">{{headerTitle}}</h3>
+  </div>
+  <div class="col-md-4">
+    <div class="input-group input-group-sm input-group-header"
+      ng-hide="originalData.length === 0">
+      <span class="input-group-addon"
+          ng-class="{ 'input-group-addon-success': filterTerm.length > 0 }">
+        <i class="glyphicon glyphicon-filter"></i>
+      </span>
+      <input type="search" ng-model="filterTerm" placeholder="Find..."
+        class="form-control input-sm">
+      <button ng-click="filterTerm = ''" ng-show="filterTerm.length > 0"
+          class="btn btn-clear btn-xs input-group-inner-right" title="Clear">
+        &times;
+      </button>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/shared/timestamp.html
----------------------------------------------------------------------
diff --git a/src/webui/app/shared/timestamp.html b/src/webui/app/shared/timestamp.html
new file mode 100644
index 0000000..5e422b9
--- /dev/null
+++ b/src/webui/app/shared/timestamp.html
@@ -0,0 +1,5 @@
+<time datetime="{{value | isoDate}}" ng-click="toggle()" ng-show="longDate">
+  {{value | isoDate}}</time>
+<time datetime="{{value | isoDate}}" ng-click="toggle()" ng-hide="longDate">
+  {{value | relativeDate:pollTime}}</time>
+<span ng-transclude></span>