Moved controllers from globals to the Angular module.

As mentioned in "Setting up the initial state of a scope object"[1],
controllers should be defined in the same module as the rest of your
app instead of being defined as global functions.

This also wraps "controllers.js" in a closure so strict mode is
applied only to that file. Because of that, the global function
"setNavbarActiveTab" moved into "controllers.js" since it was only
referenced there.

[1] http://docs.angularjs.org/guide/dev_guide.mvc.understanding_
    controller#settinguptheinitialstateofascopeobject

Original review: https://reviews.apache.org/r/12613/

From: Ross Allen <ross...@mesosphe.re>
Review: https://reviews.apache.org/r/12735


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/4bbcdcbc
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/4bbcdcbc
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/4bbcdcbc

Branch: refs/heads/master
Commit: 4bbcdcbc8a3d581a497f46dc4681d5c6389981e8
Parents: 7349da9
Author: Benjamin Mahler <bmah...@twitter.com>
Authored: Thu Jul 18 16:25:38 2013 -0700
Committer: Benjamin Mahler <bmah...@twitter.com>
Committed: Thu Jul 18 16:28:03 2013 -0700

----------------------------------------------------------------------
 src/webui/master/static/js/app.js         |   32 +-
 src/webui/master/static/js/controllers.js | 1278 ++++++++++++------------
 2 files changed, 661 insertions(+), 649 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/4bbcdcbc/src/webui/master/static/js/app.js
----------------------------------------------------------------------
diff --git a/src/webui/master/static/js/app.js 
b/src/webui/master/static/js/app.js
index 9ddcf95..235be27 100644
--- a/src/webui/master/static/js/app.js
+++ b/src/webui/master/static/js/app.js
@@ -3,15 +3,24 @@
 angular.module('mesos', ['ui.bootstrap']).
   config(['$dialogProvider', '$routeProvider', function($dialogProvider, 
$routeProvider) {
     $routeProvider
-      .when('/', {templateUrl: 'static/home.html', controller: HomeCtrl})
-      .when('/dashboard', {templateUrl: 'static/dashboard.html', controller: 
DashboardCtrl})
-      .when('/frameworks', {templateUrl: 'static/frameworks.html', controller: 
FrameworksCtrl})
-      .when('/frameworks/:id', {templateUrl: 'static/framework.html', 
controller: FrameworkCtrl})
-      .when('/slaves', {templateUrl: 'static/slaves.html', controller: 
SlavesCtrl})
-      .when('/slaves/:slave_id', {templateUrl: 'static/slave.html', 
controller: SlaveCtrl})
-      .when('/slaves/:slave_id/frameworks/:framework_id', {templateUrl: 
'static/slave_framework.html', controller: SlaveFrameworkCtrl})
-      
.when('/slaves/:slave_id/frameworks/:framework_id/executors/:executor_id', 
{templateUrl: 'static/slave_executor.html', controller: SlaveExecutorCtrl})
-      .when('/slaves/:slave_id/browse', {templateUrl: 'static/browse.html', 
controller: BrowseCtrl})
+      .when('/',
+            {templateUrl: 'static/home.html', controller: 'HomeCtrl'})
+      .when('/dashboard',
+            {templateUrl: 'static/dashboard.html', controller: 
'DashboardCtrl'})
+      .when('/frameworks',
+            {templateUrl: 'static/frameworks.html', controller: 
'FrameworksCtrl'})
+      .when('/frameworks/:id',
+            {templateUrl: 'static/framework.html', controller: 
'FrameworkCtrl'})
+      .when('/slaves',
+            {templateUrl: 'static/slaves.html', controller: 'SlavesCtrl'})
+      .when('/slaves/:slave_id',
+            {templateUrl: 'static/slave.html', controller: 'SlaveCtrl'})
+      .when('/slaves/:slave_id/frameworks/:framework_id',
+            {templateUrl: 'static/slave_framework.html', controller: 
'SlaveFrameworkCtrl'})
+      
.when('/slaves/:slave_id/frameworks/:framework_id/executors/:executor_id',
+            {templateUrl: 'static/slave_executor.html', controller: 
'SlaveExecutorCtrl'})
+      .when('/slaves/:slave_id/browse',
+            {templateUrl: 'static/browse.html', controller: 'BrowseCtrl'})
       .otherwise({redirectTo: '/'});
 
     $dialogProvider.options({dialogFade: true});
@@ -184,8 +193,3 @@ angular.module('mesos', ['ui.bootstrap']).
       }
     };
   }]);
-
-function setNavbarActiveTab(tab_name) {
-  $('#navbar li').removeClass('active');
-  $('#navbar li[data-tabname='+tab_name+']').addClass('active');
-}

http://git-wip-us.apache.org/repos/asf/mesos/blob/4bbcdcbc/src/webui/master/static/js/controllers.js
----------------------------------------------------------------------
diff --git a/src/webui/master/static/js/controllers.js 
b/src/webui/master/static/js/controllers.js
index 1f58eab..4caa64e 100644
--- a/src/webui/master/static/js/controllers.js
+++ b/src/webui/master/static/js/controllers.js
@@ -1,472 +1,362 @@
-'use strict';
-
-var slaves = [];
-
-
-// Table Object.
-//   selected_column: column predicate for the selected column.
-//   reverse:         boolean indicating sort order.
-function Table(selected_column) {
-  if (this instanceof Table) {
-    this.selected_column = selected_column;
-    this.reverse = true;
-  } else {
-    return new Table(selected_column);
-  }
-}
-
-
-function hasSelectedText () {
-  if (window.getSelection) {  // All browsers except IE before version 9.
-    var range = window.getSelection();
-    return range.toString().length > 0;
-  }
-  return false;
-}
-
-
-// Returns a curried function for returning the HTML 'class=' tag
-// attribute value for sorting table columns in the provided scope.
-function columnClass($scope) {
-  // For the given table column, this behaves as follows:
-  // Column unselected            : 'unselected'
-  // Column selected / descending : 'descending'
-  // Column selected / ascending  : 'ascending'
-  return function(table, column) {
-    if ($scope.tables[table].selected_column === column) {
-      if ($scope.tables[table].reverse) {
-        return 'descending';
-      } else {
-        return 'ascending';
-      }
-    }
-    return 'unselected';
-  };
-}
-
-
-// Returns a curried function to be called when a table column is clicked
-// in the provided scope.
-function selectColumn($scope) {
-  // Assigns the given table column as the sort column, flipping the
-  // sort order if the sort column has not changed.
-  return function(table, column) {
-    if ($scope.tables[table].selected_column === column) {
-      $scope.tables[table].reverse = !$scope.tables[table].reverse;
+(function() {
+  'use strict';
+
+  var slaves = [];
+  var mesosApp = angular.module('mesos');
+
+  // Table Object.
+  //   selected_column: column predicate for the selected column.
+  //   reverse:         boolean indicating sort order.
+  function Table(selected_column) {
+    if (this instanceof Table) {
+      this.selected_column = selected_column;
+      this.reverse = true;
     } else {
-      $scope.tables[table].reverse = true;
+      return new Table(selected_column);
     }
-    $scope.tables[table].selected_column = column;
-  };
-}
-
-
-// Invokes the pailer for the specified host and path using the
-// specified window_title.
-function pailer(host, path, window_title) {
-  var url = 'http://' + host + '/files/read.json?path=' + path;
-  var pailer =
-    window.open('/static/pailer.html', url, 'width=580px, height=700px');
-
-  // Need to use window.onload instead of document.ready to make
-  // sure the title doesn't get overwritten.
-  pailer.onload = function() {
-    pailer.document.title = window_title + ' (' + host + ')';
-  };
-}
-
-
-function updateInterval(num_slaves) {
-  // TODO(bmahler): Increasing the update interval for large clusters
-  // is done purely to mitigate webui performance issues. Ideally we can
-  // keep a consistently fast rate for updating statistical information.
-  // For the full system state updates, it may make sense to break
-  // it up using pagination and/or splitting the endpoint.
-  if (num_slaves < 10) {
-    return 3000;
-  } else if (num_slaves < 50) {
-    return 4000;
-  } else if (num_slaves < 100) {
-    return 5000;
-  } else if (num_slaves < 500) {
-    return 10000;
-  } else if (num_slaves < 1000) {
-    return 20000;
-  } else if (num_slaves < 5000) {
-    return 30000;
-  } else {
-    return 60000;
   }
-}
 
 
-// Update the outermost scope with the new state.
-function update($scope, $timeout, data) {
-  // Don't do anything if the data hasn't changed.
-  if ($scope.data == data) {
-    return true; // Continue polling.
+  function setNavbarActiveTab(tab_name) {
+    $('#navbar li').removeClass('active');
+    $('#navbar li[data-tabname='+tab_name+']').addClass('active');
   }
 
-  $scope.state = $.parseJSON(data);
 
-  // Determine if there is a leader (and redirect if not the leader).
-  if (!$scope.state.leader) {
-    $("#no-leader-alert").show();
-  } else {
-    $("#no-leader-alert").hide();
+  function hasSelectedText() {
+    if (window.getSelection) {  // All browsers except IE before version 9.
+      var range = window.getSelection();
+      return range.toString().length > 0;
+    }
+    return false;
+  }
 
-    // Redirect if we aren't the leader.
-    if ($scope.state.leader != $scope.state.pid) {
-      $scope.redirect = 6000;
-      $scope.leader = $scope.state.leader.split("@")[1];
-      $("#not-leader-alert").show();
 
-      var countdown = function() {
-        if ($scope.redirect == 0) {
-          // TODO(benh): Use '$window'.
-          window.location = 'http://' + $scope.leader;
+  // Returns a curried function for returning the HTML 'class=' tag
+  // attribute value for sorting table columns in the provided scope.
+  function columnClass($scope) {
+    // For the given table column, this behaves as follows:
+    // Column unselected            : 'unselected'
+    // Column selected / descending : 'descending'
+    // Column selected / ascending  : 'ascending'
+    return function(table, column) {
+      if ($scope.tables[table].selected_column === column) {
+        if ($scope.tables[table].reverse) {
+          return 'descending';
         } else {
-          $scope.redirect = $scope.redirect - 1000;
-          $timeout(countdown, 1000);
+          return 'ascending';
         }
-      };
-      countdown();
-      return false; // Don't continue polling.
-    }
+      }
+      return 'unselected';
+    };
   }
 
-  // A cluster is named if the state returns a non-empty string name.
-  // Track whether this cluster is named in a Boolean for display purposes.
-  $scope.clusterNamed = !!$scope.state.cluster;
 
-  // Check for selected text, and allow up to 20 seconds to pass before
-  // potentially wiping the user highlighted text.
-  // TODO(bmahler): This is to avoid the annoying loss of highlighting when
-  // the tables update. Once we can have tighter granularity control on the
-  // angular.js dynamic table updates, we should remove this hack.
-  $scope.time_since_update += $scope.delay;
-
-  if (hasSelectedText() && $scope.time_since_update < 20000) {
-    return true;
+  // Returns a curried function to be called when a table column is clicked
+  // in the provided scope.
+  function selectColumn($scope) {
+    // Assigns the given table column as the sort column, flipping the
+    // sort order if the sort column has not changed.
+    return function(table, column) {
+      if ($scope.tables[table].selected_column === column) {
+        $scope.tables[table].reverse = !$scope.tables[table].reverse;
+      } else {
+        $scope.tables[table].reverse = true;
+      }
+      $scope.tables[table].selected_column = column;
+    };
   }
 
-  $scope.data = data;
-
-  // Update the maps.
-  $scope.slaves = {};
-  $scope.frameworks = {};
-  $scope.offers = {};
-  $scope.completed_frameworks = {};
 
-  _.each($scope.state.slaves, function(slave) {
-    $scope.slaves[slave.id] = slave;
-  });
-
-  _.each($scope.state.frameworks, function(framework) {
-    $scope.frameworks[framework.id] = framework;
-    _.each(framework.offers, function(offer) {
-      $scope.offers[offer.id] = offer;
-    });
-  });
+  // Invokes the pailer for the specified host and path using the
+  // specified window_title.
+  function pailer(host, path, window_title) {
+    var url = 'http://' + host + '/files/read.json?path=' + path;
+    var pailer =
+      window.open('/static/pailer.html', url, 'width=580px, height=700px');
 
-  _.each($scope.state.completed_frameworks, function(framework) {
-    $scope.completed_frameworks[framework.id] = framework;
-  });
-
-  // Update the stats.
-  $scope.cluster = $scope.state.cluster;
-  $scope.total_cpus = 0;
-  $scope.total_mem = 0;
-  $scope.used_cpus = 0;
-  $scope.used_mem = 0;
-  $scope.offered_cpus = 0;
-  $scope.offered_mem = 0;
-
-  $scope.staged_tasks = $scope.state.staged_tasks;
-  $scope.started_tasks = $scope.state.started_tasks;
-  $scope.finished_tasks = $scope.state.finished_tasks;
-  $scope.killed_tasks = $scope.state.killed_tasks;
-  $scope.failed_tasks = $scope.state.failed_tasks;
-  $scope.lost_tasks = $scope.state.lost_tasks;
-
-  $scope.activated_slaves = $scope.state.activated_slaves;
-  $scope.deactivated_slaves = $scope.state.deactivated_slaves;
-
-  _.each($scope.slaves, function(slave) {
-    $scope.total_cpus += slave.resources.cpus;
-    $scope.total_mem += slave.resources.mem;
-  });
-
-  _.each($scope.frameworks, function(framework) {
-      $scope.used_cpus += framework.resources.cpus;
-      $scope.used_mem += framework.resources.mem;
-      $scope.active_tasks += framework.tasks.length;
-      $scope.completed_tasks += framework.completed_tasks.length;
+    // Need to use window.onload instead of document.ready to make
+    // sure the title doesn't get overwritten.
+    pailer.onload = function() {
+      pailer.document.title = window_title + ' (' + host + ')';
+    };
+  }
 
-      framework.cpus_share = 0;
-      if ($scope.total_cpus > 0) {
-        framework.cpus_share = framework.resources.cpus / $scope.total_cpus;
-      }
 
-      framework.mem_share = 0;
-      if ($scope.total_mem > 0) {
-        framework.mem_share = framework.resources.mem / $scope.total_mem;
-      }
+  function updateInterval(num_slaves) {
+    // TODO(bmahler): Increasing the update interval for large clusters
+    // is done purely to mitigate webui performance issues. Ideally we can
+    // keep a consistently fast rate for updating statistical information.
+    // For the full system state updates, it may make sense to break
+    // it up using pagination and/or splitting the endpoint.
+    if (num_slaves < 10) {
+      return 3000;
+    } else if (num_slaves < 50) {
+      return 4000;
+    } else if (num_slaves < 100) {
+      return 5000;
+    } else if (num_slaves < 500) {
+      return 10000;
+    } else if (num_slaves < 1000) {
+      return 20000;
+    } else if (num_slaves < 5000) {
+      return 30000;
+    } else {
+      return 60000;
+    }
+  }
 
-      framework.max_share = Math.max(framework.cpus_share, 
framework.mem_share);
 
-      // If the executor ID is empty, this is a command executor with an
-      // internal executor ID generated from the task ID.
-      // TODO(brenden): Remove this once
-      // https://issues.apache.org/jira/browse/MESOS-527 is fixed.
-      _.each(framework.tasks, function(task) {
-        if (!task.executor_id) {
-          task.executor_id = task.id;
-        }
-      });
-      _.each(framework.completed_tasks, function(task) {
-        if (!task.executor_id) {
-          task.executor_id = task.id;
-        }
-      });
-  });
+  // Update the outermost scope with the new state.
+  function update($scope, $timeout, data) {
+    // Don't do anything if the data hasn't changed.
+    if ($scope.data == data) {
+      return true; // Continue polling.
+    }
 
-  _.each($scope.offers, function(offer) {
-    $scope.offered_cpus += offer.resources.cpus;
-    $scope.offered_mem += offer.resources.mem;
-    offer.framework_name = $scope.frameworks[offer.framework_id].name;
-    offer.hostname = $scope.slaves[offer.slave_id].hostname;
-  });
+    $scope.state = $.parseJSON(data);
 
-  $scope.used_cpus -= $scope.offered_cpus;
-  $scope.used_mem -= $scope.offered_mem;
-
-  $scope.idle_cpus = $scope.total_cpus - ($scope.offered_cpus + 
$scope.used_cpus);
-  $scope.idle_mem = $scope.total_mem - ($scope.offered_mem + $scope.used_mem);
-
-  $scope.time_since_update = 0;
-  $.event.trigger('state_updated');
-
-  return true; // Continue polling.
-}
-
-
-// Main controller that can be used to handle "global" events. E.g.,:
-//     $scope.$on('$afterRouteChange', function() { ...; });
-//
-// In addition, the MainCntl encapsulates the "view", allowing the
-// active controller/view to easily access anything in scope (e.g.,
-// the state).
-function MainCntl($scope, $http, $route, $routeParams, $location, $timeout) {
-  // Turn off the loading gif, turn on the navbar.
-  $("#loading").hide();
-  $("#navbar").show();
-
-  // Adding bindings into scope so that they can be used from within
-  // AngularJS expressions.
-  $scope._ = _;
-  $scope.stringify = JSON.stringify;
-  $scope.encodeURIComponent = encodeURIComponent;
-  $scope.basename = function(path) {
-    // This is only a basic version of basename that handles the cases we care
-    // about, rather than duplicating unix basename functionality perfectly.
-    if (path === '/') {
-      return path;  // Handle '/'.
-    }
+    // Determine if there is a leader (and redirect if not the leader).
+    if (!$scope.state.leader) {
+      $("#no-leader-alert").show();
+    } else {
+      $("#no-leader-alert").hide();
 
-    // Strip a trailing '/' if present.
-    if (path.length > 0 && path.lastIndexOf('/') === (path.length - 1)) {
-      path = path.substr(0, path.length - 1);
-    }
-    return path.substr(path.lastIndexOf('/') + 1);
-  };
-
-  $scope.$location = $location;
-  $scope.delay = 2000;
-  $scope.retry = 0;
-  $scope.time_since_update = 0;
-
-  var poll = function() {
-    $http.get('master/state.json',
-              {transformResponse: function(data) { return data; }})
-      .success(function(data) {
-        if (update($scope, $timeout, data)) {
-          $scope.delay = updateInterval(_.size($scope.slaves));
-          $timeout(poll, $scope.delay);
-        }
-      })
-      .error(function() {
-        if ($scope.delay >= 128000) {
-          $scope.delay = 2000;
-        } else {
-          $scope.delay = $scope.delay * 2;
-        }
+      // Redirect if we aren't the leader.
+      if ($scope.state.leader != $scope.state.pid) {
+        $scope.redirect = 6000;
+        $scope.leader = $scope.state.leader.split("@")[1];
+        $("#not-leader-alert").show();
 
-        $scope.retry = $scope.delay;
         var countdown = function() {
-          if ($scope.retry === 0) {
-            $scope.errorModalClose();
+          if ($scope.redirect == 0) {
+            // TODO(benh): Use '$window'.
+            window.location = 'http://' + $scope.leader;
           } else {
-            $scope.retry = $scope.retry - 1000;
-            $scope.countdown = $timeout(countdown, 1000);
+            $scope.redirect = $scope.redirect - 1000;
+            $timeout(countdown, 1000);
           }
         };
-
         countdown();
-        $scope.errorModalOpen = true;
-      });
-  };
-
-  // Make it such that everytime we hide the error-modal, we stop the
-  // countdown and restart the polling.
-  $scope.errorModalClose = function() {
-    $scope.errorModalOpen = false;
-
-    if ($scope.countdown != null) {
-      if ($timeout.cancel($scope.countdown)) {
-        // Restart since they cancelled the countdown.
-        $scope.delay = 2000;
+        return false; // Don't continue polling.
       }
     }
 
-    // Start polling again, but do it asynchronously (and wait at
-    // least a second because otherwise the error-modal won't get
-    // properly shown).
-    $timeout(poll, 1000);
-  };
-
-  poll();
-}
-
+    // A cluster is named if the state returns a non-empty string name.
+    // Track whether this cluster is named in a Boolean for display purposes.
+    $scope.clusterNamed = !!$scope.state.cluster;
 
-function HomeCtrl($dialog, $scope) {
-  setNavbarActiveTab('home');
+    // Check for selected text, and allow up to 20 seconds to pass before
+    // potentially wiping the user highlighted text.
+    // TODO(bmahler): This is to avoid the annoying loss of highlighting when
+    // the tables update. Once we can have tighter granularity control on the
+    // angular.js dynamic table updates, we should remove this hack.
+    $scope.time_since_update += $scope.delay;
 
-  $scope.tables = {};
-  $scope.tables['frameworks'] = new Table('id');
-  $scope.tables['slaves'] = new Table('id');
-  $scope.tables['offers'] = new Table('id');
-  $scope.tables['completed_frameworks'] = new Table('id');
-
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
-
-  $scope.log = function($event) {
-    if (!$scope.state.log_dir) {
-      $dialog.messageBox(
-        'Logging to a file is not enabled',
-        "Set the 'log_dir' option if you wish to access the logs.",
-        [{label: 'Continue'}]
-      ).open();
-    } else {
-      pailer(
-          $scope.$location.host() + ':' + $scope.$location.port(),
-          '/master/log',
-          'Mesos Master');
+    if (hasSelectedText() && $scope.time_since_update < 20000) {
+      return true;
     }
-  };
-}
 
+    $scope.data = data;
 
-function DashboardCtrl($scope) {
-  setNavbarActiveTab('dashboard');
+    // Update the maps.
+    $scope.slaves = {};
+    $scope.frameworks = {};
+    $scope.offers = {};
+    $scope.completed_frameworks = {};
 
-  var context = cubism.context()
-    .step(1000)
-    .size(1440);
+    _.each($scope.state.slaves, function(slave) {
+      $scope.slaves[slave.id] = slave;
+    });
+
+    _.each($scope.state.frameworks, function(framework) {
+      $scope.frameworks[framework.id] = framework;
+      _.each(framework.offers, function(offer) {
+        $scope.offers[offer.id] = offer;
+      });
+    });
 
-  // Create a "cpus" horizon.
-  horizons.create(context, "cpus", random(context, "cpus"), [0, 10], "cpus");
+    _.each($scope.state.completed_frameworks, function(framework) {
+      $scope.completed_frameworks[framework.id] = framework;
+    });
 
-  // Create a "mem" horizon.
-  horizons.create(context, "mem", random(context, "mem"), [0, 10], "mb");
+    // Update the stats.
+    $scope.cluster = $scope.state.cluster;
+    $scope.total_cpus = 0;
+    $scope.total_mem = 0;
+    $scope.used_cpus = 0;
+    $scope.used_mem = 0;
+    $scope.offered_cpus = 0;
+    $scope.offered_mem = 0;
+
+    $scope.staged_tasks = $scope.state.staged_tasks;
+    $scope.started_tasks = $scope.state.started_tasks;
+    $scope.finished_tasks = $scope.state.finished_tasks;
+    $scope.killed_tasks = $scope.state.killed_tasks;
+    $scope.failed_tasks = $scope.state.failed_tasks;
+    $scope.lost_tasks = $scope.state.lost_tasks;
+
+    $scope.activated_slaves = $scope.state.activated_slaves;
+    $scope.deactivated_slaves = $scope.state.deactivated_slaves;
+
+    _.each($scope.slaves, function(slave) {
+      $scope.total_cpus += slave.resources.cpus;
+      $scope.total_mem += slave.resources.mem;
+    });
 
-  // Do any cleanup before we change the route.
-  $scope.$on('$beforeRouteChange', function() { context.stop(); });
-}
+    _.each($scope.frameworks, function(framework) {
+        $scope.used_cpus += framework.resources.cpus;
+        $scope.used_mem += framework.resources.mem;
+        $scope.active_tasks += framework.tasks.length;
+        $scope.completed_tasks += framework.completed_tasks.length;
 
+        framework.cpus_share = 0;
+        if ($scope.total_cpus > 0) {
+          framework.cpus_share = framework.resources.cpus / $scope.total_cpus;
+        }
 
-function FrameworksCtrl($scope) {
-  setNavbarActiveTab('frameworks');
+        framework.mem_share = 0;
+        if ($scope.total_mem > 0) {
+          framework.mem_share = framework.resources.mem / $scope.total_mem;
+        }
 
-  $scope.tables = {};
-  $scope.tables['frameworks'] = new Table('id');
+        framework.max_share = Math.max(framework.cpus_share, 
framework.mem_share);
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
-}
+        // If the executor ID is empty, this is a command executor with an
+        // internal executor ID generated from the task ID.
+        // TODO(brenden): Remove this once
+        // https://issues.apache.org/jira/browse/MESOS-527 is fixed.
+        _.each(framework.tasks, function(task) {
+          if (!task.executor_id) {
+            task.executor_id = task.id;
+          }
+        });
+        _.each(framework.completed_tasks, function(task) {
+          if (!task.executor_id) {
+            task.executor_id = task.id;
+          }
+        });
+    });
 
+    _.each($scope.offers, function(offer) {
+      $scope.offered_cpus += offer.resources.cpus;
+      $scope.offered_mem += offer.resources.mem;
+      offer.framework_name = $scope.frameworks[offer.framework_id].name;
+      offer.hostname = $scope.slaves[offer.slave_id].hostname;
+    });
 
-function FrameworkCtrl($scope, $routeParams) {
-  setNavbarActiveTab('frameworks');
+    $scope.used_cpus -= $scope.offered_cpus;
+    $scope.used_mem -= $scope.offered_mem;
 
-  $scope.tables = {};
-  $scope.tables['active_tasks'] = new Table('id');
-  $scope.tables['completed_tasks'] = new Table('id');
+    $scope.idle_cpus = $scope.total_cpus - ($scope.offered_cpus + 
$scope.used_cpus);
+    $scope.idle_mem = $scope.total_mem - ($scope.offered_mem + 
$scope.used_mem);
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
+    $scope.time_since_update = 0;
+    $.event.trigger('state_updated');
 
-  var update = function() {
-    if ($routeParams.id in $scope.completed_frameworks) {
-      $scope.framework = $scope.completed_frameworks[$routeParams.id];
-      $scope.alert_message = 'This framework has terminated!';
-      $('#alert').show();
-      $('#framework').show();
-    } else if ($routeParams.id in $scope.frameworks) {
-      $scope.framework = $scope.frameworks[$routeParams.id];
-      $('#framework').show();
-    } else {
-      $scope.alert_message = 'No framework found with ID: ' + $routeParams.id;
-      $('#alert').show();
-    }
+    return true; // Continue polling.
   }
 
-  if ($scope.state) {
-    update();
-  }
 
-  $(document).on('state_updated', update);
-  $scope.$on('$beforeRouteChange', function() {
-    $(document).off('state_updated', update);
-  });
-}
+  // Main controller that can be used to handle "global" events. E.g.,:
+  //     $scope.$on('$afterRouteChange', function() { ...; });
+  //
+  // In addition, the MainCntl encapsulates the "view", allowing the
+  // active controller/view to easily access anything in scope (e.g.,
+  // the state).
+  mesosApp.controller('MainCntl', function($scope, $http, $route, 
$routeParams, $location, $timeout) {
+    // Turn off the loading gif, turn on the navbar.
+    $("#loading").hide();
+    $("#navbar").show();
+
+    // Adding bindings into scope so that they can be used from within
+    // AngularJS expressions.
+    $scope._ = _;
+    $scope.stringify = JSON.stringify;
+    $scope.encodeURIComponent = encodeURIComponent;
+    $scope.basename = function(path) {
+      // This is only a basic version of basename that handles the cases we 
care
+      // about, rather than duplicating unix basename functionality perfectly.
+      if (path === '/') {
+        return path;  // Handle '/'.
+      }
 
+      // Strip a trailing '/' if present.
+      if (path.length > 0 && path.lastIndexOf('/') === (path.length - 1)) {
+        path = path.substr(0, path.length - 1);
+      }
+      return path.substr(path.lastIndexOf('/') + 1);
+    };
 
-function SlavesCtrl($scope) {
-  setNavbarActiveTab('slaves');
+    $scope.$location = $location;
+    $scope.delay = 2000;
+    $scope.retry = 0;
+    $scope.time_since_update = 0;
+
+    var poll = function() {
+      $http.get('master/state.json',
+                {transformResponse: function(data) { return data; }})
+        .success(function(data) {
+          if (update($scope, $timeout, data)) {
+            $scope.delay = updateInterval(_.size($scope.slaves));
+            $timeout(poll, $scope.delay);
+          }
+        })
+        .error(function() {
+          if ($scope.delay >= 128000) {
+            $scope.delay = 2000;
+          } else {
+            $scope.delay = $scope.delay * 2;
+          }
 
-  $scope.tables = {};
-  $scope.tables['slaves'] = new Table('id');
+          $scope.retry = $scope.delay;
+          var countdown = function() {
+            if ($scope.retry === 0) {
+              $scope.errorModalClose();
+            } else {
+              $scope.retry = $scope.retry - 1000;
+              $scope.countdown = $timeout(countdown, 1000);
+            }
+          };
+
+          countdown();
+          $scope.errorModalOpen = true;
+        });
+    };
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
-}
+    // Make it such that everytime we hide the error-modal, we stop the
+    // countdown and restart the polling.
+    $scope.errorModalClose = function() {
+      $scope.errorModalOpen = false;
 
+      if ($scope.countdown != null) {
+        if ($timeout.cancel($scope.countdown)) {
+          // Restart since they cancelled the countdown.
+          $scope.delay = 2000;
+        }
+      }
 
-function SlaveCtrl($dialog, $scope, $routeParams, $http, $q) {
-  setNavbarActiveTab('slaves');
+      // Start polling again, but do it asynchronously (and wait at
+      // least a second because otherwise the error-modal won't get
+      // properly shown).
+      $timeout(poll, 1000);
+    };
 
-  $scope.slave_id = $routeParams.slave_id;
+    poll();
+  });
 
-  $scope.tables = {};
-  $scope.tables['frameworks'] = new Table('id');
-  $scope.tables['completed_frameworks'] = new Table('id');
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
+  mesosApp.controller('HomeCtrl', function($dialog, $scope) {
+    setNavbarActiveTab('home');
 
-  var update = function() {
-    if (!($routeParams.slave_id in $scope.slaves)) {
-      $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
-      $('#alert').show();
-      return;
-    }
+    $scope.tables = {};
+    $scope.tables['frameworks'] = new Table('id');
+    $scope.tables['slaves'] = new Table('id');
+    $scope.tables['offers'] = new Table('id');
+    $scope.tables['completed_frameworks'] = new Table('id');
 
-    var pid = $scope.slaves[$routeParams.slave_id].pid;
-    var hostname = $scope.slaves[$routeParams.slave_id].hostname;
-    var id = pid.substring(0, pid.indexOf('@'));
-    var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
 
     $scope.log = function($event) {
       if (!$scope.state.log_dir) {
@@ -476,317 +366,435 @@ function SlaveCtrl($dialog, $scope, $routeParams, 
$http, $q) {
           [{label: 'Continue'}]
         ).open();
       } else {
-        pailer(host, '/slave/log', 'Mesos Slave');
+        pailer(
+            $scope.$location.host() + ':' + $scope.$location.port(),
+            '/master/log',
+            'Mesos Master');
       }
     };
+  });
 
-    var usageRequest = $http.jsonp(
-        'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
-
-    var stateRequest = $http.jsonp(
-        'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
 
-    $q.all([usageRequest, stateRequest]).then(function (responses) {
-      $scope.monitor = responses[0].data;
-      $scope.state = responses[1].data;
+  mesosApp.controller('DashboardCtrl', function($scope) {
+    setNavbarActiveTab('dashboard');
 
-      $scope.slave = {};
-      $scope.slave.frameworks = {};
-      $scope.slave.completed_frameworks = {};
+    var context = cubism.context()
+      .step(1000)
+      .size(1440);
 
-      $scope.slave.staging_tasks = 0;
-      $scope.slave.starting_tasks = 0;
-      $scope.slave.running_tasks = 0;
+    // Create a "cpus" horizon.
+    horizons.create(context, "cpus", random(context, "cpus"), [0, 10], "cpus");
 
-      // Update the framework map.
-      _.each($scope.state.frameworks, function(framework) {
-        $scope.slave.frameworks[framework.id] = framework;
-      });
+    // Create a "mem" horizon.
+    horizons.create(context, "mem", random(context, "mem"), [0, 10], "mb");
 
-      // Update the completed framework map.
-      _.each($scope.state.completed_frameworks, function(framework) {
-        $scope.slave.completed_frameworks[framework.id] = framework;
-      });
+    // Do any cleanup before we change the route.
+    $scope.$on('$beforeRouteChange', function() { context.stop(); });
+  });
 
-      // Compute the framework stats.
-      
_.each(_.values($scope.state.frameworks).concat(_.values($scope.state.completed_frameworks)),
-          function(framework) {
-            framework.num_tasks = 0;
-            framework.cpus = 0;
-            framework.mem = 0;
-
-            _.each(framework.executors, function(executor) {
-              framework.num_tasks += _.size(executor.tasks);
-              framework.cpus += executor.resources.cpus;
-              framework.mem += executor.resources.mem;
-            });
-      });
 
-      $('#slave').show();
-    },
-    function (reason) {
-      $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
-      $('#alert').show();
-    });
-  };
+  mesosApp.controller('FrameworksCtrl', function($scope) {
+    setNavbarActiveTab('frameworks');
 
-  if ($scope.state) {
-    update();
-  }
+    $scope.tables = {};
+    $scope.tables['frameworks'] = new Table('id');
 
-  $(document).on('state_updated', update);
-  $scope.$on('$beforeRouteChange', function() {
-    $(document).off('state_updated', update);
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
   });
-}
 
 
-function SlaveFrameworkCtrl($scope, $routeParams, $http, $q) {
-  setNavbarActiveTab('slaves');
+  mesosApp.controller('FrameworkCtrl', function($scope, $routeParams) {
+    setNavbarActiveTab('frameworks');
 
-  $scope.slave_id = $routeParams.slave_id;
-  $scope.framework_id = $routeParams.framework_id;
+    $scope.tables = {};
+    $scope.tables['active_tasks'] = new Table('id');
+    $scope.tables['completed_tasks'] = new Table('id');
 
-  $scope.tables = {};
-  $scope.tables['executors'] = new Table('id');
-  $scope.tables['completed_executors'] = new Table('id');
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
+    var update = function() {
+      if ($routeParams.id in $scope.completed_frameworks) {
+        $scope.framework = $scope.completed_frameworks[$routeParams.id];
+        $scope.alert_message = 'This framework has terminated!';
+        $('#alert').show();
+        $('#framework').show();
+      } else if ($routeParams.id in $scope.frameworks) {
+        $scope.framework = $scope.frameworks[$routeParams.id];
+        $('#framework').show();
+      } else {
+        $scope.alert_message = 'No framework found with ID: ' + 
$routeParams.id;
+        $('#alert').show();
+      }
+    };
 
-  var update = function() {
-    if (!($routeParams.slave_id in $scope.slaves)) {
-      $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
-      $('#alert').show();
-      return;
+    if ($scope.state) {
+      update();
     }
 
-    var pid = $scope.slaves[$routeParams.slave_id].pid;
-    var hostname = $scope.slaves[$routeParams.slave_id].hostname;
-    var id = pid.substring(0, pid.indexOf('@'));
-    var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
+    $(document).on('state_updated', update);
+    $scope.$on('$beforeRouteChange', function() {
+      $(document).off('state_updated', update);
+    });
+  });
+
+
+  mesosApp.controller('SlavesCtrl', function($scope) {
+    setNavbarActiveTab('slaves');
 
-    var usageRequest = $http.jsonp(
-        'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
+    $scope.tables = {};
+    $scope.tables['slaves'] = new Table('id');
 
-    var stateRequest = $http.jsonp(
-        'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
+  });
 
-    $q.all([usageRequest, stateRequest]).then(function (responses) {
-      var monitor = responses[0].data;
-      $scope.state = responses[1].data;
 
-      $scope.slave = {};
+  mesosApp.controller('SlaveCtrl', function($dialog, $scope, $routeParams, 
$http, $q) {
+    setNavbarActiveTab('slaves');
 
-      // Find the framework; it's either active or completed.
-      $scope.framework = 
_.find($scope.state.frameworks.concat($scope.state.completed_frameworks),
-          function(framework) {
-            return $scope.framework_id === framework.id;
-          });
+    $scope.slave_id = $routeParams.slave_id;
 
-      if (!$scope.framework) {
-        $scope.alert_message = 'No framework found with ID: ' + 
$routeParams.framework_id;
+    $scope.tables = {};
+    $scope.tables['frameworks'] = new Table('id');
+    $scope.tables['completed_frameworks'] = new Table('id');
+
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
+
+    var update = function() {
+      if (!($routeParams.slave_id in $scope.slaves)) {
+        $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
         $('#alert').show();
         return;
       }
 
-      // Construct maps of the executors.
-      $scope.framework.executors = _.object(
-          _.pluck($scope.framework.executors, 'id'), 
$scope.framework.executors);
-      $scope.framework.completed_executors = _.object(
-          _.pluck($scope.framework.completed_executors, 'id'), 
$scope.framework.completed_executors);
-
-      // Compute the framework stats.
-      $scope.framework.num_tasks = 0;
-      $scope.framework.cpus = 0;
-      $scope.framework.mem = 0;
-
-      _.each($scope.framework.executors, function(executor) {
-        $scope.framework.num_tasks += _.size(executor.tasks);
-        $scope.framework.cpus += executor.resources.cpus;
-        $scope.framework.mem += executor.resources.mem;
-      });
-
-      // Index the monitoring data.
-      $scope.monitor = {};
-
-      $scope.framework.resource_usage = {};
-      $scope.framework.resource_usage["cpu_time"] = 0.0;
-      $scope.framework.resource_usage["cpu_usage"] = 0.0;
-      $scope.framework.resource_usage["memory_rss"] = 0.0;
+      var pid = $scope.slaves[$routeParams.slave_id].pid;
+      var hostname = $scope.slaves[$routeParams.slave_id].hostname;
+      var id = pid.substring(0, pid.indexOf('@'));
+      var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
 
-      _.each(monitor, function(executor) {
-        if (!$scope.monitor[executor.framework_id]) {
-          $scope.monitor[executor.framework_id] = {};
+      $scope.log = function($event) {
+        if (!$scope.state.log_dir) {
+          $dialog.messageBox(
+            'Logging to a file is not enabled',
+            "Set the 'log_dir' option if you wish to access the logs.",
+            [{label: 'Continue'}]
+          ).open();
+        } else {
+          pailer(host, '/slave/log', 'Mesos Slave');
         }
-        $scope.monitor[executor.framework_id][executor.executor_id] = executor;
-
-        $scope.framework.resource_usage["cpu_time"] +=
-          executor.resource_usage.cpu_time;
-        $scope.framework.resource_usage["cpu_usage"] +=
-          executor.resource_usage.cpu_usage;
-        $scope.framework.resource_usage["memory_rss"] +=
-          executor.resource_usage.memory_rss;
-      });
+      };
 
-      $('#slave').show();
-    },
-    function (reason) {
-      $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
-      $('#alert').show();
-    });
-  };
+      var usageRequest = $http.jsonp(
+          'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
 
-  if ($scope.state) {
-    update();
-  }
+      var stateRequest = $http.jsonp(
+          'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
 
-  $(document).on('state_updated', update);
-  $scope.$on('$beforeRouteChange', function() {
-    $(document).off('state_updated', update);
-  });
-}
+      $q.all([usageRequest, stateRequest]).then(function (responses) {
+        $scope.monitor = responses[0].data;
+        $scope.state = responses[1].data;
+
+        $scope.slave = {};
+        $scope.slave.frameworks = {};
+        $scope.slave.completed_frameworks = {};
 
+        $scope.slave.staging_tasks = 0;
+        $scope.slave.starting_tasks = 0;
+        $scope.slave.running_tasks = 0;
 
-function SlaveExecutorCtrl($scope, $routeParams, $http, $q) {
-  setNavbarActiveTab('slaves');
+        // Update the framework map.
+        _.each($scope.state.frameworks, function(framework) {
+          $scope.slave.frameworks[framework.id] = framework;
+        });
 
-  $scope.slave_id = $routeParams.slave_id;
-  $scope.framework_id = $routeParams.framework_id;
-  $scope.executor_id = $routeParams.executor_id;
+        // Update the completed framework map.
+        _.each($scope.state.completed_frameworks, function(framework) {
+          $scope.slave.completed_frameworks[framework.id] = framework;
+        });
 
-  $scope.tables = {};
-  $scope.tables['tasks'] = new Table('id');
-  $scope.tables['queued_tasks'] = new Table('id');
-  $scope.tables['completed_tasks'] = new Table('id');
+        // Compute the framework stats.
+        
_.each(_.values($scope.state.frameworks).concat(_.values($scope.state.completed_frameworks)),
+            function(framework) {
+              framework.num_tasks = 0;
+              framework.cpus = 0;
+              framework.mem = 0;
+
+              _.each(framework.executors, function(executor) {
+                framework.num_tasks += _.size(executor.tasks);
+                framework.cpus += executor.resources.cpus;
+                framework.mem += executor.resources.mem;
+              });
+        });
 
-  $scope.columnClass = columnClass($scope);
-  $scope.selectColumn = selectColumn($scope);
+        $('#slave').show();
+      },
+      function (reason) {
+        $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
+        $('#alert').show();
+      });
+    };
 
-  var update = function() {
-    if (!($routeParams.slave_id in $scope.slaves)) {
-      $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
-      $('#alert').show();
-      return;
+    if ($scope.state) {
+      update();
     }
 
-    var pid = $scope.slaves[$routeParams.slave_id].pid;
-    var hostname = $scope.slaves[$routeParams.slave_id].hostname;
-    var id = pid.substring(0, pid.indexOf('@'));
-    var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
+    $(document).on('state_updated', update);
+    $scope.$on('$beforeRouteChange', function() {
+      $(document).off('state_updated', update);
+    });
+  });
 
-    var usageRequest = $http.jsonp(
-        'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
 
-    var stateRequest = $http.jsonp(
-        'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
+  mesosApp.controller('SlaveFrameworkCtrl', function($scope, $routeParams, 
$http, $q) {
+    setNavbarActiveTab('slaves');
 
-    $q.all([usageRequest, stateRequest]).then(function (responses) {
-      var monitor = responses[0].data;
-      $scope.state = responses[1].data;
+    $scope.slave_id = $routeParams.slave_id;
+    $scope.framework_id = $routeParams.framework_id;
 
-      $scope.slave = {};
+    $scope.tables = {};
+    $scope.tables['executors'] = new Table('id');
+    $scope.tables['completed_executors'] = new Table('id');
 
-      // Find the framework; it's either active or completed.
-      $scope.framework = 
_.find($scope.state.frameworks.concat($scope.state.completed_frameworks),
-          function(framework) {
-            return $scope.framework_id === framework.id;
-          });
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
 
-      if (!$scope.framework) {
-        $scope.alert_message = 'No framework found with ID: ' + 
$routeParams.framework_id;
+    var update = function() {
+      if (!($routeParams.slave_id in $scope.slaves)) {
+        $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
         $('#alert').show();
         return;
       }
 
-      // Look for the executor; it's either active or completed.
-      $scope.executor = 
_.find($scope.framework.executors.concat($scope.framework.completed_executors),
-          function(executor) {
-            return $scope.executor_id === executor.id;
-          });
+      var pid = $scope.slaves[$routeParams.slave_id].pid;
+      var hostname = $scope.slaves[$routeParams.slave_id].hostname;
+      var id = pid.substring(0, pid.indexOf('@'));
+      var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
 
-      if (!$scope.executor) {
-        $scope.alert_message = 'No executor found with ID: ' + 
$routeParams.executor_id;
-        $('#alert').show();
-        return;
-      }
+      var usageRequest = $http.jsonp(
+          'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
+
+      var stateRequest = $http.jsonp(
+          'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
+
+      $q.all([usageRequest, stateRequest]).then(function (responses) {
+        var monitor = responses[0].data;
+        $scope.state = responses[1].data;
 
-      // Index the monitoring data.
-      $scope.monitor = {};
+        $scope.slave = {};
 
-      _.each(monitor, function(executor) {
-        if (!$scope.monitor[executor.framework_id]) {
-          $scope.monitor[executor.framework_id] = {};
+        // Find the framework; it's either active or completed.
+        $scope.framework = 
_.find($scope.state.frameworks.concat($scope.state.completed_frameworks),
+            function(framework) {
+              return $scope.framework_id === framework.id;
+            });
+
+        if (!$scope.framework) {
+          $scope.alert_message = 'No framework found with ID: ' + 
$routeParams.framework_id;
+          $('#alert').show();
+          return;
         }
-        $scope.monitor[executor.framework_id][executor.executor_id] = executor;
+
+        // Construct maps of the executors.
+        $scope.framework.executors = _.object(
+            _.pluck($scope.framework.executors, 'id'), 
$scope.framework.executors);
+        $scope.framework.completed_executors = _.object(
+            _.pluck($scope.framework.completed_executors, 'id'), 
$scope.framework.completed_executors);
+
+        // Compute the framework stats.
+        $scope.framework.num_tasks = 0;
+        $scope.framework.cpus = 0;
+        $scope.framework.mem = 0;
+
+        _.each($scope.framework.executors, function(executor) {
+          $scope.framework.num_tasks += _.size(executor.tasks);
+          $scope.framework.cpus += executor.resources.cpus;
+          $scope.framework.mem += executor.resources.mem;
+        });
+
+        // Index the monitoring data.
+        $scope.monitor = {};
+
+        $scope.framework.resource_usage = {};
+        $scope.framework.resource_usage["cpu_time"] = 0.0;
+        $scope.framework.resource_usage["cpu_usage"] = 0.0;
+        $scope.framework.resource_usage["memory_rss"] = 0.0;
+
+        _.each(monitor, function(executor) {
+          if (!$scope.monitor[executor.framework_id]) {
+            $scope.monitor[executor.framework_id] = {};
+          }
+          $scope.monitor[executor.framework_id][executor.executor_id] = 
executor;
+
+          $scope.framework.resource_usage["cpu_time"] +=
+            executor.resource_usage.cpu_time;
+          $scope.framework.resource_usage["cpu_usage"] +=
+            executor.resource_usage.cpu_usage;
+          $scope.framework.resource_usage["memory_rss"] +=
+            executor.resource_usage.memory_rss;
+        });
+
+        $('#slave').show();
+      },
+      function (reason) {
+        $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
+        $('#alert').show();
       });
+    };
 
-      $('#slave').show();
-    },
-    function (reason) {
-      $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
-      $('#alert').show();
+    if ($scope.state) {
+      update();
+    }
+
+    $(document).on('state_updated', update);
+    $scope.$on('$beforeRouteChange', function() {
+      $(document).off('state_updated', update);
     });
-  };
+  });
 
-  if ($scope.state) {
-    update();
-  }
 
-  $(document).on('state_updated', update);
-  $scope.$on('$beforeRouteChange', function() {
-    $(document).off('state_updated', update);
-  });
-}
+  mesosApp.controller('SlaveExecutorCtrl', function($scope, $routeParams, 
$http, $q) {
+    setNavbarActiveTab('slaves');
 
+    $scope.slave_id = $routeParams.slave_id;
+    $scope.framework_id = $routeParams.framework_id;
+    $scope.executor_id = $routeParams.executor_id;
 
-function BrowseCtrl($scope, $routeParams, $http) {
-  setNavbarActiveTab('slaves');
+    $scope.tables = {};
+    $scope.tables['tasks'] = new Table('id');
+    $scope.tables['queued_tasks'] = new Table('id');
+    $scope.tables['completed_tasks'] = new Table('id');
 
-  var update = function() {
-    if ($routeParams.slave_id in $scope.slaves && $routeParams.path) {
-      $scope.slave_id = $routeParams.slave_id;
-      $scope.path = $routeParams.path;
+    $scope.columnClass = columnClass($scope);
+    $scope.selectColumn = selectColumn($scope);
+
+    var update = function() {
+      if (!($routeParams.slave_id in $scope.slaves)) {
+        $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
+        $('#alert').show();
+        return;
+      }
 
       var pid = $scope.slaves[$routeParams.slave_id].pid;
       var hostname = $scope.slaves[$routeParams.slave_id].hostname;
       var id = pid.substring(0, pid.indexOf('@'));
       var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
-      var url = 'http://' + host + '/files/browse.json?jsonp=JSON_CALLBACK';
 
-      $scope.slave_host = host;
+      var usageRequest = $http.jsonp(
+          'http://' + host + '/monitor/usage.json?jsonp=JSON_CALLBACK');
 
-      $scope.pail = function($event, path) {
-        pailer(host, path, decodeURIComponent(path));
-      };
+      var stateRequest = $http.jsonp(
+          'http://' + host + '/' + id + '/state.json?jsonp=JSON_CALLBACK');
 
-      // TODO(bmahler): Try to get the error code / body in the error callback.
-      // This wasn't working with the current version of angular.
-      $http.jsonp(url, {params: {path: $routeParams.path}})
-        .success(function(data) {
-          $scope.listing = data;
-          $('#listing').show();
-        })
-        .error(function() {
-          $scope.alert_message = 'Error browsing path: ' + $routeParams.path;
+      $q.all([usageRequest, stateRequest]).then(function (responses) {
+        var monitor = responses[0].data;
+        $scope.state = responses[1].data;
+
+        $scope.slave = {};
+
+        // Find the framework; it's either active or completed.
+        $scope.framework = 
_.find($scope.state.frameworks.concat($scope.state.completed_frameworks),
+            function(framework) {
+              return $scope.framework_id === framework.id;
+            });
+
+        if (!$scope.framework) {
+          $scope.alert_message = 'No framework found with ID: ' + 
$routeParams.framework_id;
+          $('#alert').show();
+          return;
+        }
+
+        // Look for the executor; it's either active or completed.
+        $scope.executor = 
_.find($scope.framework.executors.concat($scope.framework.completed_executors),
+            function(executor) {
+              return $scope.executor_id === executor.id;
+            });
+
+        if (!$scope.executor) {
+          $scope.alert_message = 'No executor found with ID: ' + 
$routeParams.executor_id;
           $('#alert').show();
+          return;
+        }
+
+        // Index the monitoring data.
+        $scope.monitor = {};
+
+        _.each(monitor, function(executor) {
+          if (!$scope.monitor[executor.framework_id]) {
+            $scope.monitor[executor.framework_id] = {};
+          }
+          $scope.monitor[executor.framework_id][executor.executor_id] = 
executor;
         });
-    } else {
-      if (!($routeParams.slave_id in $scope.slaves)) {
-        $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
+
+        $('#slave').show();
+      },
+      function (reason) {
+        $scope.alert_message = 'Failed to get slave usage / state: ' + reason;
+        $('#alert').show();
+      });
+    };
+
+    if ($scope.state) {
+      update();
+    }
+
+    $(document).on('state_updated', update);
+    $scope.$on('$beforeRouteChange', function() {
+      $(document).off('state_updated', update);
+    });
+  });
+
+
+  mesosApp.controller('BrowseCtrl', function($scope, $routeParams, $http) {
+    setNavbarActiveTab('slaves');
+
+    var update = function() {
+      if ($routeParams.slave_id in $scope.slaves && $routeParams.path) {
+        $scope.slave_id = $routeParams.slave_id;
+        $scope.path = $routeParams.path;
+
+        var pid = $scope.slaves[$routeParams.slave_id].pid;
+        var hostname = $scope.slaves[$routeParams.slave_id].hostname;
+        var id = pid.substring(0, pid.indexOf('@'));
+        var host = hostname + ":" + pid.substring(pid.lastIndexOf(':') + 1);
+        var url = 'http://' + host + '/files/browse.json?jsonp=JSON_CALLBACK';
+
+        $scope.slave_host = host;
+
+        $scope.pail = function($event, path) {
+          pailer(host, path, decodeURIComponent(path));
+        };
+
+        // TODO(bmahler): Try to get the error code / body in the error 
callback.
+        // This wasn't working with the current version of angular.
+        $http.jsonp(url, {params: {path: $routeParams.path}})
+          .success(function(data) {
+            $scope.listing = data;
+            $('#listing').show();
+          })
+          .error(function() {
+            $scope.alert_message = 'Error browsing path: ' + $routeParams.path;
+            $('#alert').show();
+          });
       } else {
-        $scope.alert_message = 'Missing "path" request parameter.';
+        if (!($routeParams.slave_id in $scope.slaves)) {
+          $scope.alert_message = 'No slave found with ID: ' + 
$routeParams.slave_id;
+        } else {
+          $scope.alert_message = 'Missing "path" request parameter.';
+        }
+        $('#alert').show();
       }
-      $('#alert').show();
-    }
-  };
+    };
 
-  if ($scope.state) {
-    update();
-  }
+    if ($scope.state) {
+      update();
+    }
 
-  $(document).on('state_updated', update);
-  $scope.$on('$beforeRouteChange', function() {
-    $(document).off('state_updated', update);
+    $(document).on('state_updated', update);
+    $scope.$on('$beforeRouteChange', function() {
+      $(document).off('state_updated', update);
+    });
   });
-}
+})();

Reply via email to