Restructured Web UI.

Does not change the logic of the Web UI, only updates the structure
to have fewer nested directories and a more logical grouping of files.

Review: https://reviews.apache.org/r/66553/


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

Branch: refs/heads/master
Commit: c76859170c9f7fe36d8479ad6c36f0f69b2f2f5d
Parents: 28998f7
Author: Armand Grillet <agril...@mesosphere.io>
Authored: Wed Apr 11 15:36:23 2018 -0700
Committer: Benjamin Mahler <bmah...@apache.org>
Committed: Wed Apr 11 15:37:02 2018 -0700

----------------------------------------------------------------------
 src/Makefile.am                                 |   78 +-
 src/master/master.cpp                           |    5 +-
 src/webui/app/agents/agent-browse.html          |   87 ++
 src/webui/app/agents/agent-executor.html        |  204 +++
 src/webui/app/agents/agent-framework.html       |  164 +++
 src/webui/app/agents/agent.html                 |  293 +++++
 src/webui/app/agents/agents.html                |   56 +
 src/webui/app/app.js                            |  371 ++++++
 src/webui/app/controllers.js                    | 1179 ++++++++++++++++++
 src/webui/app/frameworks/framework.html         |  261 ++++
 src/webui/app/frameworks/frameworks.html        |  179 +++
 src/webui/app/home.html                         |  334 +++++
 src/webui/app/maintenance/maintenance.html      |   36 +
 src/webui/app/offers/offers.html                |   47 +
 src/webui/app/roles/roles.html                  |   60 +
 src/webui/app/services.js                       |  309 +++++
 src/webui/app/shared/pagination.html            |    6 +
 src/webui/app/shared/pailer.html                |   80 ++
 src/webui/app/shared/table-header.html          |   20 +
 src/webui/app/shared/timestamp.html             |    5 +
 src/webui/assets/css/bootstrap-3.3.6.min.css    |    6 +
 .../assets/css/bootstrap-table-1.11.1.min.css   |    1 +
 src/webui/assets/css/mesos.css                  |  232 ++++
 .../fonts/glyphicons-halflings-regular.eot      |  Bin 0 -> 20127 bytes
 .../fonts/glyphicons-halflings-regular.svg      |  288 +++++
 .../fonts/glyphicons-halflings-regular.ttf      |  Bin 0 -> 45404 bytes
 .../fonts/glyphicons-halflings-regular.woff     |  Bin 0 -> 23424 bytes
 .../fonts/glyphicons-halflings-regular.woff2    |  Bin 0 -> 18028 bytes
 src/webui/assets/ico/favicon.ico                |  Bin 0 -> 5430 bytes
 src/webui/assets/img/loading.gif                |  Bin 0 -> 4710 bytes
 src/webui/assets/img/mesos-logo.png             |  Bin 0 -> 7888 bytes
 src/webui/assets/libs/angular-1.2.32.min.js     |  218 ++++
 .../assets/libs/angular-route-1.2.32.min.js     |   14 +
 .../assets/libs/bootstrap-table-1.11.1.min.js   |    8 +
 src/webui/assets/libs/clipboard-1.5.16.min.js   |    7 +
 src/webui/assets/libs/jquery-3.2.1.min.js       |    4 +
 src/webui/assets/libs/jquery.pailer.js          |  350 ++++++
 src/webui/assets/libs/relative-date.js          |   48 +
 .../assets/libs/ui-bootstrap-tpls-0.9.0.min.js  |    2 +
 src/webui/assets/libs/underscore-1.4.3.min.js   |    1 +
 src/webui/index.html                            |  128 ++
 src/webui/master/static/agent.html              |  293 -----
 src/webui/master/static/agent_executor.html     |  204 ---
 src/webui/master/static/agent_framework.html    |  164 ---
 src/webui/master/static/agents.html             |   56 -
 src/webui/master/static/browse.html             |   87 --
 .../master/static/css/bootstrap-3.3.6.min.css   |    6 -
 .../static/css/bootstrap-table-1.11.1.min.css   |    1 -
 src/webui/master/static/css/mesos.css           |  232 ----
 .../master/static/directives/pagination.html    |    6 -
 .../master/static/directives/tableHeader.html   |   20 -
 .../master/static/directives/timestamp.html     |    5 -
 .../fonts/glyphicons-halflings-regular.eot      |  Bin 20127 -> 0 bytes
 .../fonts/glyphicons-halflings-regular.svg      |  288 -----
 .../fonts/glyphicons-halflings-regular.ttf      |  Bin 45404 -> 0 bytes
 .../fonts/glyphicons-halflings-regular.woff     |  Bin 23424 -> 0 bytes
 .../fonts/glyphicons-halflings-regular.woff2    |  Bin 18028 -> 0 bytes
 src/webui/master/static/framework.html          |  261 ----
 src/webui/master/static/frameworks.html         |  179 ---
 src/webui/master/static/home.html               |  334 -----
 src/webui/master/static/ico/favicon.ico         |  Bin 5430 -> 0 bytes
 src/webui/master/static/img/loading.gif         |  Bin 4710 -> 0 bytes
 src/webui/master/static/img/mesos_logo.png      |  Bin 7888 -> 0 bytes
 src/webui/master/static/index.html              |  128 --
 .../master/static/js/angular-1.2.32.min.js      |  218 ----
 .../static/js/angular-route-1.2.32.min.js       |   14 -
 src/webui/master/static/js/app.js               |  371 ------
 .../static/js/bootstrap-table-1.11.1.min.js     |    8 -
 .../master/static/js/clipboard-1.5.16.min.js    |    7 -
 src/webui/master/static/js/controllers.js       | 1179 ------------------
 src/webui/master/static/js/jquery-3.2.1.min.js  |    4 -
 src/webui/master/static/js/jquery.pailer.js     |  350 ------
 src/webui/master/static/js/relative-date.js     |   48 -
 src/webui/master/static/js/services.js          |  309 -----
 .../static/js/ui-bootstrap-tpls-0.9.0.min.js    |    2 -
 .../master/static/js/underscore-1.4.3.min.js    |    1 -
 src/webui/master/static/maintenance.html        |   36 -
 src/webui/master/static/offers.html             |   47 -
 src/webui/master/static/pailer.html             |   80 --
 src/webui/master/static/roles.html              |   60 -
 80 files changed, 5040 insertions(+), 5039 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f4b6d3..257ff0e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1778,60 +1778,60 @@ dist_bin_SCRIPTS +=                                     
                \
 # that 'datadir' (e.g., /usr/local/share) is for read-only "data" and
 # 'sysconfdir' (e.g., /usr/local/var) is for modifiable "data".
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/js/app.js                                                
\
-  webui/master/static/js/controllers.js                                        
\
-  webui/master/static/js/jquery.pailer.js                              \
-  webui/master/static/js/services.js
+  webui/app/app.js                                                     \
+  webui/app/controllers.js                                             \
+  webui/app/services.js
 
 # Need to distribute/install webui CSS.
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/css/bootstrap-3.3.6.min.css                      \
-  webui/master/static/css/bootstrap-table-1.11.1.min.css               \
-  webui/master/static/css/mesos.css
+  webui/assets/css/bootstrap-3.3.6.min.css                              \
+  webui/assets/css/bootstrap-table-1.11.1.min.css\
+  webui/assets/css/mesos.css
 
 # Need to distribute/install webui HTML.
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/agent.html                                       \
-  webui/master/static/agents.html                                      \
-  webui/master/static/agent_executor.html                              \
-  webui/master/static/agent_framework.html                             \
-  webui/master/static/browse.html                                      \
-  webui/master/static/framework.html                                   \
-  webui/master/static/frameworks.html                                  \
-  webui/master/static/home.html                                                
\
-  webui/master/static/index.html                                       \
-  webui/master/static/maintenance.html                                 \
-  webui/master/static/offers.html                                      \
-  webui/master/static/pailer.html                                      \
-  webui/master/static/roles.html                                       \
-  webui/master/static/directives/pagination.html                       \
-  webui/master/static/directives/tableHeader.html                      \
-  webui/master/static/directives/timestamp.html
+  webui/index.html                                                     \
+  webui/app/home.html                                                  \
+  webui/app/agents/agent.html                                          \
+  webui/app/agents/agent-browse.html                                   \
+  webui/app/agents/agent-executor.html                                 \
+  webui/app/agents/agent-framework.html                                        
\
+  webui/app/agents/agents.html                                         \
+  webui/app/frameworks/framework.html                                  \
+  webui/app/frameworks/frameworks.html                                 \
+  webui/app/maintenance/maintenance.html                               \
+  webui/app/offers/offers.html                                         \
+  webui/app/roles/roles.html                                           \
+  webui/app/shared/pagination.html                                     \
+  webui/app/shared/pailer.html                                         \
+  webui/app/shared/table-header.html                                   \
+  webui/app/shared/timestamp.html
 
 # Need to distribute/install webui images.
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/ico/favicon.ico                                  \
-  webui/master/static/img/loading.gif                                  \
-  webui/master/static/img/mesos_logo.png
+  webui/assets/ico/favicon.ico                                         \
+  webui/assets/img/loading.gif                                         \
+  webui/assets/img/mesos-logo.png
 
 # Need to distribute/install webui fonts.
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/fonts/glyphicons-halflings-regular.eot           \
-  webui/master/static/fonts/glyphicons-halflings-regular.svg           \
-  webui/master/static/fonts/glyphicons-halflings-regular.ttf           \
-  webui/master/static/fonts/glyphicons-halflings-regular.woff          \
-  webui/master/static/fonts/glyphicons-halflings-regular.woff2
+  webui/assets/fonts/glyphicons-halflings-regular.eot                  \
+  webui/assets/fonts/glyphicons-halflings-regular.svg                  \
+  webui/assets/fonts/glyphicons-halflings-regular.ttf                  \
+  webui/assets/fonts/glyphicons-halflings-regular.woff                 \
+  webui/assets/fonts/glyphicons-halflings-regular.woff2
 
 # Need to distribute/install third-party javascript.
 nobase_dist_pkgdata_DATA +=                                            \
-  webui/master/static/js/angular-1.2.32.min.js                         \
-  webui/master/static/js/angular-route-1.2.32.min.js                   \
-  webui/master/static/js/bootstrap-table-1.11.1.min.js                 \
-  webui/master/static/js/clipboard-1.5.16.min.js                       \
-  webui/master/static/js/jquery-3.2.1.min.js                           \
-  webui/master/static/js/relative-date.js                              \
-  webui/master/static/js/ui-bootstrap-tpls-0.9.0.min.js                        
\
-  webui/master/static/js/underscore-1.4.3.min.js
+  webui/assets/libs/angular-1.2.32.min.js                              \
+  webui/assets/libs/angular-route-1.2.32.min.js                                
\
+  webui/assets/libs/bootstrap-table-1.11.1.min.js                      \
+  webui/assets/libs/clipboard-1.5.16.min.js                            \
+  webui/assets/libs/jquery-3.2.1.min.js                                        
\
+  webui/assets/libs/jquery.pailer.js                                   \
+  webui/assets/libs/relative-date.js                                   \
+  webui/assets/libs/ui-bootstrap-tpls-0.9.0.min.js                     \
+  webui/assets/libs/underscore-1.4.3.min.js
 
 # And the deploy related stuff.
 nodist_sbin_SCRIPTS +=                                                 \

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/master/master.cpp
----------------------------------------------------------------------
diff --git a/src/master/master.cpp b/src/master/master.cpp
index f7da675..767ad8c 100644
--- a/src/master/master.cpp
+++ b/src/master/master.cpp
@@ -1092,8 +1092,9 @@ void Master::initialize()
   // build directory before 'make install') or determined at build
   // time via the preprocessor macro '-DMESOS_WEBUI_DIR' set in the
   // Makefile.
-  provide("", path::join(flags.webui_dir, "master/static/index.html"));
-  provide("static", path::join(flags.webui_dir, "master/static"));
+  provide("", path::join(flags.webui_dir, "index.html"));
+  provide("app", path::join(flags.webui_dir, "app"));
+  provide("assets", path::join(flags.webui_dir, "assets"));
 
   const PID<Master> masterPid = self();
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/agents/agent-browse.html
----------------------------------------------------------------------
diff --git a/src/webui/app/agents/agent-browse.html 
b/src/webui/app/agents/agent-browse.html
new file mode 100644
index 0000000..b7ac395
--- /dev/null
+++ b/src/webui/app/agents/agent-browse.html
@@ -0,0 +1,87 @@
+<ol class="breadcrumb">
+  <li>
+    <a class="badge badge-type" href="#">Master</a>
+  </li>
+  <li>
+    <a class="badge badge-type" href="#/agents/{{agent_id}}" 
title="{{agent_id}}">
+      Agent</a>
+  </li>
+  <li class="active">
+    Browse
+  </li>
+</ol>
+
+<ol class="breadcrumb">
+  <!-- We want to ensure that if the user highlights the path breadcrumb,
+     and copies it, they will receive a /path/without/spaces that they
+     can then paste into a terminal, or elsewhere. In order to do this,
+     we have to ensure there is no whitespace within the <a> tag contents.
+     Also, we have to inject a hidden '/' character because the slashes
+     in the breadcrumb are not copied.
+  -->
+  <li ng-repeat="dir in path.split('/')"><a
+      href="#/agents/{{agent_id}}/browse?path={{
+            encodeURIComponent(path.split('/').slice(0, $index + 1).join('/'))
+            }}">{{dir}}</a><span class="hidden-text">/</span></li>
+</ol>
+
+<div class="alert alert-error hidden" id="alert">
+  <button class="close" data-dismiss="alert">×</button>
+  <strong>{{alert_message}}</strong>
+</div>
+
+<div class="row" id="listing">
+  <div class="col-md-9">
+    <div class="well">
+      <div data-ng-show="listing.length == 0">
+        No files in this directory.
+      </div>
+      <table class="table table-condensed" data-ng-show="listing.length > 0">
+        <thead>
+          <tr>
+            <th>mode</th>
+            <th class="text-right">nlink</th>
+            <th>uid</th>
+            <th>gid</th>
+            <th class="text-right">size</th>
+            <th class="text-right">mtime</th>
+            <th></th>
+            <th></th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr ng-repeat="file in listing | orderBy:['-mode', 'path']">
+            <td>{{file.mode}}</td>
+            <td class="text-right">{{file.nlink}}</td>
+            <td>{{file.uid}}</td>
+            <td>{{file.gid}}</td>
+            <td class="text-right">{{file.size | dataSize}}</td>
+            <td class="text-right">{{file.mtime * 1000 | unixDate}}</td>
+            <td>
+              <span data-ng-show="file.mode[0] == 'd'">
+                <i class="glyphicon glyphicon-folder-close"></i>
+                <a 
href="#/agents/{{agent_id}}/browse?path={{encodeURIComponent(file.path)}}">
+                  {{basename(file.path)}}
+                </a>
+              </span>
+              <span data-ng-show="file.mode[0] != 'd'">
+                <i class="glyphicon glyphicon-file" style="visibility: 
hidden;"></i>
+                <a href="" ng-click="pail($event, 
encodeURIComponent(file.path))">
+                  {{basename(file.path)}}
+                </a>
+              </span>
+            </td>
+            <td>
+              <a data-ng-show="file.mode[0] != 'd'"
+                 
href="{{agent_url_prefix}}/files/download?path={{encodeURIComponent(file.path)}}">
+                <button class="btn btn-xs btn-default" type="button">
+                  Download
+                </button>
+              </a>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/agents/agent-executor.html
----------------------------------------------------------------------
diff --git a/src/webui/app/agents/agent-executor.html 
b/src/webui/app/agents/agent-executor.html
new file mode 100644
index 0000000..7ec56c3
--- /dev/null
+++ b/src/webui/app/agents/agent-executor.html
@@ -0,0 +1,204 @@
+<ol class="breadcrumb">
+  <li>
+    <a class="badge badge-type" href="#">Master</a>
+  </li>
+  <li>
+    <a class="badge badge-type" href="#/agents/{{agent_id}}" 
title="{{agent_id}}">
+      Agent</a>
+  </li>
+  <li>
+    <a class="badge badge-type" 
href="#/agents/{{agent_id}}/frameworks/{{framework_id}}" 
title="{{framework_id}}">
+      Framework</a>
+  </li>
+  <li class="active">
+    <span class="badge badge-type">Executor</span>
+    {{executor_id}}
+  </li>
+</ol>
+
+<div class="alert alert-error hidden" id="alert">
+  <button class="close" data-dismiss="alert">×</button>
+  <strong>{{alert_message}}</strong>
+</div>
+
+<div class="row" id="agent">
+  <div class="col-md-3">
+    <div class="well">
+      <dl class="inline clearfix">
+        <dt>Executor Name:</dt>
+        <dd>{{executor.name}}</dd>
+        <dt>Executor Source:</dt>
+        <dd>{{executor.source}}</dd>
+        <dt>Executor Role:</dt>
+        <dd>{{executor.role}}</dd>
+      </dl>
+
+      <dl class="inline clearfix">
+        <dt>Cluster:</dt>
+        <dd>
+          <span ng-show="clusterNamed">{{cluster}}</span>
+          <span ng-show="!clusterNamed">
+            (Unnamed)
+            <i class="icon-info-sign"
+              tooltip="To name this cluster, set the --cluster flag when 
starting the master."
+              tooltip-placement="right"></i>
+          </span>
+        </dd>
+        <dt>Master:</dt>
+        <dd>{{state.master_hostname}}</dd>
+        <dt>Agent:</dt>
+        <dd>{{state.hostname}}</dd>
+      </dl>
+
+      <dl class="inline clearfix">
+        <dt>Active Tasks:</dt>
+        <dd>{{executor.tasks.length | number}}</dd>
+      </dl>
+
+      <h4>Resources</h4>
+      <table class="table table-condensed">
+        <thead>
+          <tr>
+            <td></td>
+            <td class="text-right">Used</td>
+            <td class="text-right">Allocated</td>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>CPUs</td>
+            <td class="text-right">
+              
{{monitor.frameworks[framework.id].executors[executor.id].statistics.cpus_total_usage
 | number}}
+            </td>
+            <td class="text-right">{{executor.resources.cpus | number}}</td>
+          </tr>
+          <tr>
+            <td>GPUs</td>
+            <td class="text-right">
+              N/A
+            </td>
+            <td class="text-right">{{executor.resources.gpus | number}}</td>
+          </tr>
+          <tr>
+            <td>Mem</td>
+            <td class="text-right">
+              
{{monitor.frameworks[framework.id].executors[executor.id].statistics.mem_rss_bytes
 | dataSize}}
+            </td>
+            <td class="text-right">
+              {{executor.resources.mem * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+          <tr>
+            <td>Disk</td>
+            <td class="text-right">
+              
{{monitor.frameworks[framework.id].executors[executor.id].statistics.disk_used_bytes
 | dataSize}}</td>
+            <td class="text-right">
+              {{(executor.resources.disk || 0) * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+
+  <div class="col-md-9">
+    <table m-table table-content="executor.queued_tasks" title="Queued Tasks"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="name">Name</th>
+          <th data-key="role">Role</th>
+          <th data-key="resources.cpus">CPUs</th>
+          <th data-key="resources.gpus">GPUs</th>
+          <th data-key="resources.mem">Mem</th>
+          <th data-key="resources.disk">Disk</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="queued_task in $data">
+          <td>{{queued_task.id}}</td>
+          <td>{{queued_task.name}}</td>
+          <td>{{queued_task.role}}</td>
+          <td>{{queued_task.resources.cpus | number}}</td>
+          <td>{{queued_task.resources.gpus | number}}</td>
+          <td>{{queued_task.resources.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{queued_task.resources.disk * (1024 * 1024) | dataSize}}</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <table m-table table-content="executor.tasks" title="Tasks"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="name">Name</th>
+          <th data-key="role">Role</th>
+          <th data-key="state">State</th>
+          <th data-key="healthy">Health</th>
+          <th data-key="resources.cpus">CPUs (allocated)</th>
+          <th data-key="resources.gpus">GPUs (allocated)</th>
+          <th data-key="resources.mem">Mem (allocated)</th>
+          <th data-key="resources.disk">Disk (allocated)</th>
+          <th></th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="task in $data">
+          <td>{{task.id}}</td>
+          <td>{{task.name}}</td>
+          <td>{{task.role}}</td>
+          <td>{{task.state}}</td>
+          <td class="task-{{task.healthy | taskHealth}}">{{task.healthy | 
taskHealth}}</td>
+          <td>{{task.resources.cpus | number}}</td>
+          <td>{{task.resources.gpus | number}}</td>
+          <td>{{task.resources.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{task.resources.disk * (1024 * 1024) | dataSize}}</td>
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/browse?path=' +
+                       encodeURIComponent(task.directory)}}">
+              Sandbox
+            </a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <table m-table table-content="executor.completed_tasks" title="Completed 
Tasks"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="name">Name</th>
+          <th data-key="role">Role</th>
+          <th data-key="state">State</th>
+          <th data-key="resources.cpus">CPUs (allocated)</th>
+          <th data-key="resources.gpus">GPUs (allocated)</th>
+          <th data-key="resources.mem">Mem (allocated)</th>
+          <th data-key="resources.disk">Disk (allocated)</th>
+          <th></th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="completed_task in $data">
+          <td>{{completed_task.id}}</td>
+          <td>{{completed_task.name}}</td>
+          <td>{{completed_task.role}}</td>
+          <td>{{completed_task.state}}</td>
+          <td>{{completed_task.resources.cpus | number}}</td>
+          <td>{{completed_task.resources.gpus | number}}</td>
+          <td>{{completed_task.resources.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{completed_task.resources.disk * (1024 * 1024) | dataSize}}</td>
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/browse?path=' +
+                       encodeURIComponent(completed_task.directory)}}">
+              Sandbox
+            </a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+
+</div>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/agents/agent-framework.html
----------------------------------------------------------------------
diff --git a/src/webui/app/agents/agent-framework.html 
b/src/webui/app/agents/agent-framework.html
new file mode 100644
index 0000000..06f1697
--- /dev/null
+++ b/src/webui/app/agents/agent-framework.html
@@ -0,0 +1,164 @@
+<ol class="breadcrumb">
+  <li>
+    <a class="badge badge-type" href="#">Master</a>
+  </li>
+  <li>
+    <a class="badge badge-type" href="#/agents/{{agent_id}}" 
title="{{agent_id}}">
+      Agent</a>
+  </li>
+  <li class="active">
+    <span class="badge badge-type">Framework</span>
+    {{framework.id}}
+  </li>
+</ol>
+
+<div class="alert alert-error hidden" id="alert">
+  <button class="close" data-dismiss="alert">×</button>
+  <strong>{{alert_message}}</strong>
+</div>
+
+<div class="row" id="agent">
+  <div class="col-md-3">
+    <div class="well">
+      <dl class="inline clearfix">
+        <dt>Name:</dt><dd>{{framework.name}}</dd>
+        <dt>Master:</dt><dd>{{state.master_hostname}}</dd>
+        <!-- TODO(bmahler): Consider having a break between each role
+             in order to increase readability. Also, this doesn't
+             display well when there are a lot of roles (e.g. a large
+             organization with a lot of teams & services, using roles
+             like /engineering/frontend/webserver, etc). -->
+        <dt>Roles:</dt><dd>{{framework.roles.toString()}}</dd>
+      </dl>
+
+      <dl class="inline clearfix">
+        <dt>Active Tasks:</dt>
+        <dd>{{framework.num_tasks | number}}</dd>
+      </dl>
+
+      <h4>Resources</h4>
+      <table class="table table-condensed">
+        <thead>
+          <tr>
+            <td></td>
+            <td class="text-right">Used</td>
+            <td class="text-right">Allocated</td>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>CPUs</td>
+            <td class="text-right">
+              {{monitor.frameworks[framework.id].statistics.cpus_total_usage | 
number}}
+            </td>
+            <td class="text-right">{{framework.cpus | number}}</td>
+          </tr>
+          <tr>
+            <td>GPUs</td>
+            <td class="text-right">
+              N/A
+            </td>
+            <td class="text-right">{{framework.gpus | number}}</td>
+          </tr>
+          <tr>
+            <td>Memory</td>
+            <td class="text-right">
+              {{monitor.frameworks[framework.id].statistics.mem_rss_bytes | 
dataSize}}
+            </td>
+            <td class="text-right">
+              {{framework.mem * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+          <tr>
+            <td>Disk</td>
+            <td class="text-right">-</td>
+            <td class="text-right">
+              {{(framework.disk || 0) * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+
+  <div class="col-md-9">
+    <table m-table table-content="framework.executors" title="Executors"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="name">Name</th>
+          <th data-key="source">Source</th>
+          <th data-key="role">Role</th>
+          <th data-key="tasks.length">Active Tasks</th>
+          <th data-key="queued_tasks.length">Queued Tasks</th>
+          <th data-key="resources.cpus">CPUs (Used / Allocated)</th>
+          <th data-key="resources.gpus">GPUs (Used / Allocated)</th>
+          <th data-key="resources.mem">Mem (Used / Allocated)</th>
+          <th data-key="resources.disk">Disk (Used / Allocated)</th>
+          <th></th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="executor in $data">
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/frameworks/' + framework.id 
+ '/executors/' + executor.id}}">
+              {{executor.id}}
+            </a>
+          </td>
+          <td>{{executor.name}}</td>
+          <td>{{executor.source}}</td>
+          <td>{{executor.role}}</td>
+          <td>{{executor.tasks.length | number}}</td>
+          <td>{{executor.queued_tasks.length | number}}</td>
+          
<td>{{monitor.frameworks[framework.id].executors[executor.id].statistics.cpus_total_usage
 | number}} /
+              {{executor.resources.cpus | number}}</td>
+          <!-- TODO(haosdent): We need to show statistics for gpu once it is 
provided in monitor endpoint. -->
+          <td>N/A</td>
+          
<td>{{monitor.frameworks[framework.id].executors[executor.id].statistics.mem_rss_bytes
 | dataSize}} /
+              {{executor.resources.mem * (1024 * 1024) | dataSize}}</td>
+          
<td>{{monitor.frameworks[framework.id].executors[executor.id].statistics.disk_used_bytes
 | dataSize}} /
+              {{executor.resources.disk * (1024 * 1024) | dataSize}}</td>
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/browse?path=' +
+                       encodeURIComponent(executor.directory)}}">
+              Sandbox
+            </a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <table m-table table-content="framework.completed_executors" 
title="Completed Executors"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="name">Name</th>
+          <th data-key="source">Source</th>
+          <th data-key="role">Role</th>
+          <th data-key="sandbox">Sandbox</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="completed_executor in $data">
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/frameworks/' + framework.id 
+ '/executors/' + completed_executor.id}}">
+              {{completed_executor.id}}
+            </a>
+          </td>
+          <td>{{completed_executor.name}}</td>
+          <td>{{completed_executor.source}}</td>
+          <td>{{completed_executor.role}}</td>
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/browse?path=' +
+                       encodeURIComponent(completed_executor.directory)}}">
+              browse
+            </a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+
+</div>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/agents/agent.html
----------------------------------------------------------------------
diff --git a/src/webui/app/agents/agent.html b/src/webui/app/agents/agent.html
new file mode 100644
index 0000000..a101a93
--- /dev/null
+++ b/src/webui/app/agents/agent.html
@@ -0,0 +1,293 @@
+<ol class="breadcrumb">
+  <li>
+    <a class="badge badge-type" href="#">Master</a>
+  </li>
+  <li class="active">
+    <span class="badge badge-type">Agent</span>
+    {{agent_id}}
+  </li>
+</ol>
+
+<div class="alert alert-error hidden" id="alert">
+  <button class="close" data-dismiss="alert">×</button>
+  <strong>{{alert_message}}</strong>
+</div>
+
+<div class="row" id="agent">
+  <div class="col-md-3">
+    <div class="well">
+      <dl class="inline clearfix">
+        <dt>Cluster:</dt>
+        <dd>
+          <span ng-show="clusterNamed">{{cluster}}</span>
+          <span ng-show="!clusterNamed">
+            (Unnamed)
+            <i class="icon-info-sign"
+              tooltip="To name this cluster, set the --cluster flag when 
starting the master."
+              tooltip-placement="right"></i>
+          </span>
+        </dd>
+        <dt>Agent:</dt>
+        <dd>{{state.hostname}}</dd>
+        <dt>Version:</dt>
+        <dd>{{state.version}}</dd>
+        <dt>Built:</dt>
+        <dd>
+          <m-timestamp value="{{state.build_time * 1000}}"></m-timestamp>
+        </dd>
+        <dt>Started:</dt>
+        <dd>
+          <m-timestamp value="{{state.start_time * 1000}}"></m-timestamp>
+        </dd>
+        <dt>Master:</dt>
+        <dd>{{state.master_hostname}}</dd>
+      </dl>
+
+      <p ng-if="agent.log_file_attached">
+        <b>Agent Log:</b>
+        <span class="btn-group">
+          <!-- Links can look like buttons using Bootstrap classes. -->
+          <a class="btn btn-xs btn-default" 
href="{{agent.url_prefix}}/files/download?path=/slave/log">
+            Download
+          </a>
+          <button class="btn btn-xs btn-default" ng-click="streamLogs($event)">
+            View
+          </button>
+        </span>
+      </p>
+
+      <h4>Tasks</h4>
+      <table class="table table-condensed">
+        <tbody>
+          <tr>
+            <td>Staging</td>
+            <td class="text-right">{{staging_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Starting</td>
+            <td class="text-right">{{starting_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Running</td>
+            <td class="text-right">{{running_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Killing</td>
+            <td class="text-right">{{killing_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Finished</td>
+            <td class="text-right">{{finished_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Killed</td>
+            <td class="text-right">{{killed_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Failed</td>
+            <td class="text-right">{{failed_tasks | number}}</td>
+          </tr>
+          <tr>
+            <td>Lost</td>
+            <td class="text-right">{{lost_tasks | number}}</td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h4>Resources</h4>
+      <table class="table table-condensed">
+        <thead>
+          <tr>
+            <td></td>
+            <td class="text-right">Used</td>
+            <td class="text-right">Allocated</td>
+            <td class="text-right">Available</td>
+            <td class="text-right">Total</td>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td>CPUs</td>
+            <td class="text-right">
+              {{monitor.statistics.cpus_total_usage | number}}
+            </td>
+            <td class="text-right">
+              {{state.allocated_resources.cpus | number}}
+            </td>
+            <td class="text-right">
+              {{state.resources.cpus - state.allocated_resources.cpus | 
number}}
+            </td>
+            <td class="text-right">
+              {{state.resources.cpus | number}}
+            </td>
+          </tr>
+          <tr>
+            <td>GPUs</td>
+            <td class="text-right">
+              N/A
+            </td>
+            <td class="text-right">
+              {{state.allocated_resources.gpus | number}}
+            </td>
+            <td class="text-right">
+              {{state.resources.gpus - state.allocated_resources.gpus | 
number}}
+            </td>
+            <td class="text-right">
+              {{state.resources.gpus | number}}
+            </td>
+          </tr>
+          <tr>
+            <td>Memory</td>
+            <td class="text-right">
+              {{monitor.statistics.mem_rss_bytes | dataSize}}
+            </td>
+            <td class="text-right">
+              {{state.allocated_resources.mem * (1024 * 1024) | dataSize}}
+            </td>
+            <td class="text-right">
+              {{(state.resources.mem - state.allocated_resources.mem) * (1024 
* 1024) | dataSize}}
+            </td>
+            <td class="text-right">
+              {{state.resources.mem * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+          <tr>
+            <td>Disk</td>
+            <td class="text-right">
+              {{monitor.statistics.disk_used_bytes | dataSize}}
+            </td>
+            <td class="text-right">
+              {{state.allocated_resources.disk * (1024 * 1024) | dataSize}}
+            </td>
+            <td class="text-right">
+              {{(state.resources.disk - state.allocated_resources.disk) * 
(1024 * 1024) | dataSize}}
+            </td>
+            <td class="text-right">
+              {{state.resources.disk * (1024 * 1024) | dataSize}}
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+  </div>
+  <div class="col-md-9">
+    <table m-table table-content="agent.reserved_resources_as_array" 
title="Resource Reservations"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="role">Reservation Role</th>
+          <th data-key="cpus">CPUs (Allocated / Total)</th>
+          <th data-key="gpus">GPUs (Allocated / Total)</th>
+          <th data-key="mem">Mem (Allocated / Total)</th>
+          <th data-key="disk">Disk (Allocated / Total)</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td><em>Unreserved</em></td>
+          <td>{{state.unreserved_resources_allocated.cpus | number}} / 
{{state.unreserved_resources.cpus | number}}</td>
+          <td>{{state.unreserved_resources_allocated.gpus | number}} / 
{{state.unreserved_resources.gpus | number}}</td>
+          <td>{{state.unreserved_resources_allocated.mem * (1024 * 1024) | 
dataSize}} / {{state.unreserved_resources.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{state.unreserved_resources_allocated.disk * (1024 * 1024) | 
dataSize}} / {{state.unreserved_resources.disk * (1024 * 1024) | dataSize}}</td>
+        </tr>
+        <tr ng-repeat="reservation in $data">
+          <td>{{reservation.role}}</td>
+          <td>{{(state.reserved_resources_allocated[reservation.role].cpus || 
0) | number}} / {{reservation.cpus | number}}</td>
+          <td>{{(state.reserved_resources_allocated[reservation.role].gpus || 
0) | number}} / {{reservation.gpus | number}}</td>
+          <td>{{(state.reserved_resources_allocated[reservation.role].mem * 
(1024 * 1024) || 0) | dataSize}} / {{reservation.mem * (1024 * 1024) | 
dataSize}}</td>
+          <td>{{(state.reserved_resources_allocated[reservation.role].disk * 
(1024 * 1024) || 0) | dataSize}} / {{reservation.disk * (1024 * 1024) | 
dataSize}}</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <table m-table table-content="agent.frameworks" title="Frameworks"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="user">User</th>
+          <th data-key="name">Name</th>
+          <th data-key="roles">Roles</th>
+          <th data-key="num_tasks">Active Tasks</th>
+          <th data-key="cpus">CPUs (Used / Allocated)</th>
+          <th data-key="gpus">GPUs (Used / Allocated)</th>
+          <th data-key="mem">Mem (Used / Allocated)</th>
+          <th data-key="disk">Disk (Used / Allocated)</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="framework in $data">
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/frameworks/' + 
framework.id}}">
+              {{(framework.id | truncateMesosID) || framework.name}}</a>
+            <button class="btn btn-xs btn-toggle btn-default"
+              clipboard
+              data-clipboard-text="{{framework.id}}"
+              tooltip="Copy ID"
+              tooltip-placement="right"
+              tooltip-trigger="clipboardhover">
+            </button>
+          </td>
+          <td>{{framework.user}}</td>
+          <td>{{framework.name}}</td>
+          <!-- TODO(bmahler): This doesn't display well when there are a lot
+               of roles (e.g. a large organization with a lot of teams &
+               services, using roles like /engineering/frontend/webserver, 
etc).
+               Figure out a way to display this without bloating the table. -->
+          <td>{{framework.roles.toString()}}</td>
+          <td>{{framework.num_tasks | number}}</td>
+          <td>{{monitor.frameworks[framework.id].statistics.cpus_total_usage | 
number}} / {{framework.cpus | number}}</td>
+          <!-- TODO(haosdent): We need to show statistics for gpu once it is 
provided in monitor endpoint. -->
+          <td>N/A</td>
+          <td>{{monitor.frameworks[framework.id].statistics.mem_rss_bytes | 
dataSize}} / {{framework.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{monitor.frameworks[framework.id].statistics.disk_used_bytes | 
dataSize}} / {{framework.disk * (1024 * 1024) | dataSize}}</td>
+        </tr>
+      </tbody>
+    </table>
+
+    <table m-table table-content="agent.completed_frameworks" title="Completed 
Frameworks"
+      class="table table-striped table-bordered table-condensed">
+      <thead>
+        <tr>
+          <th data-key="id">ID</th>
+          <th data-key="user">User</th>
+          <th data-key="name">Name</th>
+          <th data-key="roles">Roles</th>
+          <th data-key="tasks.length">Active Tasks</th>
+          <th data-key="resources.cpus">CPUs</th>
+          <th data-key="resources.gpus">GPUs</th>
+          <th data-key="resources.mem">Mem</th>
+          <th data-key="resources.disk">Disk</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr ng-repeat="completed_framework in $data">
+          <td>
+            <a href="{{'#/agents/' + agent_id + '/frameworks/' + 
completed_framework.id}}">
+              {{completed_framework.id | truncateMesosID}}</a>
+            <button class="btn btn-xs btn-toggle btn-default"
+              clipboard
+              data-clipboard-text="{{framework.id}}"
+              tooltip="Copy ID"
+              tooltip-placement="right"
+              tooltip-trigger="clipboardhover">
+            </button>
+          </td>
+          <td>{{completed_framework.user}}</td>
+          <td>{{completed_framework.name}}</td>
+          <!-- TODO(bmahler): This doesn't display well when there are a lot
+               of roles (e.g. a large organization with a lot of teams &
+               services, using roles like /engineering/frontend/webserver, 
etc).
+               Figure out a way to display this without bloating the table. -->
+          <td>{{completed_framework.roles.toString()}}</td>
+          <td>{{completed_framework.num_tasks | number}}</td>
+          <td>{{completed_framework.cpus | number}}</td>
+          <td>{{completed_framework.gpus | number}}</td>
+          <td>{{completed_framework.mem * (1024 * 1024) | dataSize}}</td>
+          <td>{{completed_framework.disk * (1024 * 1024) | dataSize}}</td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/agents/agents.html
----------------------------------------------------------------------
diff --git a/src/webui/app/agents/agents.html b/src/webui/app/agents/agents.html
new file mode 100644
index 0000000..98712c6
--- /dev/null
+++ b/src/webui/app/agents/agents.html
@@ -0,0 +1,56 @@
+<ol class="breadcrumb">
+  <li>
+    <a class="badge badge-type" href="#">Master</a>
+  </li>
+  <li class="active">
+    <span class="badge badge-type">Agents</span>
+  </li>
+</ol>
+
+<table m-table table-content="agents" title="Agents"
+  class="table table-striped table-bordered table-condensed">
+  <thead>
+    <tr>
+      <th data-key="id">ID</th>
+      <th data-key="hostname">Host</th>
+      <th data-key="resources.cpus">CPUs (Allocated / Total)</th>
+      <th data-key="resources.gpus">GPUs (Allocated / Total)</th>
+      <th data-key="resources.mem">Mem (Allocated / Total)</th>
+      <th data-key="resources.disk">Disk (Allocated / Total)</th>
+      <th data-key="registered_time">Registered</th>
+      <th data-key="reregistered_time">Re-Registered</th>
+    </tr>
+  </thead>
+  <tr ng-repeat="agent in $data">
+    <td>
+      <a href="#/agents/{{agent.id}}">{{agent.id | truncateMesosID}}</a>
+      <button class="btn btn-xs btn-default btn-toggle"
+          clipboard
+          data-clipboard-text="{{agent.id}}"
+          tooltip="Copy ID"
+          tooltip-placement="right"
+          tooltip-trigger="clipboardhover">
+        <i class="icon-file"></i>
+      </button>
+    </td>
+    <td>{{agent.hostname}}</td>
+    <td>
+      {{agent.used_resources.cpus | number}} / {{agent.resources.cpus | 
number}}
+    </td>
+    <td>
+      {{agent.used_resources.gpus | number}} / {{agent.resources.gpus | 
number}}
+    </td>
+    <td>
+      {{agent.used_resources.mem * (1024 * 1024) | dataSize}} / 
{{agent.resources.mem * (1024 * 1024) | dataSize}}
+    </td>
+    <td>
+      {{agent.used_resources.disk * (1024 * 1024) | dataSize}} / 
{{agent.resources.disk * (1024 * 1024) | dataSize}}
+    </td>
+    <td>
+      <m-timestamp value="{{agent.registered_time * 1000}}"></m-timestamp>
+    </td>
+    <td>
+      <m-timestamp value="{{agent.reregistered_time * 1000}}"></m-timestamp>
+    </td>
+  </tr>
+</table>

http://git-wip-us.apache.org/repos/asf/mesos/blob/c7685917/src/webui/app/app.js
----------------------------------------------------------------------
diff --git a/src/webui/app/app.js b/src/webui/app/app.js
new file mode 100644
index 0000000..f6f1138
--- /dev/null
+++ b/src/webui/app/app.js
@@ -0,0 +1,371 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+(function() {
+  'use strict';
+
+  angular.module('mesos', ['ngRoute', 'mesos.services', 'ui.bootstrap', 
'ui.bootstrap.dialog']).
+    config(['paginationConfig', '$routeProvider', function(paginationConfig, 
$routeProvider) {
+      $routeProvider
+        .when('/',
+          {templateUrl: 'app/home.html', controller: 'HomeCtrl'})
+        .when('/agents',
+          {templateUrl: 'app/agents/agents.html', controller: 'AgentsCtrl'})
+        .when('/agents/:agent_id',
+          {templateUrl: 'app/agents/agent.html', controller: 'AgentCtrl'})
+        .when('/agents/:agent_id/frameworks/:framework_id',
+          {templateUrl: 'app/agents/agent-framework.html', controller: 
'AgentFrameworkCtrl'})
+        
.when('/agents/:agent_id/frameworks/:framework_id/executors/:executor_id',
+          {templateUrl: 'app/agents/agent-executor.html', controller: 
'AgentExecutorCtrl'})
+        .when('/frameworks',
+          {templateUrl: 'app/frameworks/frameworks.html', controller: 
'FrameworksCtrl'})
+        .when('/frameworks/:id',
+          {templateUrl: 'app/frameworks/framework.html', controller: 
'FrameworkCtrl'})
+        .when('/maintenance',
+          {templateUrl: 'app/maintenance/maintenance.html', controller: 
'MaintenanceCtrl'})
+        .when('/offers',
+          {templateUrl: 'app/offers/offers.html', controller: 'OffersCtrl'})
+        .when('/roles',
+          {templateUrl: 'app/roles/roles.html', controller: 'RolesCtrl'})
+
+        // TODO(tomxing): Remove the following '/slaves/*' paths once the
+        // slave->agent rename is complete(MESOS-3779).
+        .when('/slaves', {redirectTo: '/agents'})
+        .when('/slaves/:agent_id', {redirectTo: '/agents/:agent_id'})
+        .when('/slaves/:agent_id/frameworks/:framework_id',
+          {redirectTo: '/agents/:agent_id/frameworks/:framework_id'})
+        
.when('/slaves/:agent_id/frameworks/:framework_id/executors/:executor_id',
+          {redirectTo: 
'/agents/:agent_id/frameworks/:framework_id/executors/:executor_id'})
+
+        // Use a non-falsy template so the controller will still be executed.
+        // Since the controller is intended only to redirect, the blank 
template
+        // is fine.
+        //
+        // By design, controllers currently will not handle routes if the
+        // template is falsy. There is an issue open in Angular to add that
+        // feature:
+        //
+        //     https://github.com/angular/angular.js/issues/1838
+        
.when('/agents/:agent_id/frameworks/:framework_id/executors/:executor_id/browse',
+          {template: ' ', controller: 'AgentTaskAndExecutorRerouterCtrl'})
+        
.when('/agents/:agent_id/frameworks/:framework_id/executors/:executor_id/tasks/:task_id/browse',
+          {template: ' ', controller: 'AgentTaskAndExecutorRerouterCtrl'})
+        .when('/agents/:agent_id/browse',
+          {templateUrl: 'app/agents/agent-browse.html', controller: 
'BrowseCtrl'})
+
+        // TODO(tomxing): Remove the following '/slaves/*' paths once the
+        // slave->agent rename is complete(MESOS-3779).
+        
.when('/slaves/:agent_id/frameworks/:framework_id/executors/:executor_id/browse',
+          {redirectTo: 
'/agents/:agent_id/frameworks/:framework_id/executors/:executor_id/browse'})
+        .when('/slaves/:agent_id/browse',
+          {redirectTo: '/agents/:agent_id/browse'})
+        .otherwise({redirectTo: '/'});
+
+      // Configure [Angular UI Pagination][1]:
+      //   * Show first/last buttons
+      //   * Show 50 items per page
+      //   * Show "..." when there are pages beyond the max shown
+      //
+      // [1] http://angular-ui.github.io/bootstrap/#/pagination
+      paginationConfig.boundaryLinks = true;
+      paginationConfig.rotate = false;
+    }])
+    .filter('truncateMesosID', function() {
+      // Returns a truncated ID, for example:
+      // Input: 9d4b2f2b-a759-4458-bebf-7d3507a6f0ca-S9
+      // Output: ...7d3507a6f0ca-S9
+      //
+      // Note that an ellipsis is used for display purposes.
+      return function(id) {
+        if (id) {
+          var truncatedIdParts = id.split('-');
+
+          if (truncatedIdParts.length > 4) {
+            return '\u2026' + truncatedIdParts.splice(4).join('-');
+          } else {
+            return id;
+          }
+        } else {
+          return '';
+        }
+      };
+    })
+    .filter('truncateMesosState', function() {
+      return function(state) {
+        // Remove the "TASK_" prefix.
+        return state.substring(5);
+      };
+    })
+    .filter('taskHealth', function() {
+      return function(healthy) {
+        if (healthy == null) {
+          return "-";
+        }
+
+        // Note that this string value is relied on to match
+        // against CSS classes to color the UI. Changing this
+        // also requires an update to the CSS.
+        return healthy ? "healthy" : "unhealthy";
+      }
+    })
+    .filter('isoDate', function($filter) {
+      return function(date) {
+        var i = parseInt(date, 10);
+        if (_.isNaN(i)) { return '' }
+        return $filter('date')(i, 'yyyy-MM-ddTHH:mm:ssZ');
+      };
+    })
+    .filter('relativeDate', function() {
+      return function(date, refDate) {
+        var i = parseInt(date, 10);
+        if (_.isNaN(i)) { return '' }
+        return relativeDate(i, refDate);
+      };
+    })
+    .filter('slice', function() {
+      return function(array, begin, end) {
+        if (_.isArray(array)) {
+          return array.slice(begin, end);
+        }
+      };
+    })
+    .filter('unixDate', function($filter) {
+      return function(date) {
+        if ((new Date(date)).getFullYear() == (new Date()).getFullYear()) {
+          return $filter('date')(date, 'MMM dd HH:mm');
+        } else {
+          return $filter('date')(date, 'MMM dd yyyy');
+        }
+      };
+    })
+    // A filter that uses to convert small float number to decimal string.
+    .filter('decimalFloat', function() {
+      return function(num) {
+        return num ? parseFloat(num.toFixed(4)).toString() : num;
+      }
+    })
+    .filter('dataSize', function() {
+      var BYTES_PER_KB = Math.pow(2, 10);
+      var BYTES_PER_MB = Math.pow(2, 20);
+      var BYTES_PER_GB = Math.pow(2, 30);
+      var BYTES_PER_TB = Math.pow(2, 40);
+      var BYTES_PER_PB = Math.pow(2, 50);
+      // NOTE: Number.MAX_SAFE_INTEGER is 2^53 - 1
+
+      return function(bytes) {
+        if (bytes == null || isNaN(bytes)) {
+          return '';
+        } else if (bytes < BYTES_PER_KB) {
+          return bytes.toFixed() + ' B';
+        } else if (bytes < BYTES_PER_MB) {
+          return (bytes / BYTES_PER_KB).toFixed() + ' KB';
+        } else if (bytes < BYTES_PER_GB) {
+          return (bytes / BYTES_PER_MB).toFixed() + ' MB';
+        } else if (bytes < BYTES_PER_TB) {
+          return (bytes / BYTES_PER_GB).toFixed(1) + ' GB';
+        } else if (bytes < BYTES_PER_PB) {
+          return (bytes / BYTES_PER_TB).toFixed(1) + ' TB';
+        } else {
+          return (bytes / BYTES_PER_PB).toFixed(1) + ' PB';
+        }
+      };
+    })
+    .directive('clipboard', [function() {
+      return {
+        restrict: 'A',
+        scope: true,
+        template: '<i class="glyphicon glyphicon-file"></i>',
+
+        link: function(scope, element, _attrs) {
+          var clip = new Clipboard(element[0]);
+
+          element.on('mouseenter', function() {
+            element.addClass('clipboard-is-hover');
+            element.triggerHandler('clipboardhover');
+          });
+
+          element.on('mouseleave', function() {
+            // Restore tooltip content to its original value if it was
+            // changed by this Clipboard instance.
+            if (scope && scope.tt_content_orig) {
+              scope.tt_content = scope.tt_content_orig;
+              delete scope.tt_content_orig;
+            }
+
+            element.removeClass('clipboard-is-hover');
+            element.triggerHandler('clipboardhover');
+          });
+
+          // Success for browsers with `execCommand` support.
+          clip.on('success', function () {
+            // Store the tooltip's original content so it can
+            // be restored when the tooltip is hidden.
+            scope.tt_content_orig = scope.tt_content;
+
+            // Angular UI's Tooltip sets content on the element's scope in a
+            // variable named 'tt_content'. The Tooltip has no public 
interface,
+            // so set the value directly here to change the value of the 
tooltip
+            // when content is successfully copied.
+            scope.tt_content = 'Copied!';
+            scope.$apply();
+          });
+
+          // Support for all other browsers without `execCommand`
+          // support. Text will be selected and user will be prompted
+          // to copy.
+          clip.on('error', function() {
+            scope.tt_content_orig = scope.tt_content;
+            scope.tt_content = 'Press Ctrl/Cmd + C to copy!';
+            scope.$apply();
+          });
+        }
+      };
+    }])
+    .directive('mTimestamp', [ '$rootScope', function($rootScope) {
+      return {
+        restrict: 'E',
+        transclude: true,
+        scope: {
+          value: '@'
+        },
+        link: function($scope, _element, _attrs) {
+          $scope.longDate = JSON.parse(
+            localStorage.getItem('longDate') || false);
+
+          $scope.$on('mTimestamp.toggle', function() {
+            $scope.longDate = !$scope.longDate;
+          });
+
+          $scope.toggle = function() {
+            localStorage.setItem('longDate', !$scope.longDate);
+            $rootScope.$broadcast('mTimestamp.toggle');
+          };
+        },
+        templateUrl: 'app/shared/timestamp.html'
+      }
+    }])
+    .directive('mPagination', function() {
+      return { templateUrl: 'app/shared/pagination.html' }
+    })
+    .directive('mTableHeader', function() {
+      return { templateUrl: 'app/shared/table-header.html' }
+    })
+    .directive('mTable', ['$compile', '$filter', function($compile, $filter) {
+      /* This directive does not have a template. The DOM doesn't like
+       * having partially defined tables and so they don't work well with
+       * directives and templates. Because of this, the sub-elements that this
+       * includes are their own directive/templates and it adds them via. DOM
+       * manipulation here.
+       */
+      return {
+        scope: true,
+        link: function(scope, element, attrs) {
+          var defaultOrder = true;
+
+          _.extend(scope, {
+            originalData: [],
+            columnKey: '',
+            sortOrder: defaultOrder,
+            pgNum: 1,
+            pageLength: 50,
+            filterTerm: '',
+            headerTitle: attrs.title
+          })
+          // ---
+
+          // --- Allow sorting by column based on the <th> data-key attribute.
+          // Does not apply for group columns as their children are sortable.
+          var th = element.find('th').not('.group-column');
+          th.attr('ng-click', 'sortColumn($event)');
+          $compile(th)(scope);
+
+          var setSorting = function(el) {
+            var key = el.attr('data-key');
+
+            // Prevent sorting when 'data-key' is undefined.
+            if (!key) {
+              return;
+            }
+
+            if (scope.columnKey === key) {
+              scope.sortOrder = !scope.sortOrder;
+            } else if (el.hasClass('ascending')) {
+              // We can order the table the other way around by adding
+              // 'class="ascending"' to the table header.
+              scope.sortOrder = !defaultOrder;
+            } else {
+              scope.sortOrder = defaultOrder;
+            }
+
+            scope.columnKey = key;
+
+            th.removeClass('descending ascending');
+            el.addClass(scope.sortOrder ? 'descending' : 'ascending');
+          };
+
+          var defaultSortColumn = function() {
+            var el = element.find('[data-sort]');
+            if (el.length === 0) {
+              el = element.find('th:first');
+            }
+            return el;
+          };
+
+          scope.sortColumn = function(ev) {
+            setSorting(angular.element(ev.target));
+          };
+
+          setSorting(defaultSortColumn());
+          // ---
+
+          scope.$watch(attrs.tableContent, function(data) {
+            if (!data) { scope.originalData = []; return }
+            if (angular.isObject(data)) { data = _.values(data) }
+
+            scope.originalData = data;
+          });
+
+          var setTableData = function() {
+            scope.filteredData = $filter('filter')(scope.originalData, 
scope.filterTerm)
+            scope.$data = $filter('orderBy')(
+              scope.filteredData,
+              scope.columnKey,
+              scope.sortOrder).slice(
+                (scope.pgNum - 1) * scope.pageLength,
+                scope.pgNum * scope.pageLength);
+          };
+
+          // Reset the page number for each new filtering.
+          scope.$watch('filterTerm', function() { scope.pgNum = 1; });
+
+          _.each(['originalData', 'columnKey', 'sortOrder', 'pgNum', 
'filterTerm'],
+            function(k) { scope.$watch(k, setTableData); });
+
+          // --- Pagination controls
+          var el = angular.element('<div m-pagination></div>');
+          $compile(el)(scope);
+          element.after(el);
+          // ---
+
+          // --- Filtering
+          el = angular.element('<div m-table-header></div>');
+          $compile(el)(scope);
+          element.before(el);
+          // ---
+        }
+      };
+     }]);
+})();

Reply via email to