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