aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject [01/15] Role and Job pages using new Angular UI.
Date Tue, 22 Apr 2014 17:59:45 GMT
Repository: incubator-aurora
Updated Branches:
  refs/heads/master 48a52baf4 -> 88d25ed94


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st b/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st
deleted file mode 100644
index b53f352..0000000
--- a/src/main/resources/org/apache/aurora/scheduler/http/schedulerzrole.st
+++ /dev/null
@@ -1,242 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8">
-    <meta name="google" value="notranslate">
-    <link rel="icon" href="/images/aurora.png" type="image/png" />
-    <title>Aurora $cluster_name$ scheduler - Jobs for $role$</title>
-    <link href="/css/bootstrap.min.css" rel="stylesheet" />
-    <link href="/css/jquery.dataTables.css" rel="stylesheet" />
-    <script type="text/javascript" src="/js/jquery.min.js"></script>
-    <script type="text/javascript" src="/js/bootstrap.min.js"></script>
-    <script type="text/javascript" src="/js/util.js"></script>
-    <script type="text/javascript" src="/js/jquery.dataTables.min.js"></script>
-    <script type="text/javascript" src="/js/dataTables.bootstrap.js"></script>
-    <script type="text/javascript" src="/js/dataTables.localstorage.js"></script>
-    <script type="text/javascript" src="/js/dataTables.htmlNumberType.js"></script>
-  </head>
-  <body>
-    <div class="container-fluid">
-      <div class="row-fluid">
-        <center>
-          <h2>Jobs for role $role$ $if(environment)$ and environment $environment$ $endif$ </h2>
-        </center>
-      </div>
-
-      <div class="row-fluid">
-        <ul class="breadcrumb">
-          <li>
-            <a href="/scheduler">Home</a> <span class="divider">></span>
-          </li>
-          $if(environment)$
-           <li><a href="/scheduler/$role$">Role : $role$</a> <span class="divider">></span></li>
-           <li>Environment: $environment$</li>
-          $else$
-            <li class="active">Role: $role$</li>
-          $endif$
-        </ul>
-      </div>
-
-      $if(jobs)$
-        $if(environment)$
-          <span/>
-        $else$
-          <div class="row-fluid" style="margin-bottom: 20px;">
-            <button id="prod-resources-toggle-btn" class="btn">
-              <i class="icon-plus" style="margin-right: 3px;"></i><span class="btn-text">Show Resource Consumption</span>
-            </button>
-            <div id="prod-resources" class="collapse" style="width: 600px; margin-top: 10px;">
-              <table class="table table-striped table-condensed table-hover">
-                <thead>
-                  <th>Resource
-                  <th><span class="label" title="Resources used by production jobs(counted against quota)">Production consumption</span>
-                  <th><span class="label" title="Quota allocated in production">Quota</span>
-                  <th><span class="label" title="Resources used by non-production jobs (not counted against your quota)">Non-Production consumption</span>
-                </thead>
-                <tbody>
-                  <tr>
-                    <td>CPU
-                    <td>$prodResourcesUsed.numCpus$ cores
-                    <td>$resourceQuota.numCpus$ cores
-                    <td>$nonProdResourcesUsed.numCpus$ cores
-                  </tr>
-                  <tr>
-                    <td>RAM
-                    <td><script>document.write(scaleMb($prodResourcesUsed.ramMb$));</script>
-                    <td><script>document.write(scaleMb($resourceQuota.ramMb$));</script>
-                    <td><script>document.write(scaleMb($nonProdResourcesUsed.ramMb$));</script>
-                  </tr>
-                  <tr>
-                    <td>Disk
-                    <td><script>document.write(scaleMb($prodResourcesUsed.diskMb$));</script>
-                    <td><script>document.write(scaleMb($resourceQuota.diskMb$));</script>
-                    <td><script>document.write(scaleMb($nonProdResourcesUsed.diskMb$));</script>
-                  </tr>
-                </tbody>
-              </table>
-            </div>
-          </div>
-         $endif$
-
-        <div class="row-fluid" style="margin-bottom: 20px;">
-          <table id="jobs-table" class="table table-bordered table-striped table-condensed table-hover">
-            <thead>
-              <th>Job Type
-              <th>Environment
-              <th>Job
-              <th>Pending Tasks
-              <th>Active Tasks
-              <th>Finished Tasks
-              <th>Failed Tasks
-            </thead>
-            <tbody>
-              $jobs:{ job |
-              <tr>
-                <td>
-                  <span class="jobType">$job.type$</span>
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$">$job.environment$</a>
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$/$job.name$">$job.name$</a>
-                  <span class="job-status pull-right badge"></span>
-                  <span class="production-badge pull-right badge"></span>
-                  <input type="hidden" class="production" value="$job.production$" />
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$/$job.name$?status=pending">$job.pendingTaskCount$</a>
-                  <input type="hidden" class="pending-tasks" value="$job.pendingTaskCount$" />
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$/$job.name$?status=running">$job.activeTaskCount$</a>
-                  <input type="hidden" class="active-tasks" value="$job.activeTaskCount$" />
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$/$job.name$?status=finished">$job.finishedTaskCount$</a>
-                  <input type="hidden" class="finished-tasks" value="$job.finishedTaskCount$" />
-                <td>
-                  <a href="/scheduler/$role$/$job.environment$/$job.name$?status=failed">$job.failedTaskCount$</a>
-                  <input type="hidden" class="failed-tasks" value="$job.failedTaskCount$" />
-                  <input type="hidden" class="recently-failed-tasks"
-                      value="$job.recentlyFailedTaskCount$" />
-              </tr>
-              }$
-            </tbody>
-          </table>
-        </div>
-      $endif$
-
-      $if(cronJobs)$
-      <div class="row-fluid">
-        <h4>Cron jobs</h4>
-        <div>
-          <table id="cron-jobs-table" class="table table-bordered table-striped table-condensed table-hover">
-            <thead>
-              <th>Environment
-              <th>Name
-              <th>Tasks
-              <th>Schedule
-              <th>Next Run
-              <th>Collision policy
-              <th>Metadata
-            </thead>
-            <tbody>
-              $cronJobs:{ job |
-              <tr>
-                <td>$job.environment$
-                <td>$job.name$
-                <td>$job.pendingTaskCount$
-                <td>$job.cronSchedule$
-                <td><script>document.write(printDate($job.nextRun$));</script>
-                <td>$job.cronCollisionPolicy$
-                <td>$job.metadata$
-              </tr>
-              }$
-            </tbody>
-          </table>
-        </div>
-      </div>
-      $endif$
-    </div>
-
-    $if(exception)$
-      <font color='red'>Exception: $exception$</font>
-    $endif$
-
-    <script type="text/javascript">
-      jQuery(function(){
-        jQuery('#jobs-table').dataTable({
-          'iDisplayLength': 25,
-          'aLengthMenu': [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
-          'bStateSave': false,
-          'aoColumns': [
-            {'bSearchable': true},
-            {'bSearchable': false},
-            {'bSearchable': true},
-            {'bSearchable': false, 'sType': 'num-html'},
-            {'bSearchable': false, 'sType': 'num-html'},
-            {'bSearchable': false, 'sType': 'num-html'},
-            {'bSearchable': false, 'sType': 'num-html'}
-          ],
-          'fnDrawCallback': drawBadges
-        });
-        jQuery('#cron-jobs-table').dataTable({
-          'iDisplayLength': 25,
-          'aLengthMenu': [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
-          'bStateSave': false,
-          'aoColumns': [
-            {'bSearchable': false},
-            {'bSearchable': true},
-            {'bSearchable': false},
-            {'bSearchable': false, 'bSortable': false},
-            {'bSearchable': false},
-            {'bSearchable': true},
-            {'bSearchable': true, 'bSortable': true}
-          ]
-        });
-
-        // Bind click event to button
-        jQuery('#prod-resources-toggle-btn').on('click', function(e){
-          e.stopPropagation();
-          e.preventDefault();
-          jQuery('#prod-resources').collapse('toggle');
-        });
-
-        // Switch between + and - icons based on button state
-        jQuery('#prod-resources').on('show', function(){
-          var parent = jQuery(this).parent();
-          var icon = parent.find('i');
-          var buttonText = parent.find('.btn-text');
-
-          icon.addClass('icon-minus').removeClass('icon-plus');
-          buttonText.html('Hide Resource Consumption');
-        });
-
-        jQuery('#prod-resources').on('hide', function(){
-          var parent = jQuery(this).parent();
-          var icon = parent.find('i');
-          var buttonText = parent.find('.btn-text');
-
-          icon.addClass('icon-plus').removeClass('icon-minus');
-          buttonText.html('Show Resource Consumption');
-        });
-
-        function drawBadges() {
-          jQuery('#jobs-table>tbody').find('tr').each(function(){
-            var tr = jQuery(this);
-            displayProductionBadge(tr);
-          });
-        }
-
-        function displayProductionBadge(tr) {
-          var productionBadgeElem = tr.find('.production-badge');
-          var production = tr.find('.production').val();
-          if (production == "true") {
-            productionBadgeElem.addClass('badge-important').html('P');
-            productionBadgeElem.attr('title', 'This is a production job. This job gets higher priority over a non-production job.');
-          } else {
-            productionBadgeElem.hide();
-          }
-
-          productionBadgeElem.tooltip();
-        }
-      });
-    </script>
-  </body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html
new file mode 100644
index 0000000..23f0cdc
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/breadcrumb.html
@@ -0,0 +1,17 @@
+<div class='row-fluid'>
+  <ul class='breadcrumb'>
+    <li><a href='/scheduler'>Home</a><span ng-if='role' class='divider'>></span></li>
+
+    <li ng-if='role && !environment' class='active'>Role: {{role}}</a></li>
+    <li ng-if='role && environment'><a href='/scheduler/{{role}}'>Role: {{role}}</a>
+      <span class='divider'>></span></li>
+
+    <li ng-if='environment && !job' class='active'>Environment: {{environment}}</li>
+    <li ng-if='environment && job'>
+      <a href='/scheduler/{{role}}/{{environment}}'>Environment: {{environment}}</a>
+      <span class='divider'>></span>
+    </li>
+
+    <li ng-if='job' class='active'>Job: {{job}}</li>
+  </ul>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css b/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css
index ade850c..dc515ac 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/css/app.css
@@ -15,3 +15,26 @@
 .sort-descent:before {
   content: "\25B4";
 }
+
+ul.breadcrumb {
+  margin: 0 0 0;
+}
+
+.page-header {
+  margin: 0 0 0;
+  border-bottom: 0px;
+}
+
+.summaryButton {
+  margin-bottom: 10px;
+}
+
+.schedulingStatus {
+  border-bottom: 1px #aaaaaa dashed;
+}
+
+.bottomRight {
+  bottom: 10px;
+  right: 10px;
+  position: fixed;
+}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html
new file mode 100644
index 0000000..96dcf11
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/error.html
@@ -0,0 +1,7 @@
+<div>
+  <p class='lead text-center text-warning'>
+    An error occurred when querying the server. Please reload this page.
+  </p>
+
+  <p class='text-center text-warning'>{{error}}</p>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html
new file mode 100644
index 0000000..d367b81
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/home.html
@@ -0,0 +1,23 @@
+<div>
+  <div ng-show='error'>
+    <error/>
+  </div>
+
+  <div ng-hide='error'>
+    <div>
+      <breadcrumb/>
+    </div>
+
+    <div class='page-header'>
+      <h2 class='text-center'>{{title}}</h2>
+    </div>
+
+    <div>
+      <smart-table config='roleSummaryTableConfig'
+                   columns='roleSummaryColumns'
+                   rows='roleSummaries'
+                   class='table table-striped table-hover table-bordered table-condensed'>
+      </smart-table>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html
index 36225d1..817bd55 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/index.html
@@ -1,50 +1,49 @@
 <!doctype html>
-<html lang="en" ng-app="auroraUI">
+<html lang='en' ng-app='auroraUI'>
 <head>
-  <meta charset="utf-8">
+  <meta charset='utf-8'>
+  <meta name='google' value='notranslate'>
+  <link rel='icon' href='/images/aurora.png' type='image/png'/>
   <title>Aurora UI</title>
-  <link rel="stylesheet" href="/css/app.css"/>
 </head>
 <body class='ng-cloak'>
 
-<div class='container'>
-  <div ng-controller='JobSummaryController'>
-    <div ng-show="{{error}}">
-      <p class="lead text-center text-warning"> {{reloadMsg}}</p>
-      <p class="text-center text-warning"> {{errorMsg}}</p>
-    </div>
-
-    <div ng-hide="{{error}}">
-      <div class='page-header'>
-        <h2 class="text-center">{{title}}</h2>
-      </div>
-      <div>
-        <smart-table config="globalConfig" columns="columnCollection" rows="rowCollection"
-                     class='table table-striped table-hover table-bordered table-condensed'>
-        </smart-table>
-      </div>
-    </div>
-  </div>
+<div class='container-fluid'>
+  <ng-view></ng-view>
 </div>
 
 <!-- Thrift -->
-<script src="/js/thrift.js"></script>
-<script src="/js/apiTypes.js"></script>
-<script src="/js/readOnlyScheduler.js"></script>
+<script src='/js/thrift.js'></script>
+<script src='/js/apiTypes.js'></script>
+<script src='/js/readOnlyScheduler.js'></script>
 
 <!-- Angular -->
-<script src="/js/angular.js"></script>
-<script src="/js/app.js"></script>
-<script src="/js/controllers.js"></script>
-<script src="/js/directives.js"></script>
-<script src="/js/services.js"></script>
+<script src='/js/angular.js'></script>
+<script src='/js/angular-route.js'></script>
+<script src='/js/app.js'></script>
+<script src='/js/controllers.js'></script>
+<script src='/js/directives.js'></script>
+<script src='/js/services.js'></script>
+<script src='/js/filters.js'></script>
 
 <!-- Bootstrap -->
-<script src="/js/jquery.min.js"></script>
-<script src="/js/bootstrap.min.js"></script>
-<link href="/css/bootstrap.min.css" rel="stylesheet">
+<script src='/js/jquery.min.js'></script>
+<script src='/js/bootstrap.min.js'></script>
+<link href='/css/bootstrap.min.css' rel='stylesheet'>
+<link href='/img/glyphicons-halflings.png' rel='stylesheet'>
+<link href='/css/bootstrap.icon-large.min.css' rel='stylesheet'>
+<link href='/img/glyphicons.png' rel='stylesheet'>
 
-<!--smart table-->
-<script src="/js/smartTable.js"></script>
+<!-- smart table -->
+<script src='/js/smartTable.js'></script>
+
+<!-- underscore -->
+<script src='/js/underscore.js'></script>
+
+<!-- moment.js -->
+<script src='/js/moment.js'></script>
+
+<!-- Load our css at the end so we can override bootstrap css properties as needed -->
+<link rel='stylesheet' href='/css/app.css'/>
 </body>
 </html>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html
new file mode 100644
index 0000000..cd78c20
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/job.html
@@ -0,0 +1,68 @@
+<div>
+  <div ng-show='error'>
+    <error/>
+  </div>
+
+  <div ng-hide='error'>
+    <div class='container-fluid'>
+      <div>
+        <breadcrumb/>
+      </div>
+
+      <div class='container-fluid'>
+        <div class='page-header'>
+          <h2 class='text-center'>
+            Job <em>{{job}}</em> in role <em>{{role}}</em> and environment <em>{{environment}}</em>
+            <span ng-if='jobDashboardUrl'>
+            <a ng-href='{{jobDashboardUrl}}' title='Container stats for this job'>
+              <i class='icon-large icon-stats'></i>
+            </a>
+            </span>
+          </h2>
+        </div>
+      </div>
+
+      <div class='container-fluid'>
+        <button class="btn summaryButton" ng-click='toggleSummaryVisibility()'>
+          {{summaryButtonText}}
+        </button>
+        <div ng-if='showSummary'>
+          <div class='row'>
+            <div class='span8'>
+              <smart-table config='taskSummaryTableConfig'
+                           columns='taskSummaryTableColumns'
+                           rows='taskSummary'
+                           class='table table-striped table-hover table-bordered table-condensed'>
+              </smart-table>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class='container-fluid'>
+        <h3>Active tasks</h3>
+
+        <div>
+          <smart-table config='activeTasksTableConfig'
+                       columns='activeTasksTableColumns'
+                       rows='activeTasks'
+                       class='table table-striped table-hover table-bordered table-condensed'>
+          </smart-table>
+        </div>
+      </div>
+
+      <div class='container-fluid'>
+        <h3>Completed tasks</h3>
+        <smart-table config='completedTasksTableConfig'
+                     columns='completedTasksTableColumns'
+                     rows='completedTasks'
+                     class='table table-striped table-hover table-bordered table-condensed'>
+        </smart-table>
+      </div>
+
+      <div class='container-fluid'>
+        <a class='bottomRight' ng-click='toggleTaskInfoLinkVisibility()'>π</a>
+      </div>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html
new file mode 100644
index 0000000..2deccf4
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/jobLink.html
@@ -0,0 +1 @@
+<div class='job-link'></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js
index db6ea99..8806964 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/app.js
@@ -1,4 +1,24 @@
 'use strict';
 
 // Declare app level module which depends on filters, and services
-var auroraUI = angular.module('auroraUI', ['auroraUI.controllers', 'smartTable.table']);
+var auroraUI = angular.module('auroraUI', ['ngRoute', 'auroraUI.controllers', 'smartTable.table']);
+
+auroraUI.config(function ($routeProvider, $locationProvider) {
+  $routeProvider.when("/scheduler",
+    {templateUrl: '/home.html', controller: 'RoleSummaryController'});
+
+  $routeProvider.when("/scheduler/:role",
+    {templateUrl: '/role.html', controller: 'JobSummaryController'});
+
+  $routeProvider.when("/scheduler/:role/:environment",
+    {templateUrl: '/role.html', controller: 'JobSummaryController'});
+
+  $routeProvider.when("/scheduler/:role/:environment/:job",
+    {templateUrl: '/job.html', controller: 'JobController'});
+
+  $routeProvider.otherwise({redirectTo: function (location, path) {
+    window.location.href = path;
+  }});
+
+  $locationProvider.html5Mode(true);
+});

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js
index 7cd5344..e0b3aab 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/controllers.js
@@ -4,28 +4,40 @@
 
 var auroraUIControllers = angular.module('auroraUI.controllers', []);
 
-auroraUIControllers.controller('JobSummaryController',
-  function ($scope, $window, auroraClient) {
+var infoTableConfig = {
+  isGlobalSearchActivated: true,
+  isPaginationEnabled: true,
+  itemsByPage: 25,
+  maxSize: 8,
+  selectionMode: 'single'
+};
+
+var summaryTableConfig = {
+  isPaginationEnabled: false,
+  isGlobalSearchActivated: false,
+  selectionMode: 'none'
+};
+
+auroraUIControllers.controller('RoleSummaryController',
+  function ($scope, auroraClient) {
     $scope.title = 'Scheduled Jobs Summary';
 
-    $scope.error = false;
-    $scope.reloadMsg = "An error occurred when querying the server. Please reload this page.";
-    $scope.errorMsg = "";
+    $scope.error = '';
 
-    $scope.columnCollection = [
+    $scope.roleSummaryColumns = [
       {label: 'Role', map: 'role', cellTemplateUrl: 'roleLink.html'},
       {label: 'Jobs', map: 'jobCount'},
       {label: 'Cron Jobs', map: 'cronJobCount'}
     ];
 
-    $scope.rowCollection = parseResponse(auroraClient.getRoleSummary());
+    $scope.roleSummaries = parseResponse(auroraClient.getRoleSummary());
 
     function parseResponse(response) {
-      $scope.error = response.error;
-      $scope.errorMsg = response.errorMsg;
+      $scope.error = response.error ? 'Error requesting role summary: ' + response.error : '';
 
-      // Not the best way to set the page title, but this works on all browsers.
-      $window.document.title = response.pageTitle;
+      if ($scope.error) {
+        return [];
+      }
 
       // TODO(Suman Karumuri): Replace sort with defaultSortColumn once it lands
       // https://github.com/lorenzofox3/Smart-Table/pull/61
@@ -40,10 +52,410 @@ auroraUIControllers.controller('JobSummaryController',
       });
     }
 
-    $scope.globalConfig = {
-      isGlobalSearchActivated: true,
-      isPaginationEnabled: true,
-      itemsByPage: 25,
-      maxSize: 8
+    $scope.roleSummaryTableConfig = infoTableConfig;
+  });
+
+auroraUIControllers.controller('JobSummaryController',
+  function ($scope, $routeParams, auroraClient) {
+    $scope.role = $routeParams.role;
+    $scope.environment = $routeParams.environment;
+
+    $scope.error = '';
+
+    var showResourcesMsg = 'Show Resource Consumption';
+    var hideResourcesMsg = 'Hide Resource Consumption';
+    $scope.showResources = false;
+    $scope.resourceButtonText = showResourcesMsg;
+
+    $scope.toggleResourceVisibility = function () {
+      $scope.showResources = !$scope.showResources;
+      $scope.resourceButtonText = ($scope.showResources ? showResourcesMsg : hideResourcesMsg);
     };
+
+    $scope.jobsTableColumns = [
+      {label: 'Job Type', map: 'jobType'},
+      {label: 'Environment', map: 'environment', cellTemplateUrl: '/roleEnvLink.html'},
+      {label: 'Job', map: 'jobName', cellTemplateUrl: '/jobLink.html'},
+      {label: 'production', map: 'isProduction'},
+      {label: 'Pending Tasks', map: 'pendingTasks'},
+      {label: 'Active Tasks', map: 'activeTasks'},
+      {label: 'Finished Tasks', map: 'finishedTasks'},
+      {label: 'Failed Tasks', map: 'failedTasks'}
+    ];
+
+    $scope.cronJobsTableColumns = [
+      {label: 'Environment', map: 'environment', cellTemplateUrl: '/roleEnvLink.html'},
+      {label: 'Job Name', map: 'jobName'},
+      {label: 'Tasks', map: 'tasks'},
+      {label: 'Schedule', map: 'schedule'},
+      {label: 'Next Run',
+        map: 'nextCronRun',
+        formatFunction: function (value, format) {
+          return printDate(value);
+        }
+      },
+      {label: 'Collision Policy', map: 'collisionPolicy'},
+      {label: 'Metadata', map: 'metadata'}
+    ];
+
+    $scope.jobsTableConfig = infoTableConfig;
+
+    $scope.cronJobsTableConfig = infoTableConfig;
+
+    $scope.cronJobs = [];
+
+    $scope.jobs = getJobs();
+
+    function getJobs() {
+      var summaries = auroraClient.getJobSummary($scope.role);
+      $scope.error = summaries.error ? 'Error fetching job summaries: ' + summaries.error : '';
+
+      if ($scope.error) {
+        return [];
+      }
+
+      var jobSummaries = summaries.jobs;
+
+      if ($scope.environment) {
+        jobSummaries = _.filter(jobSummaries, function (summary) {
+          return summary.job.key.environment === $scope.environment;
+        });
+      }
+
+      var byJobName = function (summary) {
+        return summary.jobName;
+      };
+
+      $scope.cronJobs = _.chain(jobSummaries)
+        .filter(function (summary) {
+          return isCronJob(summary.job);
+        })
+        .map(function (summary) {
+          return {
+            role: $scope.role, // required for roleEnvLink directive
+            environment: summary.job.key.environment,
+            jobName: summary.job.taskConfig.jobName,
+            tasks: summary.job.instanceCount,
+            schedule: summary.job.cronSchedule,
+            nextCronRun: summary.nextCronRunMs,
+            collisionPolicy: getCronCollisionPolicy(summary.job.cronCollisionPolicy),
+            metadata: getMetadata(summary.job.taskConfig.metadata)
+          };
+        })
+        .sortBy(byJobName)
+        .value();
+
+      return _.chain(jobSummaries)
+        .reject(isCronJobWithNoTasks)
+        .map(function (summary) {
+          return {
+            role: $scope.role, // required for roleEnvLink directive
+            environment: summary.job.key.environment,
+            jobName: summary.job.taskConfig.jobName,
+            jobType: getJobType(summary.job),
+            isProduction: summary.job.taskConfig.production ? 'yes' : '',
+            pendingTasks: summary.stats.pendingTaskCount,
+            activeTasks: summary.stats.activeTaskCount,
+            finishedTasks: summary.stats.finishedTaskCount,
+            failedTasks: summary.stats.failedTaskCount
+          };
+        })
+        .sortBy(byJobName)
+        .value();
+    }
+
+    function getJobType(job) {
+      if (job.taskConfig.isService) {
+        return 'service';
+      }
+
+      if (isCronJob(job)) {
+        return 'cron';
+      }
+
+      return 'adhoc';
+    }
+
+    function isCronJob(job) {
+      return job.cronSchedule !== null;
+    }
+
+    function isCronJobWithNoTasks(summary) {
+      var stats = summary.stats;
+      var taskCount = stats.pendingTaskCount
+        + stats.activeTaskCount
+        + stats.finishedTaskCount
+        + stats.failedTaskCount;
+      return isCronJob(summary.job) && taskCount === 0;
+    }
+
+    function getMetadata(attributes) {
+      return _.map(attributes,function (attribute) {
+        return attribute.key + ': ' + attribute.value;
+      }).join(', ');
+    }
+
+    function getCronCollisionPolicy(cronCollisionPolicy) {
+      return _.keys(CronCollisionPolicy)[cronCollisionPolicy ? cronCollisionPolicy : 0];
+    }
+
+    // TODO(Suman Karumuri): Replace printDate with a more user friendly directive.
+    function printDate(timestamp) {
+      function pad(number) {
+        number = '' + number;
+        if (number.length < 2) {
+          return '0' + number;
+        }
+        return number;
+      }
+
+      var d = new Date(timestamp);
+      return pad(d.getUTCMonth() + 1) + '/'
+        + pad(d.getUTCDate()) + ' '
+        + pad(d.getUTCHours()) + ':'
+        + pad(d.getUTCMinutes()) + ':'
+        + pad(d.getUTCSeconds())
+        + ' UTC ('
+        + pad(d.getMonth() + 1) + '/'
+        + pad(d.getDate()) + ' '
+        + pad(d.getHours()) + ':'
+        + pad(d.getMinutes()) + ':'
+        + pad(d.getSeconds())
+        + ' local)';
+    }
   });
+
+auroraUIControllers.controller('QuotaController',
+  function ($scope, $filter, auroraClient) {
+    $scope.error = '';
+
+    $scope.resourcesTableColumns = [
+      {label: 'Resource', map: 'resource'},
+      {label: 'Production Consumption', map: 'prodConsumption'},
+      {label: 'Quota', map: 'quota'},
+      {label: 'Non-Production Consumption', map: 'nonProdConsumption'}
+    ];
+
+    $scope.resourcesTableConfig = summaryTableConfig;
+
+    $scope.resources = getQuota();
+
+    function getQuota() {
+      var quotaResponse = auroraClient.getQuota($scope.role);
+      $scope.error = quotaResponse.error ? 'Error fetching quota: ' + quotaResponse.error : '';
+
+      if ($scope.error) {
+        return [];
+      }
+
+      var consumption = quotaResponse.quota;
+      return [
+        {
+          resource: 'CPU',
+          prodConsumption: $filter('toCores')(consumption.prodConsumption.numCpus),
+          quota: $filter('toCores')(consumption.quota.numCpus),
+          nonProdConsumption: $filter('toCores')(consumption.nonProdConsumption.numCpus)
+        },
+        {
+          resource: 'RAM',
+          prodConsumption: $filter('scaleMb')(consumption.prodConsumption.ramMb),
+          quota: $filter('scaleMb')(consumption.quota.ramMb),
+          nonProdConsumption: $filter('scaleMb')(consumption.nonProdConsumption.ramMb)
+        },
+        {
+          resource: 'Disk',
+          prodConsumption: $filter('scaleMb')(consumption.prodConsumption.diskMb),
+          quota: $filter('scaleMb')(consumption.quota.diskMb),
+          nonProdConsumption: $filter('scaleMb')(consumption.nonProdConsumption.diskMb)
+        }
+      ];
+    }
+  }
+);
+
+auroraUIControllers.controller('JobController',
+  function ($scope, $routeParams, auroraClient, taskUtil) {
+    $scope.error = '';
+
+    $scope.role = $routeParams.role;
+    $scope.environment = $routeParams.environment;
+    $scope.job = $routeParams.job;
+
+    $scope.taskSummary = [];
+    $scope.taskSummaryTableColumns = [
+      {label: 'Instances',
+        map: 'range',
+        isSortable: false,
+        formatFunction: function (range) {
+          return range.start === range.end ? range.start : range.start + '-' + range.end;
+        }
+      },
+      {label: 'Details',
+        map: 'schedulingDetail',
+        isSortable: false,
+        cellTemplateUrl: '/schedulingDetail.html'
+      }
+    ];
+
+    $scope.taskSummaryTableConfig = summaryTableConfig;
+
+    var showSummary = 'Show Summary';
+    var hideSummary = 'Hide Summary';
+    $scope.summaryButtonText = showSummary;
+    $scope.showSummary = false;
+
+    $scope.toggleSummaryVisibility = function () {
+      $scope.showSummary = !$scope.showSummary;
+      $scope.summaryButtonText = $scope.showSummary ? hideSummary : showSummary;
+    };
+
+    var taskTableConfig = {
+      isGlobalSearchActivated: false,
+      isPaginationEnabled: true,
+      itemsByPage: 50,
+      maxSize: 8,
+      selectionMode: 'single'
+    };
+
+    $scope.activeTasksTableConfig = taskTableConfig;
+    $scope.completedTasksTableConfig = taskTableConfig;
+
+    var taskColumns = [
+      {label: 'Instance', map: 'instanceId'},
+      {label: 'Status', map: 'status', cellTemplateUrl: '/taskStatus.html'},
+      {label: 'Host', map: 'host', cellTemplateUrl: '/taskSandbox.html'}
+    ];
+
+    var completedTaskColumns = addColumn(2,
+      taskColumns,
+      {label: 'Running duration',
+        map: 'duration',
+        formatFunction: function (duration) {
+          return moment.duration(duration).humanize();
+        }
+      });
+
+    var taskIdColumn = {label: 'Task ID', map: 'taskId', cellTemplateUrl: '/taskLink.html'};
+
+    $scope.activeTasksTableColumns = taskColumns;
+
+    $scope.completedTasksTableColumns = completedTaskColumns;
+
+    function addColumn(idxPosition, currentColumns, newColumn) {
+      return _.union(
+        _.first(currentColumns, idxPosition),
+        [newColumn],
+        _.last(currentColumns, currentColumns.length - idxPosition));
+    }
+
+    $scope.showTaskInfoLink = false;
+
+    $scope.toggleTaskInfoLinkVisibility = function () {
+      $scope.showTaskInfoLink = !$scope.showTaskInfoLink;
+
+      $scope.activeTasksTableColumns = $scope.showTaskInfoLink
+        ? addColumn(2, taskColumns, taskIdColumn)
+        : taskColumns;
+
+      $scope.completedTasksTableColumns = $scope.showTaskInfoLink
+        ? addColumn(3, completedTaskColumns, taskIdColumn) :
+        completedTaskColumns;
+    };
+
+    $scope.jobDashboardUrl = '';
+
+    $scope.completedTasks = [];
+
+    $scope.activeTasks = getTasksForJob($scope.role, $scope.environment, $scope.job);
+
+    function getTasksForJob(role, environment, job) {
+      var response = auroraClient.getTasks(role, environment, job);
+
+      if (response.error) {
+        $scope.error = 'Error fetching tasks: ' + response.error;
+        return [];
+      }
+
+      $scope.jobDashboardUrl = getJobDashboardUrl(response.statsUrlPrefix);
+
+      $scope.taskSummary = taskUtil.summarizeActiveTaskConfigs(response.tasks);
+
+      var tasks = _.map(response.tasks, function (task) {
+        return summarizeTask(task);
+      });
+
+      var activeTaskPredicate = function (task) {
+        return task.isActive;
+      };
+
+      $scope.completedTasks = _.chain(tasks)
+        .reject(activeTaskPredicate)
+        .sortBy(function (task) {
+          return -task.latestActivity; //sort in descending order
+        })
+        .value();
+
+      return _.chain(tasks)
+        .filter(activeTaskPredicate)
+        .sortBy(function (task) {
+          return task.instanceId;
+        })
+        .value();
+    }
+
+    function summarizeTask(task) {
+      var isActive = taskUtil.isActiveTask(task);
+      var sortedTaskEvents = _.sortBy(task.taskEvents, function (taskEvent) {
+        return taskEvent.timestamp;
+      });
+
+      // Since all task sandboxes are eventually garbage collected SANDBOX_DELETED doesn't indicate
+      // the state of the task, so use the previous task event to determine task status.
+      var latestTaskEvent = task.status === ScheduleStatus.SANDBOX_DELETED
+        ? _.chain(sortedTaskEvents).last(2).first().value()
+        : _.last(sortedTaskEvents);
+
+      return {
+        instanceId: task.assignedTask.instanceId,
+        status: _.invert(ScheduleStatus)[latestTaskEvent.status],
+        statusMessage: latestTaskEvent.message,
+        host: task.assignedTask.slaveHost || '',
+        latestActivity: _.isEmpty(sortedTaskEvents) ? 0 : latestTaskEvent.timestamp,
+        duration: getDuration(sortedTaskEvents, isActive),
+        isActive: isActive,
+        taskId: task.assignedTask.taskId,
+        taskEvents: summarizeTaskEvents(sortedTaskEvents),
+        showDetails: false,
+        sandboxExists: task.status !== ScheduleStatus.SANDBOX_DELETED
+      };
+    }
+
+    function getDuration(sortedTaskEvents, isActive) {
+      if (!isActive || _.isEmpty(sortedTaskEvents)) {
+        return 0;
+      }
+
+      var runningTaskEvent = _.find(sortedTaskEvents, function (taskEvent) {
+        return taskEvent.status === ScheduleStatus.RUNNING;
+      });
+
+      return runningTaskEvent ? _.last(sortedTaskEvents).timestamp - runningTaskEvent.timestamp : 0;
+    }
+
+    function summarizeTaskEvents(taskEvents) {
+      return _.map(taskEvents, function (taskEvent) {
+        return {
+          date: moment(taskEvent.timestamp).format('MM/DD h:mm:ss'),
+          status: _.invert(ScheduleStatus)[taskEvent.status],
+          message: taskEvent.message
+        };
+      });
+    }
+
+    function getJobDashboardUrl(statsUrlPrefix) {
+      return _.isEmpty(statsUrlPrefix)
+        ? ''
+        : statsUrlPrefix + $scope.role + '.' + $scope.environment + '.' + $scope.job;
+    }
+  }
+);

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js
index d2b2017..5d6f2b6 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/directives.js
@@ -3,6 +3,68 @@
 auroraUI.directive('roleLink', function () {
   return {
     restrict: 'C',
-    template: "<a ng-href='/scheduler/{{formatedValue}}'>{{formatedValue}}</a>"
+    template: '<a ng-href="/scheduler/{{formatedValue}}">{{formatedValue}}</a>'
   };
-});
\ No newline at end of file
+});
+
+auroraUI.directive('roleEnvLink', function () {
+  return {
+    restrict: 'C',
+    template: '<a ng-href="/scheduler/{{dataRow.role}}/{{formatedValue}}">{{formatedValue}}</a>'
+  };
+});
+
+auroraUI.directive('jobLink', function () {
+  return {
+    restrict: 'C',
+    template: '<a ng-href="/scheduler/{{dataRow.role}}/{{dataRow.environment}}/{{formatedValue}}">'
+      + '{{formatedValue}}</a>'
+  };
+});
+
+auroraUI.directive('breadcrumb', function () {
+  return {
+    restrict: 'E',
+    templateUrl: '/breadcrumb.html'
+  };
+});
+
+auroraUI.directive('error', function () {
+  return {
+    restrict: 'E',
+    templateUrl: '/error.html'
+  };
+});
+
+auroraUI.directive('taskSandboxLink', function () {
+  return {
+    restrict: 'E',
+    templateUrl: '/taskSandbox.html'
+  };
+});
+
+auroraUI.directive('taskStatus', function () {
+  return {
+    restrict: 'E',
+    replace: true,
+    link: function (scope, element, attrs, ctrl) {
+      element.on('click', function (e) {
+        scope.showDetails = !scope.showDetails;
+      });
+    }
+  };
+});
+
+auroraUI.directive('taskLink', function () {
+  return {
+    restrict: 'C',
+    template: '<a class="span4" ng-href="/structdump/task/{{formatedValue}}" target="_self">' +
+      '{{formatedValue}}</a>'
+  };
+});
+
+auroraUI.directive('schedulingDetail', function () {
+  return {
+    restrict: 'C'
+  };
+})

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js
new file mode 100644
index 0000000..3db34ec
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/filters.js
@@ -0,0 +1,54 @@
+'use strict';
+
+auroraUI.filter('scheduleStatusTooltip', function () {
+  var STATES = {
+    PENDING: 'The scheduler is searching for a machine that satisfies the resources and '
+      + 'constraints for this task.',
+
+    THROTTLED: 'The task will be rescheduled, but is being throttled for restarting too '
+      + 'frequently.',
+
+    ASSIGNED: 'The scheduler has selected a machine to run the task and is instructing the '
+      + 'slave to launch it.',
+
+    STARTING: 'The executor is preparing to launch the task.',
+    RUNNING: 'The user process(es) are running.',
+    FAILED: 'The task ran, but did not exit indicating success.',
+    FINISHED: 'The task ran and exited successfully.',
+    KILLED: 'A user or cron invocation terminated the task.',
+    PREEMPTING: 'This task is being killed to make resources available for a production task.',
+    KILLING: 'A user request or cron invocation has requested the task be killed.',
+    LOST: 'The task cannot be accounted for, usually a result of slave process or machine '
+      + 'failure.'
+  };
+
+  return function (value) {
+    return STATES[value] ? STATES[value] : value;
+  };
+});
+
+auroraUI.filter('scaleMb', function () {
+  var SCALE = ['MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
+
+  return function (sizeInMb) {
+    var size = sizeInMb;
+    var unit = 0;
+    while (size >= 1024 && unit < SCALE.length) {
+      size = size / 1024;
+      unit++;
+    }
+    return size.toFixed(2).toString() + ' ' + SCALE[unit];
+  };
+});
+
+auroraUI.filter('toCores', function () {
+  return  function (count) {
+    return count + ' cores';
+  }
+});
+
+auroraUI.filter('toElapsedTime', function () {
+  return function (timestamp) {
+    return moment.duration(moment().valueOf() - timestamp).humanize();
+  }
+});

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js
index 8681bbe..e04c8c6 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/js/services.js
@@ -2,38 +2,199 @@
 
 auroraUI.factory(
   'auroraClient',
+  ['$window',
+    function ($window) {
+      var auroraClient = {
+        // Each of the functions below wrap an API call on the scheduler.
+        getRoleSummary: function () {
+          var response = auroraClient.getSchedulerClient().getRoleSummary();
+          var result = auroraClient.processResponse(response);
+          result.summaries = response.result !== null
+            ? response.result.roleSummaryResult.summaries : [];
+          return result;
+        },
+
+        getJobSummary: function (role) {
+          var response = auroraClient.getSchedulerClient().getJobSummary(role);
+          var result = auroraClient.processResponse(response);
+          result.jobs = response.result !== null ? response.result.jobSummaryResult.summaries : [];
+          return result;
+        },
+
+        getQuota: function (role) {
+          var response = auroraClient.getSchedulerClient().getQuota(role);
+          var result = auroraClient.processResponse(response);
+          result.quota = response.result !== null ? response.result.getQuotaResult : [];
+          return result;
+        },
+
+        getTasks: function (role, environment, jobName) {
+          var id = new Identity();
+          id.role = role;
+          var taskQuery = new TaskQuery();
+          taskQuery.identity = id;
+          taskQuery.environment = environment;
+          taskQuery.jobName = jobName;
+          var response = auroraClient.getSchedulerClient().getTasksStatus(taskQuery);
+          var result = auroraClient.processResponse(response);
+          result.tasks = response.result !== null ? response.result.scheduleStatusResult.tasks : [];
+          return result;
+        },
+
+        // Utility functions
+        // TODO(Suman Karumuri): Make schedulerClient a service
+        schedulerClient: null,
+
+        getSchedulerClient: function () {
+          if (!auroraClient.schedulerClient) {
+            var transport = new Thrift.Transport("/api/");
+            var protocol = new Thrift.Protocol(transport);
+            auroraClient.schedulerClient = new ReadOnlySchedulerClient(protocol);
+            return auroraClient.schedulerClient;
+          } else {
+            return auroraClient.schedulerClient;
+          }
+        },
+
+        processResponse: function (response) {
+          auroraClient.setPageTitle(response.serverInfo);
+          return {
+            error: response.responseCode !== 1
+              ? (response.message || 'No error message returned by the scheduler')
+              : '',
+            statsUrlPrefix: response.serverInfo && response.serverInfo.statsUrlPrefix
+              ? response.serverInfo.statsUrlPrefix
+              : ''
+          };
+        },
+
+        getPageTitle: function (info) {
+          var title = "Aurora UI";
+          return _.isNull(info) || info.error || typeof info.clusterName === "undefined"
+            ? title
+            : info.clusterName + " " + title;
+        },
+
+        setPageTitle: function (serverInfo) {
+          $window.document.title = auroraClient.getPageTitle(serverInfo);
+        }
+      };
+      return auroraClient;
+    }
+  ]);
+
+auroraUI.factory(
+  'taskUtil',
   function () {
-    return {
-      getRoleSummary: function () {
-        var response = this.getSchedulerClient().getRoleSummary();
+    var taskUtil = {
+      // Given a list of tasks, group tasks with identical task configs and belonging to contiguous
+      // instance ids together.
+      summarizeActiveTaskConfigs: function (tasks) {
+        return _.chain(tasks)
+          .filter(taskUtil.isActiveTask)
+          .map(function (task) {
+            return {
+              instanceId: task.assignedTask.instanceId,
+              schedulingDetail: taskUtil.configToDetails(task.assignedTask.task)
+            };
+          })
+          .groupBy(function (task) {
+            return JSON.stringify(task.schedulingDetail);
+          })
+          .map(function (tasks) {
+            // Given a list of tasks with the same task config, group the tasks into ranges where
+            // each range consists of consecutive task ids along with their task config.
+            var schedulingDetail = _.first(tasks).schedulingDetail;
+            var ranges = taskUtil.toRanges(_.pluck(tasks, 'instanceId'));
+            return _.map(ranges, function (range) {
+              return {
+                range: range,
+                schedulingDetail: schedulingDetail
+              }
+            });
+          })
+          .flatten(true)
+          .sortBy(function (scheduleDetail) {
+            return scheduleDetail.range.start;
+          })
+          .value();
+      },
+
+      configToDetails: function (task) {
+        var constraints = _.chain(task.constraints)
+          .sortBy(function (constraint) {
+            return constraint.name;
+          })
+          .map(taskUtil.formatConstraint)
+          .value()
+          .join(', ');
+
+        var metadata = _.chain(task.metadata)
+          .sortBy(function (metadata) {
+            return metadata.key;
+          })
+          .map(function (metadata) {
+            return metadata.key + ':' + metadata.value;
+          })
+          .value()
+          .join(', ');
+
         return {
-          error: response.responseCode !== 1,
-          errorMsg: response.message,
-          summaries: response.result !== null ? response.result.roleSummaryResult.summaries : [],
-          pageTitle: this.getPageTitle(response.serverInfo)
+          numCpus: task.numCpus,
+          ramMb: task.ramMb,
+          diskMb: task.diskMb,
+          isService: task.isService,
+          production: task.production,
+          contact: task.contactEmail || '',
+          ports: _.sortBy(task.requestedPorts).join(', '),
+          constraints: constraints,
+          metadata: metadata
+        };
+      },
+
+      // Given a list of instanceIds, group them into contiguous ranges.
+      toRanges: function (instanceIds) {
+        instanceIds = _.sortBy(instanceIds);
+        var ranges = [];
+        var i = 0;
+        var start = instanceIds[i];
+        var end = instanceIds[i];
+        while (i < instanceIds.length) {
+          if ((i + 1 === instanceIds.length) || (instanceIds[i] + 1 !== instanceIds[i + 1])) {
+            ranges.push({start: start, end: instanceIds[i]});
+            i++;
+            start = instanceIds[i];
+          } else {
+            i++;
+          }
         }
+        return ranges;
       },
 
-      // TODO(Suman Karumuri): Make schedulerClient a service
-      schedulerClient: null,
+      // A function that converts a task constraint into a string
+      formatConstraint: function (constraint) {
+        var taskConstraint = constraint.constraint;
+
+        var valueConstraintStr = '';
+        var valueConstraint = taskConstraint.value;
+        if (valueConstraint && valueConstraint.values && _.isArray(valueConstraint.values)) {
+          var values = valueConstraint.values.join(',');
+          valueConstraintStr = valueConstraint.negated ? "not " + values : values;
+        }
+
+        var limitConstraintStr = taskConstraint.limit ? JSON.stringify(taskConstraint.limit) : '';
 
-      getSchedulerClient: function () {
-        if (!this.schedulerClient) {
-          var transport = new Thrift.Transport("/api");
-          var protocol = new Thrift.Protocol(transport);
-          this.schedulerClient = new ReadOnlySchedulerClient(protocol);
-          return this.schedulerClient;
+        if (_.isEmpty(limitConstraintStr) && _.isEmpty(valueConstraintStr)) {
+          return '';
         } else {
-          return this.schedulerClient;
+          return constraint.name + ':' +
+            (_.isEmpty(limitConstraintStr) ? valueConstraintStr : limitConstraintStr);
         }
       },
 
-      getPageTitle: function (info) {
-        var title = "Aurora UI";
-        return info.error || typeof info.clusterName === "undefined"
-                 ? title
-                 : info.clusterName + " " + title;
+      isActiveTask: function (task) {
+        return _.contains(ACTIVE_STATES, task.status);
       }
     };
-  }
-);
+    return taskUtil;
+  });

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html
new file mode 100644
index 0000000..ff631c8
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/role.html
@@ -0,0 +1,67 @@
+<div>
+  <div ng-show='error'>
+    <error/>
+  </div>
+
+  <div ng-hide='error'>
+    <div>
+      <div>
+        <breadcrumb/>
+      </div>
+
+      <div>
+       <div class='container-fluid'>
+          <h2 class='page-header text-center'>
+            Jobs for role <em>{{role}}</em>
+            <span ng-if='environment'> and environment <em>{{environment}}</em></span>
+          </h2>
+        </div>
+
+        <div class='container-fluid'>
+         <div ng-if='!environment'>
+            <button class='btn summaryButton' ng-click='toggleResourceVisibility()'>
+              {{resourceButtonText}}
+            </button>
+          </div>
+
+          <div ng-show='showResources && !environment'>
+            <div class='row'>
+              <div class='span8'>
+                <div ng-controller="QuotaController">
+                  <div ng-show='error'>
+                    <error/>
+                  </div>
+
+                  <div ng-hide='error'>
+                    <smart-table config='resourcesTableConfig'
+                                 columns='resourcesTableColumns'
+                                 rows='resources'
+                                 class='table table-striped table-hover table-condensed'>
+                    </smart-table>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class='container-fluid'>
+          <smart-table config='jobsTableConfig'
+                       columns='jobsTableColumns'
+                       rows='jobs'
+                       class='table table-striped table-hover table-bordered table-condensed'>
+          </smart-table>
+        </div>
+
+        <div class='container-fluid'>
+          <h2 class='text-center'>Cron jobs for role <em>{{role}}</em></h2>
+          <smart-table config='cronJobsTableConfig'
+                       columns='cronJobsTableColumns'
+                       rows='cronJobs'
+                       class='table table-striped table-hover table-bordered table-condensed'>
+          </smart-table>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html
new file mode 100644
index 0000000..1db7ad1
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleEnvLink.html
@@ -0,0 +1 @@
+<div class='role-env-link'></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html
index fc25526..63a1688 100644
--- a/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/roleLink.html
@@ -1 +1 @@
-<div class="role-link"></div>
\ No newline at end of file
+<div class='role-link'></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html
new file mode 100644
index 0000000..8fee019
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/schedulingDetail.html
@@ -0,0 +1,23 @@
+<div class='scheduling-detail'>
+  <ul class='unstyled'>
+    <li>resources:
+      cpu: {{dataRow.schedulingDetail.numCpus | toCores}},
+      ram: {{dataRow.schedulingDetail.ramMb | scaleMb}},
+      disk: {{dataRow.schedulingDetail.diskMb | scaleMb}}
+    </li>
+    <li ng-if='dataRow.schedulingDetail.constraints'>
+      constraints: {{dataRow.schedulingDetail.constraints}}
+    </li>
+    <li>production: {{dataRow.schedulingDetail.production}}</li>
+    <li>service: {{dataRow.schedulingDetail.isService}}</li>
+    <li ng-if='dataRow.schedulingDetail.ports'>
+      ports: {{dataRow.schedulingDetail.ports}}
+    </li>
+    <li ng-if='dataRow.schedulingDetail.metadata'>
+      metadata: {{dataRow.schedulingDetail.metadata}}
+    </li>
+    <li ng-if='dataRow.schedulingDetail.contact'>
+      contact: {{dataRow.schedulingDetail.contact}}
+    </li>
+  </ul>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html
new file mode 100644
index 0000000..3779b7e
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskLink.html
@@ -0,0 +1 @@
+<div class='task-link'></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html
new file mode 100644
index 0000000..65fb8f0
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskSandbox.html
@@ -0,0 +1,13 @@
+<div class='row'>
+  <div class='span2'>
+    <span ng-if='dataRow.sandboxExists'>
+      <a ng-href='http://{{formatedValue}}:1338/task/{{dataRow.taskId}}'>
+        {{formatedValue}}
+      </a>
+    </span>
+
+    <span ng-if='!dataRow.sandboxExists'>
+      {{formatedValue}}
+    </span>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html
new file mode 100644
index 0000000..2cab236
--- /dev/null
+++ b/src/main/resources/org/apache/aurora/scheduler/http/ui/taskStatus.html
@@ -0,0 +1,25 @@
+<task-status>
+  <div class='row'>
+    <div class='span4'>
+    <span title='{{formatedValue | scheduleStatusTooltip}}'>
+      <span ng-if='!showDetails'><i class='icon-plus'></i></span>
+      <span ng-if='showDetails'><i class='icon-minus'></i></span>
+      <span>
+        {{dataRow.latestActivity | toElapsedTime}} ago -
+        <span class='schedulingStatus'>{{formatedValue}}</span>
+        <span ng-if='statusMessage'>: {{statusMessage}}</span>
+      </span>
+    </span>
+
+      <ul ng-if='showDetails'>
+        <li ng-repeat='taskEvent in dataRow.taskEvents'>
+          {{taskEvent.date}} local -
+        <span title='{{taskEvent.status | scheduleStatusTooltip}}'>
+          <span class='schedulingStatus'>{{taskEvent.status}}</span>
+        </span>
+          <span ng-if='taskEvent.message'> - {{taskEvent.message}}</span>
+        </li>
+      </ul>
+    </div>
+  </div>
+</task-status>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/main/thrift/org/apache/aurora/gen/api.thrift
----------------------------------------------------------------------
diff --git a/src/main/thrift/org/apache/aurora/gen/api.thrift b/src/main/thrift/org/apache/aurora/gen/api.thrift
index c37928f..32596d3 100644
--- a/src/main/thrift/org/apache/aurora/gen/api.thrift
+++ b/src/main/thrift/org/apache/aurora/gen/api.thrift
@@ -436,6 +436,7 @@ struct JobSummaryResult {
 struct ServerInfo {
   1: string clusterName
   2: i32 thriftAPIVersion
+  3: string statsUrlPrefix  // A url prefix for job container stats.
 }
 
 union Result {

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
index db63e79..ddbb025 100644
--- a/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/app/SchedulerIT.java
@@ -116,6 +116,7 @@ public class SchedulerIT extends BaseZooKeeperTest {
 
   private static final String CLUSTER_NAME = "integration_test_cluster";
   private static final String SERVERSET_PATH = "/fake/service/path";
+  private static final String STATS_URL_PREFIX = "fake_url";
   private static final String FRAMEWORK_ID = "integration_test_framework_id";
 
   private ExecutorService executor = Executors.newCachedThreadPool(
@@ -200,7 +201,8 @@ public class SchedulerIT extends BaseZooKeeperTest {
         .withCredentials(ZooKeeperClient.digestCredentials("mesos", "mesos"));
     injector = Guice.createInjector(
         ImmutableList.<Module>builder()
-            .addAll(SchedulerMain.getModules(CLUSTER_NAME, SERVERSET_PATH, zkClientConfig))
+            .addAll(SchedulerMain.getModules(
+                CLUSTER_NAME, SERVERSET_PATH, zkClientConfig, STATS_URL_PREFIX))
             .add(new LifecycleModule())
             .add(new AppLauncherModule())
             .add(new ZooKeeperClientModule(zkClientConfig))

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
index fae2de1..f530c53 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
@@ -144,7 +144,10 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
   private static final IResourceAggregate CONSUMED =
       IResourceAggregate.build(new ResourceAggregate(0.0, 0, 0));
   private static final ServerInfo SERVER_INFO =
-      new ServerInfo().setClusterName("test").setThriftAPIVersion(THRIFT_API_VERSION);
+      new ServerInfo()
+          .setClusterName("test")
+          .setThriftAPIVersion(THRIFT_API_VERSION)
+          .setStatsUrlPrefix("fake_url");
   private static final APIVersion API_VERSION = new APIVersion().setMajor(THRIFT_API_VERSION);
   private static final String CRON_SCHEDULE = "0 * * * *";
 
@@ -318,7 +321,7 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
   }
 
   private static IScheduledTask buildScheduledTask(String jobName) {
-     return IScheduledTask.build(new ScheduledTask()
+    return IScheduledTask.build(new ScheduledTask()
         .setAssignedTask(new AssignedTask()
             .setTask(new TaskConfig()
                 .setOwner(ROLE_IDENTITY)

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/88d25ed9/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java
index dd991fb..5d8d87b 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/aop/ServerInfoInterceptorTest.java
@@ -45,8 +45,11 @@ public class ServerInfoInterceptorTest extends EasyMockTest {
   private AuroraAdmin.Iface realThrift;
   private AuroraAdmin.Iface decoratedThrift;
 
-  private static final IServerInfo SERVER_INFO =
-      IServerInfo.build(new ServerInfo().setClusterName("test").setThriftAPIVersion(1));
+  private static final IServerInfo SERVER_INFO = IServerInfo.build(
+      new ServerInfo()
+          .setClusterName("test")
+          .setThriftAPIVersion(1)
+          .setStatsUrlPrefix("fake_url"));
 
   private ServerInfoInterceptor interceptor;
 
@@ -86,9 +89,9 @@ public class ServerInfoInterceptorTest extends EasyMockTest {
         new ServerInfo().setClusterName("FAKECLUSTER").setThriftAPIVersion(100000);
 
     Response response = okResponse(
-            Result.getJobsResult(
-                new GetJobsResult().setConfigs(ImmutableSet.<JobConfiguration>of())))
-            .setServerInfo(previousServerInfo);
+        Result.getJobsResult(
+            new GetJobsResult().setConfigs(ImmutableSet.<JobConfiguration>of())))
+        .setServerInfo(previousServerInfo);
 
     expect(realThrift.getJobs(ROLE)).andReturn(response);
 


Mime
View raw message