http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html 
b/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html
new file mode 100644
index 0000000..da6b75b
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/config.html
@@ -0,0 +1,33 @@
+
+<!--
+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.
+
+-->
+<table class="table table-properties">
+  <thead>
+    <tr>
+      <th>Key</th>
+      <th>Value</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="entry in jobmanager.config | orderBy: 'key'">
+      <td>{{entry.key}}</td>
+      <td>{{entry.value}}</td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html 
b/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html
new file mode 100644
index 0000000..02c2f47
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/index.html
@@ -0,0 +1,33 @@
+
+<!--
+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.
+
+-->
+<nav class="navbar navbar-default navbar-fixed-top navbar-main">
+  <div id="fold-button" ng-click="showSidebar()" class="btn btn-default 
navbar-btn pull-left"><i class="fa fa-navicon"></i></div>
+  <div class="navbar-title">Job Manager</div>
+</nav>
+<nav class="navbar navbar-default navbar-fixed-top navbar-main-additional">
+  <ul class="nav nav-tabs">
+    <li ui-sref-active="active"><a ui-sref=".config">Configuration</a></li>
+    <li ui-sref-active="active"><a ui-sref=".log">Logs</a></li>
+    <li ui-sref-active="active"><a ui-sref=".stdout">Stdout</a></li>
+  </ul>
+</nav>
+<div id="content-inner" class="has-navbar-main-additional">
+  <div ui-view="details"></div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html 
b/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html
new file mode 100644
index 0000000..df6a817
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobmanager/stdout.html
@@ -0,0 +1,40 @@
+
+<!--
+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.
+
+
+-->
+<table class="table table-properties">
+  <thead>
+    <tr>
+      <th colspan="2">
+        <div class="row">
+          <div class="col-xs-10">Job Manager Output</div>
+          <div class="col-xs-1 text-right"><a ng-click="reloadData()" 
class="show-pointer"><i class="fa fa-refresh"></i></a></div>
+          <div class="col-xs-1 text-left"><a href="jobmanager/stdout"><i 
class="fa fa-download"></i></a></div>
+        </div>
+      </th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td colspan="2">
+        <pre>{{jobmanager.stdout}}</pre>
+      </td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html
new file mode 100644
index 0000000..b76278d
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/completed-jobs.html
@@ -0,0 +1,53 @@
+
+<!--
+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.
+
+-->
+<nav class="navbar navbar-default navbar-fixed-top navbar-main">
+  <div id="fold-button" ng-click="showSidebar()" class="btn btn-default 
navbar-btn pull-left"><i class="fa fa-navicon"></i></div>
+  <div class="navbar-title">Completed Jobs</div>
+</nav>
+<div id="content-inner">
+  <table class="table table-hover table-clickable">
+    <thead>
+      <tr>
+        <th>Start Time</th>
+        <th>End Time</th>
+        <th>Duration</th>
+        <th>Job Name</th>
+        <th>Job ID</th>
+        <th>Tasks</th>
+        <th>Status</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr ng-repeat="job in jobs" ui-sref="single-job.plan.overview({ jobid: 
job.jid })">
+        <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+        <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+        <td>{{job.duration}} ms</td>
+        <td>{{job.name}}</td>
+        <td>{{job.jid}}</td>
+        <td class="label-group">
+          <bs-label status="{{status}}" ng-repeat="(status, value) in 
job.tasks">{{value}}</bs-label>
+        </td>
+        <td> 
+          <bs-label status="{{job.state}}">{{job.state}}</bs-label>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html
new file mode 100644
index 0000000..a7a5d9d
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.config.html
@@ -0,0 +1,57 @@
+
+<!--
+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.
+
+-->
+<table ng-if="job['execution-config']" class="table table-properties">
+  <thead>
+    <tr>
+      <th colspan="2">Execution configuration</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td>Execution mode</td>
+      <td>{{ job['execution-config']['execution-mode'] }}</td>
+    </tr>
+    <tr>
+      <td>Max. number of execution retries</td>
+      <td>{{ job['execution-config']['max-execution-retries'] === -1 ? 
'deactivated' : job['execution-config']['max-execution-retries'] }}</td>
+    </tr>
+    <tr>
+      <td>Job parallelism</td>
+      <td>{{ job['execution-config']['job-parallelism'] === -1 ? 'auto' : 
job['execution-config']['job-parallelism'] }}</td>
+    </tr>
+    <tr>
+      <td>Object reuse mode</td>
+      <td>{{ job['execution-config']['object-reuse-mode'] }}</td>
+    </tr>
+  </tbody>
+</table>
+<table ng-if="job['execution-config']['user-config']" class="table 
table-properties">
+  <thead>
+    <tr>
+      <th colspan="2">User configuration</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="property in job['execution-config']['user-config']">
+      <td>{{property.name}}</td>
+      <td table-property="table-property" value="property.value"></td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html
new file mode 100644
index 0000000..a5f6676
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.exceptions.html
@@ -0,0 +1,38 @@
+
+<!--
+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.
+
+-->
+<div ng-if="exceptions['root-exception']" class="panel panel-default 
panel-multi">
+  <div class="panel-heading clearfix">
+    <div class="panel-title">Root exception</div>
+  </div>
+  <div class="panel-body">
+    <pre class="exception">{{ exceptions['root-exception'] }}</pre>
+  </div>
+</div>
+<div ng-repeat="exception in exceptions['all-exceptions']" class="panel 
panel-default panel-multi">
+  <div class="panel-heading clearfix">
+    <div class="panel-title">{{ exception.task }}</div>
+  </div>
+  <div class="panel-heading clearfix">
+    <div class="panel-info thin last"><span>{{ exception.location 
}}</span></div>
+  </div>
+  <div class="panel-body">
+    <pre class="exception">{{ exception.exception }}</pre>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/resources/web/partials/jobs/job.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.html
new file mode 100644
index 0000000..9d3e171
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.html
@@ -0,0 +1,48 @@
+
+<!--
+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.
+
+-->
+<nav ng-if="job" class="navbar navbar-default navbar-fixed-top navbar-main">
+  <div id="fold-button" ng-click="showSidebar()" class="btn btn-default 
navbar-btn pull-left"><i class="fa fa-navicon"></i></div>
+  <div class="navbar-title">
+    <indicator-primary status="{{job.state}}"></indicator-primary>{{ job.name 
}}
+  </div>
+  <div class="navbar-info first last hidden-xs hidden-sm">{{ job.jid }}</div>
+  <div class="navbar-info first last">
+    <div class="label-group">
+      <bs-label status="{{status}}" ng-repeat="(status, value) in 
job['status-counts']">{{value}}</bs-label>
+    </div>
+  </div>
+  <div class="navbar-info first last hidden-xs hidden-sm">{{ job['start-time'] 
| amDateFormat:'YYYY-MM-DD, H:mm:ss' }}<span ng-if="job['end-time'] &gt; -1">
+      - 
+      {{ job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></div>
+  <div ng-if="job.duration &gt; -1" class="navbar-info last 
first">{{job.duration}} ms</div>
+</nav>
+<nav ng-if="job" class="navbar navbar-default navbar-fixed-top 
navbar-main-additional">
+  <ul class="nav nav-tabs">
+    <li ui-sref-active="active"><a ui-sref=".plan.overview">Plan</a></li>
+    <li ui-sref-active="active"><a ui-sref=".statistics">Job Accumulators / 
Statistics</a></li>
+    <li ui-sref-active="active"><a ui-sref=".timeline">Timeline</a></li>
+    <li ui-sref-active="active"><a ui-sref=".exceptions">Exceptions</a></li>
+    <li ui-sref-active="active"><a ui-sref=".properties">Properties</a></li>
+    <li ui-sref-active="active"><a ui-sref=".config">Configuration</a></li>
+  </ul>
+</nav>
+<div id="content-inner" class="has-navbar-main-additional">
+  <div ui-view="details"></div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html
new file mode 100644
index 0000000..f2c4143
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.html
@@ -0,0 +1,31 @@
+
+<!--
+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.
+
+-->
+<div class="canvas-wrapper">
+  <div job-plan="job-plan" plan="plan" jobid="{{jobid}}" 
set-node="changeNode(nodeid)" class="main-canvas"></div>
+</div>
+<div ng-if="plan" class="panel panel-default panel-multi">
+  <nav class="navbar navbar-default navbar-secondary-additional">
+    <ul class="nav nav-tabs">
+      <li ui-sref-active="active"><a ui-sref=".overview({nodeid: 
nodeid})">Overview</a></li>
+      <li ui-sref-active="active"><a ui-sref=".accumulators({nodeid: 
nodeid})">Accumulators</a></li>
+    </ul>
+  </nav>
+  <div ui-view="node-details" class="panel-body clean"></div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html
 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html
new file mode 100644
index 0000000..8de3921
--- /dev/null
+++ 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.accumulators.html
@@ -0,0 +1,40 @@
+
+<!--
+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.
+
+-->
+<table class="table table-body-hover table-clickable table-activable">
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Status</th>
+    </tr>
+  </thead>
+  <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" 
ng-click="v.id == nodeid || changeNode(v.id)">
+    <tr ng-if="v.type == 'regular'">
+      <td>{{ v.name | humanizeText }}</td>
+      <td> 
+        <bs-label status="{{v.status}}">{{v.status}}</bs-label>
+      </td>
+    </tr>
+    <tr ng-if="nodeid &amp;&amp; v.id == nodeid">
+      <td colspan="10">
+        <div ng-include=" 'partials/jobs/job.plan.node.accumulators.html' 
"></div>
+      </td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html
 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html
new file mode 100644
index 0000000..1706d3e
--- /dev/null
+++ 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node-list.overview.html
@@ -0,0 +1,60 @@
+
+<!--
+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.
+
+-->
+<table class="table table-body-hover table-clickable table-activable">
+  <thead>
+    <tr>
+      <th>Start Time</th>
+      <th>End Time</th>
+      <th>Duration</th>
+      <th>Name</th>
+      <th>Bytes read</th>
+      <th>Records read</th>
+      <th>Bytes written</th>
+      <th>Records written</th>
+      <th>Tasks</th>
+      <th>Status</th>
+    </tr>
+  </thead>
+  <tbody ng-repeat="v in job.vertices" ng-class="{ active: v.id == nodeid }" 
ng-click="changeNode(v.id)">
+    <tr ng-if="v.type == 'regular'">
+      <td><span ng-if="v['start-time'] &gt; -1">{{ v['start-time'] | 
amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td>
+      <td><span ng-if="v['end-time'] &gt; -1">{{ v['end-time'] | 
amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td>
+      <td><span ng-if="v.duration &gt; -1">{{ v.duration }} ms</span></td>
+      <td class="td-long">{{ v.name | humanizeText }}</td>
+      <td>{{ v.metrics['read-bytes'] }}</td>
+      <td>{{ v.metrics['read-records'] }}</td>
+      <td>{{ v.metrics['write-bytes'] }}</td>
+      <td>{{ v.metrics['write-records'] }}</td>
+      <td>
+        <div class="label-group">
+          <bs-label status="{{status}}" ng-repeat="(index, status) in 
stateList">{{v.tasks[status]}}</bs-label>
+        </div>
+      </td>
+      <td> 
+        <bs-label status="{{v.status}}">{{v.status}}</bs-label>
+      </td>
+    </tr>
+    <tr ng-if="nodeid &amp;&amp; v.id == nodeid">
+      <td colspan="10">
+        <div ng-include=" 'partials/jobs/job.plan.node.subtasks.html' "></div>
+      </td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html
 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html
new file mode 100644
index 0000000..e7dcf2c
--- /dev/null
+++ 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.accumulators.html
@@ -0,0 +1,68 @@
+
+<!--
+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.
+
+-->
+<div ng-if="accumulators.length == 0">
+  <p><i>No accumulators</i></p>
+</div>
+<div ng-if="accumulators &amp;&amp; accumulators.length &gt; 0">
+  <table class="table table-hover table-clickable table-activable table-inner">
+    <thead>
+      <tr>
+        <th>Name</th>
+        <th>Type</th>
+        <th>Value</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr ng-repeat="accumulator in accumulators">
+        <td width="30%">{{ accumulator.name }}</td>
+        <td width="30%">{{ accumulator.type }}</td>
+        <td width="30%">{{ accumulator.value }}</td>
+      </tr>
+    </tbody>
+  </table>
+  <div ng-if="!nodeUnfolded"><a ng-click="toggleFold()" class="btn 
btn-default">
+      Show subtasks
+       <i class="fa fa-chevron-down"></i></a><a ng-click="deactivateNode(); 
$event.stopPropagation()" title="Fold" class="btn btn-default pull-right"><i 
class="fa fa-chevron-up"></i></a></div>
+  <div ng-if="nodeUnfolded &amp;&amp; subtaskAccumulators &amp;&amp; 
subtaskAccumulators.length &gt; 0"><a ng-click="toggleFold()" class="btn 
btn-default">
+      Hide subtasks
+       <i class="fa fa-chevron-up"></i></a>
+    <table class="table table-hover table-clickable table-activable 
table-inner">
+      <thead>
+        <tr>
+          <th>Name</th>
+          <th>Type</th>
+          <th>Value</th>
+        </tr>
+      </thead>
+      <tbody ng-if="subtask['user-accumulators'] &amp;&amp; 
subtask['user-accumulators'].length &gt; 0" ng-repeat="subtask in 
subtaskAccumulators">
+        <tr>
+          <td colwidth="3">
+            <div class="small-label">({{ subtask.subtask }}) {{ subtask.host 
}}, attempt: {{ subtask.attempt + 1 }}</div>
+          </td>
+        </tr>
+        <tr ng-repeat="accumulator in subtask['user-accumulators']">
+          <td width="30%">{{ accumulator.name }}</td>
+          <td width="30%">{{ accumulator.type }}</td>
+          <td width="30%">{{ accumulator.value }}</td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html
 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html
new file mode 100644
index 0000000..40b16bc
--- /dev/null
+++ 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.plan.node.subtasks.html
@@ -0,0 +1,52 @@
+
+<!--
+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.
+
+
+-->
+<table ng-if="subtasks" class="table table-hover table-clickable 
table-activable table-inner">
+  <thead>
+    <tr>
+      <th>Start Time</th>
+      <th>End Time</th>
+      <th>Duration</th>
+      <th>Bytes read</th>
+      <th>Records read</th>
+      <th>Bytes written</th>
+      <th>Records written</th>
+      <th>Attempt</th>
+      <th>Host</th>
+      <th>Status</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr ng-repeat="subtask in subtasks">
+      <td><span ng-if="subtask['start-time'] &gt; -1">{{ subtask['start-time'] 
| amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td>
+      <td><span ng-if="subtask['end-time'] &gt; -1">{{ subtask['end-time'] | 
amDateFormat:'YYYY-MM-DD, H:mm:ss' }}</span></td>
+      <td><span ng-if="subtask.duration &gt; -1">{{ subtask.duration }} 
ms</span></td>
+      <td><span ng-if="subtask.metrics['read-bytes'] &gt; -1">{{ 
subtask.metrics['read-bytes'] }}</span></td>
+      <td><span ng-if="subtask.metrics['read-records'] &gt; -1">{{ 
subtask.metrics['read-records'] }}</span></td>
+      <td><span ng-if="subtask.metrics['write-bytes'] &gt; -1">{{ 
subtask.metrics['write-bytes'] }}</span></td>
+      <td><span ng-if="subtask.metrics['write-records'] &gt; -1">{{ 
subtask.metrics['write-records'] }}</span></td>
+      <td>{{ subtask.attempt + 1 }}</td>
+      <td>{{ subtask.host }}</td>
+      <td> 
+        <bs-label status="{{subtask.status}}">{{subtask.status}}</bs-label>
+      </td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html
new file mode 100644
index 0000000..907afd3
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.properties.html
@@ -0,0 +1,140 @@
+
+<!--
+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.
+
+-->
+<div class="canvas-wrapper">
+  <div job-plan="job-plan" plan="plan" jobid="{{jobid}}" 
set-node="changeNode(nodeid)" class="main-canvas"></div>
+</div>
+<div ng-if="node" class="panel panel-default">
+  <div class="panel-heading clearfix">
+    <div class="panel-title">{{ node.description | humanizeText }}</div>
+  </div>
+  <div class="panel-body clean">
+    <div class="row">
+      <div class="col-sm-6 col-md-4">
+        <table ng-if="node.optimizer_properties.global_properties" 
class="table table-properties">
+          <thead>
+            <tr>
+              <th colspan="2">Global Data Properties</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr ng-repeat="property in 
node.optimizer_properties.global_properties">
+              <td>{{property.name}}</td>
+              <td table-property="table-property" value="property.value"></td>
+            </tr>
+          </tbody>
+        </table>
+        <table ng-if="node.optimizer_properties.local_properties" class="table 
table-properties">
+          <thead>
+            <tr>
+              <th colspan="2">Local Data Properties</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr ng-repeat="property in 
node.optimizer_properties.local_properties">
+              <td>{{property.name}}</td>
+              <td table-property="table-property" value="property.value"></td>
+            </tr>
+          </tbody>
+        </table>
+        <div class="visible-xs visible-sm">
+          <table class="table table-properties">
+            <thead>
+              <tr>
+                <th colspan="2">Properties</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr>
+                <td>Operator</td>
+                <td table-property="table-property" 
value="node.operator_strategy"></td>
+              </tr>
+              <tr>
+                <td>Parallelism</td>
+                <td table-property="table-property" 
value="node.parallelism"></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+      <div class="hidden-sm col-md-4">
+        <table class="table table-properties">
+          <thead>
+            <tr>
+              <th colspan="2">Properties</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td>Operator</td>
+              <td table-property="table-property" 
value="node.operator_strategy"></td>
+            </tr>
+            <tr>
+              <td>Parallelism</td>
+              <td table-property="table-property" 
value="node.parallelism"></td>
+            </tr>
+          </tbody>
+        </table>
+        <table ng-if="node.optimizer_properties.estimates" class="table 
table-properties">
+          <thead>
+            <tr>
+              <th colspan="2">Size Estimates</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr ng-repeat="property in node.optimizer_properties.estimates">
+              <td>{{property.name}}</td>
+              <td table-property="table-property" value="property.value"></td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+      <div class="col-sm-6 col-md-4">
+        <div class="visible-xs visible-sm">
+          <table ng-if="node.optimizer_properties.estimates" class="table 
table-properties">
+            <thead>
+              <tr>
+                <th colspan="2">Size Estimates</th>
+              </tr>
+            </thead>
+            <tbody>
+              <tr ng-repeat="property in node.optimizer_properties.estimates">
+                <td>{{property.name}}</td>
+                <td table-property="table-property" 
value="property.value"></td>
+              </tr>
+            </tbody>
+          </table>
+        </div>
+        <table ng-if="node.optimizer_properties.costs" class="table 
table-properties">
+          <thead>
+            <tr>
+              <th colspan="2">Cost Estimates</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr ng-repeat="property in node.optimizer_properties.costs">
+              <td>{{property.name}}</td>
+              <td table-property="table-property" value="property.value"></td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html
new file mode 100644
index 0000000..951cc1c
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.statistics.html
@@ -0,0 +1,40 @@
+
+<!--
+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.
+
+-->
+<table class="table table-properties">
+  <thead>
+    <tr>
+      <th colspan="2">Some statistics</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td>Operator</td>
+      <td>1</td>
+    </tr>
+    <tr>
+      <td>Parallelism</td>
+      <td>2</td>
+    </tr>
+    <tr>
+      <td>Subtasks-per-instance</td>
+      <td>3</td>
+    </tr>
+  </tbody>
+</table>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html
new file mode 100644
index 0000000..2f22576
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.html
@@ -0,0 +1,23 @@
+
+<!--
+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.
+
+-->
+<div class="canvas-wrapper">
+  <div timeline="timeline" vertices="vertices" jobid="jobid" 
class="timeline-canvas"></div>
+</div>
+<div ui-view="vertex"></div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html
 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html
new file mode 100644
index 0000000..1a4bd06
--- /dev/null
+++ 
b/flink-runtime-web/src/main/resources/web/partials/jobs/job.timeline.vertex.html
@@ -0,0 +1,30 @@
+
+<!--
+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.
+
+-->
+<div ng-if="vertex" class="panel panel-default panel-multi">
+  <div class="panel-heading clearfix">
+    <div class="panel-title">{{ vertex.groupvertex.groupvertexname | 
humanizeText }}</div>
+  </div>
+  <div class="panel-body">
+    <div class="canvas-wrapper">
+      <div vertex="vertex" data="vertex" class="timeline-canvas"></div>
+    </div>
+    <div id="timeline1"></div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html 
b/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html
new file mode 100644
index 0000000..e175d07
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/jobs/running-jobs.html
@@ -0,0 +1,53 @@
+
+<!--
+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.
+
+-->
+<nav class="navbar navbar-default navbar-fixed-top navbar-main">
+  <div id="fold-button" ng-click="showSidebar()" class="btn btn-default 
navbar-btn pull-left"><i class="fa fa-navicon"></i></div>
+  <div class="navbar-title">Running Jobs</div>
+</nav>
+<div id="content-inner">
+  <table class="table table-hover table-clickable">
+    <thead>
+      <tr>
+        <th>Start Time</th>
+        <th>End Time</th>
+        <th>Duration</th>
+        <th>Job Name</th>
+        <th>Job ID</th>
+        <th>Tasks</th>
+        <th>Status</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr ng-repeat="job in jobs" ui-sref="single-job.plan.overview({ jobid: 
job.jid })">
+        <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+        <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+        <td>{{job.duration}} ms</td>
+        <td>{{job.name}}</td>
+        <td>{{job.jid}}</td>
+        <td class="label-group">
+          <bs-label status="{{status}}" ng-repeat="(status, value) in 
job.tasks">{{value}}</bs-label>
+        </td>
+        <td> 
+          <bs-label status="{{job.state}}">{{job.state}}</bs-label>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/main/resources/web/partials/overview.html
----------------------------------------------------------------------
diff --git a/flink-runtime-web/src/main/resources/web/partials/overview.html 
b/flink-runtime-web/src/main/resources/web/partials/overview.html
new file mode 100644
index 0000000..ec3c580
--- /dev/null
+++ b/flink-runtime-web/src/main/resources/web/partials/overview.html
@@ -0,0 +1,147 @@
+
+<!--
+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.
+
+-->
+<nav class="navbar navbar-default navbar-fixed-top navbar-main">
+  <div id="fold-button" ng-click="showSidebar()" class="btn btn-default 
navbar-btn pull-left"><i class="fa fa-navicon"></i></div>
+  <div class="navbar-title">Overview</div>
+</nav>
+<div id="content-inner">
+  <div class="row">
+    <div class="col-md-6">
+      <div class="panel panel-default panel-dashboard">
+        <div class="panel-heading">
+          <div class="row">
+            <div class="col-xs-3"><i class="fa fa-tasks fa-3x"></i></div>
+            <div class="col-xs-9 text-right">
+              <div class="huge">{{overview.taskmanagers}}</div>
+              <div>Task Managers</div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-heading">
+          <div class="row">
+            <div class="col-xs-3"><i class="fa fa-folder fa-3x"></i></div>
+            <div class="col-xs-9 text-right">
+              <div class="huge">{{overview["slots-total"]}}</div>
+              <div>Task Slots</div>
+            </div>
+          </div>
+        </div>
+        <div class="panel-heading">
+          <div class="row">
+            <div class="col-xs-3"><i class="fa fa-folder-o fa-3x"></i></div>
+            <div class="col-xs-9 text-right">
+              <div class="huge">{{overview["slots-available"]}}</div>
+              <div>Available Task Slots</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="col-md-6">
+      <div class="panel panel-default panel-lg">
+        <div class="panel-heading">Total Jobs</div>
+        <div class="list-group">
+          <div class="list-group-item">
+            <div class="badge 
badge-primary">{{overview["jobs-running"]}}</div>Running
+          </div>
+          <div class="list-group-item">
+            <div class="badge 
badge-success">{{overview["jobs-finished"]}}</div>Finished
+          </div>
+          <div class="list-group-item">
+            <div class="badge 
badge-info">{{overview["jobs-cancelled"]}}</div>Canceled
+          </div>
+          <div class="list-group-item">
+            <div class="badge 
badge-danger">{{overview["jobs-failed"]}}</div>Failed
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Running Jobs</h3>
+    </div>
+    <div class="panel-body">
+      <table class="table table-hover table-clickable">
+        <thead>
+          <tr>
+            <th>Start Time</th>
+            <th>End Time</th>
+            <th>Duration</th>
+            <th>Job Name</th>
+            <th>Job ID</th>
+            <th>Tasks</th>
+            <th>Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr ng-repeat="job in runningJobs" 
ui-sref="single-job.plan.overview({ jobid: job.jid })">
+            <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+            <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+            <td>{{job.duration}} ms</td>
+            <td>{{job.name}}</td>
+            <td>{{job.jid}}</td>
+            <td class="label-group">
+              <bs-label status="{{status}}" ng-repeat="(status, value) in 
job.tasks">{{value}}</bs-label>
+            </td>
+            <td> 
+              <bs-label status="{{job.state}}">{{job.state}}</bs-label>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+  <div class="panel panel-default">
+    <div class="panel-heading">
+      <h3 class="panel-title">Completed Jobs</h3>
+    </div>
+    <div class="panel-body">
+      <table class="table table-hover table-clickable">
+        <thead>
+          <tr>
+            <th>Start Time</th>
+            <th>End Time</th>
+            <th>Duration</th>
+            <th>Job Name</th>
+            <th>Job ID</th>
+            <th>Tasks</th>
+            <th>Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr ng-repeat="job in finishedJobs" 
ui-sref="single-job.plan.overview({ jobid: job.jid })">
+            <td>{{job['start-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+            <td>{{job['end-time'] | amDateFormat:'YYYY-MM-DD, H:mm:ss'}}</td>
+            <td>{{job.duration}} ms</td>
+            <td>{{job.name}}</td>
+            <td>{{job.jid}}</td>
+            <td class="label-group">
+              <bs-label status="{{status}}" ng-repeat="(status, value) in 
job.tasks">{{value}}</bs-label>
+            </td>
+            <td> 
+              <bs-label status="{{job.state}}">{{job.state}}</bs-label>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java
 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java
new file mode 100644
index 0000000..26f66b0
--- /dev/null
+++ 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/WebRuntimeMonitorITCase.java
@@ -0,0 +1,329 @@
+/*
+ * 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.
+ */
+
+package org.apache.flink.runtime.webmonitor;
+
+import akka.actor.ActorRef;
+import akka.actor.ActorSystem;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.apache.curator.test.TestingServer;
+import org.apache.flink.configuration.ConfigConstants;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.runtime.StreamingMode;
+import org.apache.flink.runtime.akka.AkkaUtils;
+import org.apache.flink.runtime.jobmanager.JobManager;
+import org.apache.flink.runtime.leaderelection.TestingListener;
+import org.apache.flink.runtime.leaderretrieval.LeaderRetrievalService;
+import org.apache.flink.runtime.testingUtils.TestingCluster;
+import org.apache.flink.runtime.util.ZooKeeperUtils;
+import org.apache.flink.runtime.webmonitor.files.MimeTypes;
+import org.apache.flink.runtime.webmonitor.testutils.HttpTestClient;
+import org.junit.Test;
+import org.powermock.reflect.Whitebox;
+import scala.Some;
+import scala.Tuple2;
+import scala.concurrent.duration.Deadline;
+import scala.concurrent.duration.FiniteDuration;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+public class WebRuntimeMonitorITCase {
+
+       private final static FiniteDuration TestTimeout = new FiniteDuration(2, 
TimeUnit.MINUTES);
+
+       private final String MAIN_RESOURCES_PATH = 
getClass().getResource("/../classes/web").getPath();
+
+       /**
+        * Tests operation of the monitor in standalone operation.
+        */
+       @Test
+       public void testStandaloneWebRuntimeMonitor() throws Exception {
+               final Deadline deadline = TestTimeout.fromNow();
+
+               TestingCluster flink = null;
+               WebRuntimeMonitor webMonitor = null;
+
+               try {
+                       // Flink w/o a web monitor
+                       flink = new TestingCluster(new Configuration());
+                       flink.start(true);
+
+                       ActorSystem jmActorSystem = 
flink.jobManagerActorSystems().get().head();
+                       ActorRef jmActor = 
flink.jobManagerActors().get().head();
+
+                       Configuration monitorConfig = new Configuration();
+                       
monitorConfig.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, 
MAIN_RESOURCES_PATH);
+                       
monitorConfig.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, 
true);
+
+                       // Needs to match the leader address from the leader 
retrieval service
+                       String jobManagerAddress = 
AkkaUtils.getAkkaURL(jmActorSystem, jmActor);
+
+                       webMonitor = new WebRuntimeMonitor(monitorConfig, 
flink.createLeaderRetrievalService(),
+                                       jmActorSystem);
+
+                       webMonitor.start(jobManagerAddress);
+
+                       try (HttpTestClient client = new 
HttpTestClient("localhost", webMonitor.getServerPort())) {
+                               String expected = new Scanner(new 
File(MAIN_RESOURCES_PATH + "/index.html"))
+                                               .useDelimiter("\\A").next();
+
+                               // Request the file from the web server
+                               client.sendGetRequest("index.html", 
deadline.timeLeft());
+
+                               HttpTestClient.SimpleHttpResponse response = 
client.getNextResponse(deadline.timeLeft());
+
+                               assertEquals(HttpResponseStatus.OK, 
response.getStatus());
+                               assertEquals(response.getType(), 
MimeTypes.getMimeTypeForExtension("html"));
+                               assertEquals(expected, response.getContent());
+
+                               // Simple overview request
+                               client.sendGetRequest("/overview", 
deadline.timeLeft());
+
+                               response = 
client.getNextResponse(deadline.timeLeft());
+                               assertEquals(HttpResponseStatus.OK, 
response.getStatus());
+                               assertEquals(response.getType(), 
MimeTypes.getMimeTypeForExtension("json"));
+                               
assertTrue(response.getContent().contains("\"taskmanagers\":1"));
+                       }
+               }
+               finally {
+                       if (flink != null) {
+                               flink.shutdown();
+                       }
+
+                       if (webMonitor != null) {
+                               webMonitor.stop();
+                       }
+               }
+       }
+
+       /**
+        * Tests that the monitor associated with the following job manager 
redirects to the leader.
+        */
+       @Test
+       public void testRedirectToLeader() throws Exception {
+               final Deadline deadline = TestTimeout.fromNow();
+
+               ActorSystem[] jobManagerSystem = new ActorSystem[2];
+               WebRuntimeMonitor[] webMonitor = new WebRuntimeMonitor[2];
+               List<LeaderRetrievalService> leaderRetrievalServices = new 
ArrayList<>();
+
+               try (TestingServer zooKeeper = new TestingServer()) {
+                       final Configuration config = new Configuration();
+                       
config.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, 
MAIN_RESOURCES_PATH);
+                       
config.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, true);
+                       
config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);
+                       config.setString(ConfigConstants.RECOVERY_MODE, 
"ZOOKEEPER");
+                       config.setString(ConfigConstants.ZOOKEEPER_QUORUM_KEY, 
zooKeeper.getConnectString());
+
+                       for (int i = 0; i < jobManagerSystem.length; i++) {
+                               jobManagerSystem[i] = 
AkkaUtils.createActorSystem(new Configuration(),
+                                               new Some<>(new Tuple2<String, 
Object>("localhost", 0)));
+                       }
+
+                       for (int i = 0; i < webMonitor.length; i++) {
+                               LeaderRetrievalService lrs = 
ZooKeeperUtils.createLeaderRetrievalService(config);
+                               leaderRetrievalServices.add(lrs);
+                               webMonitor[i] = new WebRuntimeMonitor(config, 
lrs, jobManagerSystem[i]);
+                       }
+
+                       ActorRef[] jobManager = new ActorRef[2];
+                       String[] jobManagerAddress = new String[2];
+                       for (int i = 0; i < jobManager.length; i++) {
+                               Configuration jmConfig = config.clone();
+                               
jmConfig.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY,
+                                               webMonitor[i].getServerPort());
+
+                               jobManager[i] = 
JobManager.startJobManagerActors(
+                                               jmConfig, jobManagerSystem[i], 
StreamingMode.STREAMING)._1();
+
+                               jobManagerAddress[i] = 
AkkaUtils.getAkkaURL(jobManagerSystem[i], jobManager[i]);
+                               webMonitor[i].start(jobManagerAddress[i]);
+                       }
+
+                       LeaderRetrievalService lrs = 
ZooKeeperUtils.createLeaderRetrievalService(config);
+                       leaderRetrievalServices.add(lrs);
+                       TestingListener leaderListener = new TestingListener();
+                       lrs.start(leaderListener);
+
+                       
leaderListener.waitForNewLeader(deadline.timeLeft().toMillis());
+
+                       String leaderAddress = leaderListener.getAddress();
+
+                       int leaderIndex = 
leaderAddress.equals(jobManagerAddress[0]) ? 0 : 1;
+                       int followerIndex = (leaderIndex + 1) % 2;
+
+                       ActorSystem leadingSystem = 
jobManagerSystem[leaderIndex];
+                       ActorSystem followerSystem = 
jobManagerSystem[followerIndex];
+
+                       WebMonitor leadingWebMonitor = webMonitor[leaderIndex];
+                       WebMonitor followerWebMonitor = 
webMonitor[followerIndex];
+
+                       // For test stability reason we have to wait until we 
are sure that both leader
+                       // listeners have been notified.
+                       JobManagerRetriever leadingRetriever = Whitebox
+                                       .getInternalState(leadingWebMonitor, 
"retriever");
+
+                       JobManagerRetriever followerRetriever = Whitebox
+                                       .getInternalState(followerWebMonitor, 
"retriever");
+
+                       // Wait for the initial notifications
+                       waitForLeaderNotification(leadingSystem, 
jobManager[leaderIndex], leadingRetriever, deadline);
+                       waitForLeaderNotification(leadingSystem, 
jobManager[leaderIndex], followerRetriever, deadline);
+
+                       try (
+                                       HttpTestClient leaderClient = new 
HttpTestClient(
+                                                       "localhost", 
leadingWebMonitor.getServerPort());
+
+                                       HttpTestClient followingClient = new 
HttpTestClient(
+                                                       "localhost", 
followerWebMonitor.getServerPort())) {
+
+                               String expected = new Scanner(new 
File(MAIN_RESOURCES_PATH + "/index.html"))
+                                               .useDelimiter("\\A").next();
+
+                               // Request the file from the leaading web server
+                               leaderClient.sendGetRequest("index.html", 
deadline.timeLeft());
+
+                               HttpTestClient.SimpleHttpResponse response = 
leaderClient.getNextResponse(deadline.timeLeft());
+                               assertEquals(HttpResponseStatus.OK, 
response.getStatus());
+                               assertEquals(response.getType(), 
MimeTypes.getMimeTypeForExtension("html"));
+                               assertEquals(expected, response.getContent());
+
+                               // Request the file from the following web 
server
+                               followingClient.sendGetRequest("index.html", 
deadline.timeLeft());
+                               response = 
followingClient.getNextResponse(deadline.timeLeft());
+                               
assertEquals(HttpResponseStatus.TEMPORARY_REDIRECT, response.getStatus());
+                               assertTrue(response.getLocation().contains("" + 
leadingWebMonitor.getServerPort()));
+
+                               // Kill the leader
+                               leadingSystem.shutdown();
+
+                               // Wait for the notification of the follower
+                               waitForLeaderNotification(followerSystem, 
jobManager[followerIndex], followerRetriever, deadline);
+
+                               // Same request to the new leader
+                               followingClient.sendGetRequest("index.html", 
deadline.timeLeft());
+
+                               response = 
followingClient.getNextResponse(deadline.timeLeft());
+                               assertEquals(HttpResponseStatus.OK, 
response.getStatus());
+                               assertEquals(response.getType(), 
MimeTypes.getMimeTypeForExtension("html"));
+                               assertEquals(expected, response.getContent());
+
+                               // Simple overview request
+                               followingClient.sendGetRequest("/overview", 
deadline.timeLeft());
+
+                               response = 
followingClient.getNextResponse(deadline.timeLeft());
+                               assertEquals(HttpResponseStatus.OK, 
response.getStatus());
+                               assertEquals(response.getType(), 
MimeTypes.getMimeTypeForExtension("json"));
+                               
assertTrue(response.getContent().contains("\"taskmanagers\":1") ||
+                                               
response.getContent().contains("\"taskmanagers\":0"));
+                       }
+               }
+               finally {
+                       for (ActorSystem system : jobManagerSystem) {
+                               if (system != null) {
+                                       system.shutdown();
+                               }
+                       }
+
+                       for (WebMonitor monitor : webMonitor) {
+                               monitor.stop();
+                       }
+
+                       for (LeaderRetrievalService lrs : 
leaderRetrievalServices) {
+                               lrs.stop();
+                       }
+               }
+       }
+
+       @Test
+       public void testLeaderNotAvailable() throws Exception {
+               final Deadline deadline = TestTimeout.fromNow();
+
+               ActorSystem actorSystem = null;
+               WebRuntimeMonitor webRuntimeMonitor = null;
+
+               try (TestingServer zooKeeper = new TestingServer()) {
+
+                       final Configuration config = new Configuration();
+                       
config.setString(WebMonitorConfig.JOB_MANAGER_WEB_DOC_ROOT_KEY, 
MAIN_RESOURCES_PATH);
+                       
config.setBoolean(ConfigConstants.JOB_MANAGER_NEW_WEB_FRONTEND_KEY, true);
+                       
config.setInteger(ConfigConstants.JOB_MANAGER_WEB_PORT_KEY, 0);
+                       config.setString(ConfigConstants.RECOVERY_MODE, 
"ZOOKEEPER");
+                       config.setString(ConfigConstants.ZOOKEEPER_QUORUM_KEY, 
zooKeeper.getConnectString());
+
+                       actorSystem = AkkaUtils.createDefaultActorSystem();
+
+                       LeaderRetrievalService leaderRetrievalService = 
mock(LeaderRetrievalService.class);
+                       webRuntimeMonitor = new WebRuntimeMonitor(
+                                       config, leaderRetrievalService, 
actorSystem);
+
+                       webRuntimeMonitor.start("akka://schmakka");
+
+                       try (HttpTestClient client = new HttpTestClient(
+                                       "localhost", 
webRuntimeMonitor.getServerPort())) {
+
+                               client.sendGetRequest("index.html", 
deadline.timeLeft());
+
+                               HttpTestClient.SimpleHttpResponse response = 
client.getNextResponse();
+
+                               
assertEquals(HttpResponseStatus.SERVICE_UNAVAILABLE, response.getStatus());
+                               
assertEquals(MimeTypes.getMimeTypeForExtension("txt"), response.getType());
+                               
assertTrue(response.getContent().contains("refresh"));
+                       }
+               }
+               finally {
+                       if (actorSystem != null) {
+                               actorSystem.shutdown();
+                       }
+
+                       if (webRuntimeMonitor != null) {
+                               webRuntimeMonitor.stop();
+                       }
+               }
+       }
+
+       // 
------------------------------------------------------------------------
+
+       private void waitForLeaderNotification(
+                       ActorSystem system,
+                       ActorRef expectedLeader,
+                       JobManagerRetriever retriever,
+                       Deadline deadline) throws Exception {
+
+               String expectedJobManagerUrl = AkkaUtils.getAkkaURL(system, 
expectedLeader);
+
+               while (deadline.hasTimeLeft()) {
+                       ActorRef leaderRef = 
retriever.awaitJobManagerGatewayAndWebPort()._1().actor();
+
+                       if (AkkaUtils.getAkkaURL(system, 
leaderRef).equals(expectedJobManagerUrl)) {
+                               return;
+                       }
+                       else {
+                               Thread.sleep(100);
+                       }
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java
----------------------------------------------------------------------
diff --git 
a/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java
 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java
new file mode 100644
index 0000000..d7d4457
--- /dev/null
+++ 
b/flink-runtime-web/src/test/java/org/apache/flink/runtime/webmonitor/testutils/HttpTestClient.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.runtime.webmonitor.testutils;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpContent;
+import io.netty.handler.codec.http.HttpContentDecompressor;
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpObject;
+import io.netty.handler.codec.http.HttpRequest;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.LastHttpContent;
+import io.netty.util.CharsetUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.concurrent.duration.FiniteDuration;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A simple HTTP client.
+ *
+ * <pre>
+ * HttpTestClient client = new HttpTestClient("localhost", 8081);
+ * client.sendGetRequest("/overview", timeout);
+ * SimpleHttpResponse response = client.getNextResponse(timeout);
+ *
+ * assertEquals(200, response.getStatus().code()); // OK
+ * assertEquals("application/json", response.getType());
+ * assertTrue(response.getContent().contains("\"jobs-running\":0"));
+ * </pre>
+ *
+ * This code is based on Netty's HttpSnoopClient.
+ *
+ * @see <a 
href="https://github.com/netty/netty/blob/master/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java";>HttpSnoopClient</a>
+ */
+public class HttpTestClient implements AutoCloseable {
+
+       private static final Logger LOG = 
LoggerFactory.getLogger(HttpTestClient.class);
+
+       /** Target host */
+       private final String host;
+
+       /** Target port */
+       private final int port;
+
+       /** Netty's thread group for the client */
+       private final EventLoopGroup group;
+
+       /** Client bootstrap */
+       private final Bootstrap bootstrap;
+
+       /** Responses received by the client */
+       private final BlockingQueue<SimpleHttpResponse> responses = new 
LinkedBlockingQueue<>();
+
+       /**
+        * Creates a client instance for the server at the target host and port.
+        *
+        * @param host Host of the HTTP server
+        * @param port Port of the HTTP server
+        */
+       public HttpTestClient(String host, int port) {
+               this.host = host;
+               this.port = port;
+
+               this.group = new NioEventLoopGroup();
+
+               this.bootstrap = new Bootstrap();
+               this.bootstrap.group(group)
+                               .channel(NioSocketChannel.class)
+                               .handler(new 
ChannelInitializer<SocketChannel>() {
+
+                                       @Override
+                                       protected void 
initChannel(SocketChannel ch) throws Exception {
+                                               ChannelPipeline p = 
ch.pipeline();
+                                               p.addLast(new 
HttpClientCodec());
+                                               p.addLast(new 
HttpContentDecompressor());
+                                               p.addLast(new 
ClientHandler(responses));
+                                       }
+                               });
+       }
+
+       /**
+        * Sends a request to to the server.
+        *
+        * <pre>
+        * HttpRequest request = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/overview");
+        * request.headers().set(HttpHeaders.Names.HOST, host);
+        * request.headers().set(HttpHeaders.Names.CONNECTION, 
HttpHeaders.Values.CLOSE);
+        *
+        * sendRequest(request);
+        * </pre>
+        *
+        * @param request The {@link HttpRequest} to send to the server
+        */
+       public void sendRequest(HttpRequest request, FiniteDuration timeout) 
throws InterruptedException, TimeoutException {
+               LOG.debug("Writing {}.", request);
+
+               // Make the connection attempt.
+               ChannelFuture connect = bootstrap.connect(host, port);
+
+               Channel channel;
+               if (connect.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
+                       channel = connect.channel();
+               }
+               else {
+                       throw new TimeoutException("Connection failed");
+               }
+
+               channel.writeAndFlush(request);
+       }
+
+       /**
+        * Sends a simple GET request to the given path. You only specify the 
$path part of
+        * http://$host:$host/$path.
+        *
+        * @param path The $path to GET (http://$host:$host/$path)
+        */
+       public void sendGetRequest(String path, FiniteDuration timeout) throws 
TimeoutException, InterruptedException {
+               if (!path.startsWith("/")) {
+                       path = "/" + path;
+               }
+
+               HttpRequest getRequest = new 
DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
+                               HttpMethod.GET, path);
+               getRequest.headers().set(HttpHeaders.Names.HOST, host);
+               getRequest.headers().set(HttpHeaders.Names.CONNECTION, 
HttpHeaders.Values.CLOSE);
+
+               sendRequest(getRequest, timeout);
+       }
+
+       /**
+        * Returns the next available HTTP response. A call to this method 
blocks until a response
+        * becomes available.
+        *
+        * @return The next available {@link SimpleHttpResponse}
+        */
+       public SimpleHttpResponse getNextResponse() throws InterruptedException 
{
+               return responses.take();
+       }
+
+       /**
+        * Returns the next available HTTP response . A call to this method 
blocks until a response
+        * becomes available or throws an Exception if the timeout fires.
+        *
+        * @param timeout Timeout in milliseconds for the next response to 
become available
+        * @return The next available {@link SimpleHttpResponse}
+        */
+       public SimpleHttpResponse getNextResponse(FiniteDuration timeout) 
throws InterruptedException,
+                       TimeoutException {
+
+               SimpleHttpResponse response = 
responses.poll(timeout.toMillis(), TimeUnit.MILLISECONDS);
+
+               if (response == null) {
+                       throw new TimeoutException("No response within timeout 
of " + timeout + " ms");
+               }
+               else {
+                       return response;
+               }
+       }
+
+       /**
+        * Closes the client.
+        */
+       @Override
+       public void close() throws InterruptedException {
+               if (group != null) {
+                       group.shutdownGracefully();
+               }
+
+               LOG.debug("Closed");
+       }
+
+       /**
+        * A simple HTTP response.
+        */
+       public static class SimpleHttpResponse {
+
+               private final HttpResponseStatus status;
+
+               private final String type;
+
+               private final String content;
+
+               private final String location;
+
+               public SimpleHttpResponse(HttpResponseStatus status, String 
type, String content, String location) {
+                       this.status = status;
+                       this.type = type;
+                       this.content = content;
+                       this.location = location;
+               }
+
+               public HttpResponseStatus getStatus() {
+                       return status;
+               }
+
+               public String getType() {
+                       return type;
+               }
+
+               public final String getLocation() {
+                       return location;
+               }
+
+               public String getContent() {
+                       return content;
+               }
+
+               @Override
+               public String toString() {
+                       return "HttpResponse(status=" + status + ", type='" + 
type + "'" + ", content='" +
+                                       content + "')";
+               }
+       }
+
+       /**
+        * The response handler. Responses from the server are handled here.
+        */
+       @ChannelHandler.Sharable
+       private static class ClientHandler extends 
SimpleChannelInboundHandler<HttpObject> {
+
+               private final BlockingQueue<SimpleHttpResponse> responses;
+
+               private HttpResponseStatus currentStatus;
+
+               private String currentType;
+
+               private String currentLocation;
+
+               private String currentContent = "";
+
+               public ClientHandler(BlockingQueue<SimpleHttpResponse> 
responses) {
+                       this.responses = responses;
+               }
+
+               @Override
+               protected void channelRead0(ChannelHandlerContext ctx, 
HttpObject msg) throws Exception {
+                       LOG.debug("Received {}", msg);
+
+                       if (msg instanceof HttpResponse) {
+                               HttpResponse response = (HttpResponse) msg;
+
+                               currentStatus = response.getStatus();
+                               currentType = 
response.headers().get(HttpHeaders.Names.CONTENT_TYPE);
+                               currentLocation = 
response.headers().get(HttpHeaders.Names.LOCATION);
+
+                               if 
(HttpHeaders.isTransferEncodingChunked(response)) {
+                                       LOG.debug("Content is chunked");
+                               }
+                       }
+
+                       if (msg instanceof HttpContent) {
+                               HttpContent content = (HttpContent) msg;
+
+                               // Add the content
+                               currentContent += 
content.content().toString(CharsetUtil.UTF_8);
+
+                               // Finished with this
+                               if (content instanceof LastHttpContent) {
+                                       responses.add(new 
SimpleHttpResponse(currentStatus, currentType,
+                                                       currentContent, 
currentLocation));
+
+                                       currentStatus = null;
+                                       currentType = null;
+                                       currentLocation = null;
+                                       currentContent = "";
+
+                                       ctx.close();
+                               }
+                       }
+               }
+       }
+}

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/web-dashboard/gulpfile.js
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/gulpfile.js 
b/flink-runtime-web/web-dashboard/gulpfile.js
index 737519e..2abfcce 100644
--- a/flink-runtime-web/web-dashboard/gulpfile.js
+++ b/flink-runtime-web/web-dashboard/gulpfile.js
@@ -43,7 +43,7 @@ var path = require('path');
 var environment = 'development';
 var paths = {
   src: './app/',
-  dest: './web/',
+  dest: '../src/main/resources/web/',
   vendor: './bower_components/',
   vendorLocal: './vendor-local/',
   assets: './assets/',
@@ -168,7 +168,7 @@ gulp.task('watch', function () {
 });
 
 gulp.task('serve', serve({
-  root: 'web',
+  root: '../src/main/resources/web/',
   port: 3001
 }));
 

http://git-wip-us.apache.org/repos/asf/flink/blob/77fc0cc4/flink-runtime-web/web-dashboard/web/css/index.css
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/css/index.css 
b/flink-runtime-web/web-dashboard/web/css/index.css
deleted file mode 100644
index 43f7d98..0000000
--- a/flink-runtime-web/web-dashboard/web/css/index.css
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * 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.
- */
-#sidebar {
-  overflow: hidden;
-  position: fixed;
-  left: -250px;
-  top: 0;
-  bottom: 0;
-  height: 100%;
-  width: 250px;
-  background: #151515;
-  -webkit-transition: 400ms;
-  -moz-transition: 400ms;
-  -o-transition: 400ms;
-  -ms-transition: 400ms;
-  transition: 400ms;
-  -webkit-box-shadow: inset -10px 0px 10px rgba(0,0,0,0.2);
-  box-shadow: inset -10px 0px 10px rgba(0,0,0,0.2);
-}
-#sidebar.sidebar-visible {
-  left: 0;
-}
-#sidebar .logo {
-  width: auto;
-  height: 22px;
-}
-#sidebar .logo img {
-  display: inline-block;
-}
-#sidebar .navbar-static-top {
-  overflow: hidden;
-  height: 51px;
-}
-#sidebar .navbar-static-top .navbar-header {
-  width: 100%;
-}
-#sidebar .navbar-brand.navbar-brand-text {
-  font-size: 14px;
-  font-weight: bold;
-  color: #fff;
-  padding-left: 0;
-}
-#sidebar .nav > li > a {
-  color: #aaa;
-  margin-bottom: 1px;
-}
-#sidebar .nav > li > a:hover,
-#sidebar .nav > li > a:focus {
-  background-color: rgba(40,40,40,0.5);
-}
-#sidebar .nav > li > a.active {
-  background-color: rgba(100,100,100,0.5);
-}
-#content {
-  background-color: #fff;
-  overflow: hidden;
-  margin-left: 0;
-  padding-top: 70px;
-  -webkit-transition: 400ms;
-  -moz-transition: 400ms;
-  -o-transition: 400ms;
-  -ms-transition: 400ms;
-  transition: 400ms;
-}
-#content .navbar-main,
-#content .navbar-main-additional {
-  -webkit-transition: 400ms;
-  -moz-transition: 400ms;
-  -o-transition: 400ms;
-  -ms-transition: 400ms;
-  transition: 400ms;
-}
-#content .navbar-main-additional {
-  margin-top: 51px;
-  border-bottom: none;
-  padding: 0 20px;
-}
-#content .navbar-main-additional .nav-tabs {
-  margin: 0 -20px;
-  padding: 0 20px;
-}
-#content .navbar-secondary-additional {
-  border: none;
-  padding: 0 20px;
-  margin-bottom: 0;
-}
-#content .navbar-secondary-additional .nav-tabs {
-  margin: 0 -20px;
-}
-#content.sidebar-visible {
-  margin-left: 250px;
-}
-#content.sidebar-visible .navbar-main,
-#content.sidebar-visible .navbar-main-additional {
-  left: 250px;
-}
-#content #fold-button {
-  display: inline-block;
-  margin-left: 20px;
-}
-#content #content-inner {
-  padding: 0px 20px 20px 20px;
-}
-#content #content-inner.has-navbar-main-additional {
-  padding-top: 42px;
-}
-.page-header {
-  margin: 0 0 20px 0;
-}
-.nav > li > a,
-.nav > li > a:hover,
-.nav > li > a:focus {
-  color: #aaa;
-  background-color: transparent;
-  border-bottom: 2px solid transparent;
-}
-.nav > li.active > a,
-.nav > li.active > a:hover,
-.nav > li.active > a:focus {
-  color: #000;
-  border-bottom: 2px solid #000;
-}
-.nav.nav-tabs {
-  margin-bottom: 20px;
-}
-.table .table {
-  background-color: transparent;
-}
-.table th {
-  font-weight: normal;
-  color: #999;
-}
-.table td.td-long {
-  width: 20%;
-  white-space: pre-wrap;
-  white-space: -moz-pre-wrap;
-  white-space: -pre-wrap;
-  white-space: -o-pre-wrap;
-  word-wrap: break-word;
-}
-.table.table-clickable tr {
-  cursor: pointer;
-}
-.table.table-inner {
-  background-color: transparent;
-}
-.table.table-properties {
-  table-layout: fixed;
-  white-space: nowrap;
-}
-.table.table-properties td {
-  width: 50%;
-  white-space: nowrap;
-  overflow: hidden;
-  -o-text-overflow: ellipsis;
-  text-overflow: ellipsis;
-}
-.table.table-body-hover > tbody {
-  border-top: none;
-  border-left: 2px solid transparent;
-}
-.table.table-body-hover > tbody.active {
-  border-left: 2px solid #000;
-}
-.table.table-body-hover > tbody:hover td:not(.tab-column),
-.table.table-body-hover > tbody.active td:not(.tab-column) {
-  background-color: #f0f0f0;
-}
-.table.table-body-hover > tbody:hover td.tab-column li.active,
-.table.table-body-hover > tbody.active td.tab-column li.active {
-  background-color: #f0f0f0;
-}
-.table.table-activable th.tab-column,
-.table.table-activable td.tab-column {
-  border-top: none;
-  width: 47px;
-}
-.table.table-activable td.tab-column {
-  border-right: 1px solid #ddd;
-}
-.table.table-activable td {
-  position: relative;
-}
-.table .small-label {
-  text-transform: uppercase;
-  font-size: 13px;
-  color: #999;
-}
-.panel.panel-dashboard .huge {
-  font-size: 28px;
-}
-.panel.panel-lg {
-  font-size: 16px;
-}
-.panel.panel-lg .badge {
-  font-size: 14px;
-}
-.navbar-secondary {
-  overflow: auto;
-}
-.navbar-main .navbar-title,
-.navbar-secondary .navbar-title,
-.navbar-main-additional .navbar-title,
-.panel.panel-multi .navbar-title,
-.navbar-secondary-additional .navbar-title,
-.navbar-main .panel-title,
-.navbar-secondary .panel-title,
-.navbar-main-additional .panel-title,
-.panel.panel-multi .panel-title,
-.navbar-secondary-additional .panel-title {
-  float: left;
-  font-size: 18px;
-  padding: 12px 20px 13px 10px;
-  color: #333;
-  display: inline-block;
-}
-.navbar-main .navbar-info,
-.navbar-secondary .navbar-info,
-.navbar-main-additional .navbar-info,
-.panel.panel-multi .navbar-info,
-.navbar-secondary-additional .navbar-info,
-.navbar-main .panel-info,
-.navbar-secondary .panel-info,
-.navbar-main-additional .panel-info,
-.panel.panel-multi .panel-info,
-.navbar-secondary-additional .panel-info {
-  float: left;
-  font-size: 14px;
-  padding: 15px 15px 15px 15px;
-  color: #999;
-  display: inline-block;
-  border-right: 1px solid #e7e7e7;
-  overflow: hidden;
-}
-.navbar-main .navbar-info .overflow,
-.navbar-secondary .navbar-info .overflow,
-.navbar-main-additional .navbar-info .overflow,
-.panel.panel-multi .navbar-info .overflow,
-.navbar-secondary-additional .navbar-info .overflow,
-.navbar-main .panel-info .overflow,
-.navbar-secondary .panel-info .overflow,
-.navbar-main-additional .panel-info .overflow,
-.panel.panel-multi .panel-info .overflow,
-.navbar-secondary-additional .panel-info .overflow {
-  position: absolute;
-  display: block;
-  -o-text-overflow: ellipsis;
-  text-overflow: ellipsis;
-  overflow: hidden;
-  height: 22px;
-  line-height: 22px;
-  vertical-align: middle;
-}
-.navbar-main .navbar-info.first,
-.navbar-secondary .navbar-info.first,
-.navbar-main-additional .navbar-info.first,
-.panel.panel-multi .navbar-info.first,
-.navbar-secondary-additional .navbar-info.first,
-.navbar-main .panel-info.first,
-.navbar-secondary .panel-info.first,
-.navbar-main-additional .panel-info.first,
-.panel.panel-multi .panel-info.first,
-.navbar-secondary-additional .panel-info.first {
-  border-left: 1px solid #e7e7e7;
-}
-.navbar-main .navbar-info.last,
-.navbar-secondary .navbar-info.last,
-.navbar-main-additional .navbar-info.last,
-.panel.panel-multi .navbar-info.last,
-.navbar-secondary-additional .navbar-info.last,
-.navbar-main .panel-info.last,
-.navbar-secondary .panel-info.last,
-.navbar-main-additional .panel-info.last,
-.panel.panel-multi .panel-info.last,
-.navbar-secondary-additional .panel-info.last {
-  border-right: none;
-}
-.panel.panel-multi .panel-heading {
-  padding: 0;
-}
-.panel.panel-multi .panel-heading .panel-info.thin {
-  padding: 8px 10px;
-}
-.panel.panel-multi .panel-body {
-  padding: 10px;
-  background-color: #fdfdfd;
-  color: #999;
-  font-size: 13px;
-}
-.panel.panel-multi .panel-body.clean {
-  color: inherit;
-  font-size: inherit;
-}
-.navbar-main-additional,
-.navbar-secondary-additional {
-  min-height: 40px;
-  background-color: #fdfdfd;
-}
-.navbar-main-additional .navbar-info,
-.navbar-secondary-additional .navbar-info {
-  font-size: 13px;
-  padding: 10px 15px 10px 15px;
-}
-.nav-top-affix.affix {
-  width: 100%;
-  top: 50px;
-  margin-left: -20px;
-  padding-left: 20px;
-  margin-right: -20px;
-  padding-right: 20px;
-  background-color: #fff;
-  z-index: 1;
-}
-.badge-default[href]:hover,
-.badge-default[href]:focus {
-  background-color: #808080;
-}
-.badge-primary {
-  background-color: #428bca;
-}
-.badge-primary[href]:hover,
-.badge-primary[href]:focus {
-  background-color: #3071a9;
-}
-.badge-success {
-  background-color: #5cb85c;
-}
-.badge-success[href]:hover,
-.badge-success[href]:focus {
-  background-color: #449d44;
-}
-.badge-info {
-  background-color: #5bc0de;
-}
-.badge-info[href]:hover,
-.badge-info[href]:focus {
-  background-color: #31b0d5;
-}
-.badge-warning {
-  background-color: #f0ad4e;
-}
-.badge-warning[href]:hover,
-.badge-warning[href]:focus {
-  background-color: #ec971f;
-}
-.badge-danger {
-  background-color: #d9534f;
-}
-.badge-danger[href]:hover,
-.badge-danger[href]:focus {
-  background-color: #c9302c;
-}
-.indicator {
-  display: inline-block;
-  margin-right: 15px;
-}
-.indicator.indicator-primary {
-  color: #428bca;
-}
-.indicator.indicator-success {
-  color: #5cb85c;
-}
-.indicator.indicator-info {
-  color: #5bc0de;
-}
-.indicator.indicator-warning {
-  color: #f0ad4e;
-}
-.indicator.indicator-danger {
-  color: #d9534f;
-}
-pre.exception {
-  border: none;
-  background-color: transparent;
-  padding: 0;
-  margin: 0;
-}
-.nav-tabs.tabs-vertical {
-  position: absolute;
-  left: 0;
-  top: 0;
-  border-bottom: none;
-  z-index: 100;
-}
-.nav-tabs.tabs-vertical li {
-  float: none;
-  margin-bottom: 0;
-  margin-right: -1px;
-}
-.nav-tabs.tabs-vertical li > a {
-  margin-right: 0;
-  -webkit-border-radius: 0;
-  border-radius: 0;
-  border-bottom: none;
-  border-left: 2px solid transparent;
-}
-.nav-tabs.tabs-vertical li > a:hover,
-.nav-tabs.tabs-vertical li > a:focus {
-  border-bottom: none;
-  border-left: 2px solid #000;
-}
-.nav-tabs.tabs-vertical li.active > a {
-  border-bottom: none;
-  border-left: 2px solid #000;
-}
-.navbar-main .navbar-title,
-.navbar-secondary .navbar-title,
-.navbar-main-additional .navbar-title,
-.navbar-secondary-additional .navbar-title {
-  padding: 12px 20px 13px 20px;
-}
-livechart {
-  width: 30%;
-  height: 30%;
-  text-align: center;
-}
-.canvas-wrapper {
-  border: 1px solid #ddd;
-  position: relative;
-  margin-bottom: 20px;
-}
-.canvas-wrapper .main-canvas {
-  height: 400px;
-  overflow: hidden;
-}
-.canvas-wrapper .main-canvas .zoom-buttons {
-  position: absolute;
-  top: 10px;
-  right: 10px;
-}
-.label-group .label {
-  display: inline-block;
-  width: 2em;
-  padding-left: 0.1em;
-  padding-right: 0.1em;
-  margin: 0;
-  border-right: 1px solid #fff;
-  -webkit-border-radius: 0;
-  border-radius: 0;
-}
-.label-group .label.label-black {
-  background-color: #000;
-}
-svg.graph {
-  overflow: hidden;
-}
-svg.graph g.type-TK > rect {
-  fill: #00ffd0;
-}
-svg.graph text {
-  font-weight: 300;
-  font-size: 14px;
-}
-svg.graph .node {
-  cursor: pointer;
-}
-svg.graph .node > rect {
-  stroke: #999;
-  stroke-width: 5px;
-  fill: #fff;
-  margin: 0;
-  padding: 0;
-}
-svg.graph .node[active] > rect {
-  fill: #eee;
-}
-svg.graph .node.node-mirror > rect {
-  stroke: #a8a8a8;
-}
-svg.graph .node.node-iteration > rect {
-  stroke: #cd3333;
-}
-svg.graph .node.node-source > rect {
-  stroke: #4ce199;
-}
-svg.graph .node.node-sink > rect {
-  stroke: #e6ec8b;
-}
-svg.graph .node.node-normal > rect {
-  stroke: #3fb6d8;
-}
-svg.graph .node h4 {
-  color: #000;
-}
-svg.graph .node h5 {
-  color: #999;
-}
-svg.graph .edgeLabel rect {
-  fill: #fff;
-}
-svg.graph .edgePath path {
-  stroke: #333;
-  stroke-width: 2px;
-  fill: #333;
-}
-svg.graph .label {
-  color: #777;
-  margin: 0;
-}
-svg.graph .edge-label {
-  font-size: 14px;
-}
-svg.graph .node-label {
-  display: block;
-  margin: 0;
-  text-decoration: none;
-}
-.timeline {
-  overflow: hidden;
-}
-.timeline-canvas {
-  overflow: hidden;
-  padding: 10px;
-}
-.timeline-canvas .bar-container {
-  overflow: hidden;
-}
-.timeline-canvas .timeline-insidelabel,
-.timeline-canvas .timeline-series {
-  cursor: pointer;
-}
-.timeline-canvas.secondary .timeline-insidelabel,
-.timeline-canvas.secondary .timeline-series {
-  cursor: auto;
-}
-.qtip-timeline-bar {
-  font-size: 14px;
-  line-height: 1.4;
-}
-@media (min-width: 1024px) and (max-width: 1279px) {
-  #sidebar {
-    left: 0;
-    width: 160px;
-  }
-  #sidebar .navbar-static-top .navbar-brand-text {
-    display: none;
-  }
-  #content {
-    margin-left: 160px;
-  }
-  #content #fold-button {
-    display: none;
-  }
-  #content .navbar-main,
-  #content .navbar-main-additional {
-    left: 160px;
-  }
-  .table td.td-long {
-    width: 20%;
-  }
-}
-@media (min-width: 1280px) {
-  #sidebar {
-    left: 0;
-  }
-  #content {
-    margin-left: 250px;
-  }
-  #content #fold-button {
-    display: none;
-  }
-  #content .navbar-main,
-  #content .navbar-main-additional {
-    left: 250px;
-  }
-  .table td.td-long {
-    width: 30%;
-  }
-}
-#total-mem {
-  background-color: #7cb5ec;
-}
-#heap-mem {
-  background-color: #434348;
-}
-#non-heap-mem {
-  background-color: #90ed7d;
-}
-a.show-pointer {
-  cursor: pointer;
-}

Reply via email to