瀏覽代碼

YARN-7499. Layout changes to Application details page in new YARN UI. Contributed by Vasudevan Skm.

Sunil G 7 年之前
父節點
當前提交
641ba5c7a1
共有 26 個文件被更改,包括 518 次插入337 次删除
  1. 2 2
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/app-table-columns.js
  2. 68 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-app.js
  3. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-flowrun/info.js
  4. 5 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-app.js
  5. 6 6
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js
  6. 22 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app.js
  7. 11 4
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/attempts.js
  8. 12 6
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/charts.js
  9. 11 5
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/components.js
  10. 11 5
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/configs.js
  11. 12 5
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/info.js
  12. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-app.js
  13. 24 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/app.scss
  14. 2 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/colors.scss
  15. 42 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/layout.scss
  16. 4 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/variables.scss
  17. 35 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/yarn-app.scss
  18. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/components/timeline-view.hbs
  19. 117 32
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs
  20. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/attempts.hbs
  21. 22 24
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/charts.hbs
  22. 2 4
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/components.hbs
  23. 28 30
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/configs.hbs
  24. 76 205
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/info.hbs
  25. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/loading.hbs
  26. 1 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-services.hbs

+ 2 - 2
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/app-table-columns.js

@@ -39,7 +39,7 @@ export default Ember.Controller.extend({
           getCellContent: function(row) {
             return {
               displayText: row.id,
-              href: `#/yarn-app/${row.id}/info`
+              href: `#/yarn-app/${row.id}/attempts`
             };
           }
       }, {
@@ -120,7 +120,7 @@ export default Ember.Controller.extend({
       getCellContent: function(row) {
         return {
           displayText: row.get('appName'),
-          href: `#/yarn-app/${row.id}/info?service=${row.get('appName')}`
+          href: `#/yarn-app/${row.id}/attempts?service=${row.get('appName')}`
         };
       }
     }, {

+ 68 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-app.js

@@ -32,6 +32,65 @@ export default Ember.Controller.extend({
     text: 'App'
   }],
 
+  actions: {
+    showStopServiceConfirm() {
+      this.set('actionResponse', null);
+      Ember.$("#stopServiceConfirmDialog").modal('show');
+    },
+
+    stopService() {
+      var self = this;
+      Ember.$("#stopServiceConfirmDialog").modal('hide');
+      var adapter = this.store.adapterFor('yarn-servicedef');
+      self.set('isLoading', true);
+      adapter.stopService(this.model.serviceName).then(function () {
+        self.set('actionResponse', { msg: 'Service stopped successfully. Auto refreshing in 5 seconds.', type: 'success' });
+        Ember.run.later(self, function () {
+          this.set('actionResponse', null);
+          this.send("refresh");
+        }, 5000);
+      }, function (errr) {
+        let messg = errr.diagnostics || 'Error: Stop service failed!';
+        self.set('actionResponse', { msg: messg, type: 'error' });
+      }).finally(function () {
+        self.set('isLoading', false);
+      });
+    },
+
+    showDeleteServiceConfirm() {
+      this.set('actionResponse', null);
+      Ember.$("#deleteServiceConfirmDialog").modal('show');
+    },
+
+    deleteService() {
+      var self = this;
+      Ember.$("#deleteServiceConfirmDialog").modal('hide');
+      var adapter = this.store.adapterFor('yarn-servicedef');
+      self.set('isLoading', true);
+      adapter.deleteService(this.model.serviceName).then(function () {
+        self.set('actionResponse', { msg: 'Service deleted successfully. Redirecting to services in 5 seconds.', type: 'success' });
+        Ember.run.later(self, function () {
+          this.set('actionResponse', null);
+          this.transitionToRoute("yarn-services");
+        }, 5000);
+      }, function (errr) {
+        let messg = errr.diagnostics || 'Error: Delete service failed!';
+        self.set('actionResponse', { msg: messg, type: 'error' });
+      }).finally(function () {
+        self.set('isLoading', false);
+      });
+    },
+
+    resetActionResponse() {
+      this.set('actionResponse', null);
+    }
+  },
+
+  isRunningService: Ember.computed('model.serviceName', 'model.app.state', function () {
+    return this.model.serviceName && this.model.app.get('state') === 'RUNNING';
+  }),
+
+
   updateBreadcrumbs(appId, serviceName, tailCrumbs) {
     var breadcrumbs = [{
       text: "Home",
@@ -58,5 +117,13 @@ export default Ember.Controller.extend({
       breadcrumbs.pushObjects(tailCrumbs);
     }
     this.set('breadcrumbs', breadcrumbs);
-  }
+  },
+
+  amHostHttpAddressFormatted: Ember.computed('model.app.amHostHttpAddress', function () {
+    var amHostAddress = this.get('model.app.amHostHttpAddress');
+    if (amHostAddress && amHostAddress.indexOf('://') < 0) {
+      amHostAddress = 'http://' + amHostAddress;
+    }
+    return amHostAddress;
+  })
 });

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/controllers/yarn-flowrun/info.js

@@ -32,7 +32,7 @@ function createColumn() {
     minWidth: "300px",
     getCellContent: function (row) {
       return {
-        routeName: 'yarn-app.info',
+        routeName: 'yarn-app.attempts',
         id: row.get('appId'),
         displayText: row.get('appId')
       };

+ 5 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/models/yarn-app.js

@@ -33,7 +33,7 @@ export default DS.Model.extend({
   amContainerLogs: DS.attr('string'),
   amHostHttpAddress: DS.attr('string'),
   logAggregationStatus: DS.attr('string'),
-  unmanagedApplication: DS.attr('string'),
+  unmanagedApplication: DS.attr('boolean'),
   amNodeLabelExpression: DS.attr('string'),
   applicationTags: DS.attr('string'),
   applicationType: DS.attr('string'),
@@ -65,6 +65,10 @@ export default DS.Model.extend({
     return this.get("finishedTime");
   }.property("finishedTime"),
 
+  hasFinishedTime: function() {
+    return this.get("finishedTime") >= this.get("startTime");
+  }.property("hasFinishedTime"),
+
   formattedElapsedTime: function() {
     return Converter.msToElapsedTimeUnit(this.get('elapsedTime'));
   }.property('elapsedTime'),

+ 6 - 6
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/router.js

@@ -54,12 +54,12 @@ Router.map(function() {
 
   this.route('yarn-deploy-service');
   this.route('cluster-overview');
-  this.route('yarn-app', function() {
-    this.route('info', {path: '/:app_id/info'});
-    this.route('attempts', {path: '/:app_id/attempts'});
-    this.route('components', {path: '/:app_id/components'});
-    this.route('charts', {path: '/:app_id/charts'});
-    this.route('configs', {path: '/:app_id/configs'});
+  this.route('yarn-app', { path: '/yarn-app/:app_id' }, function() {
+    this.route('info');
+    this.route('attempts');
+    this.route('components');
+    this.route('charts');
+    this.route('configs');
   });
   this.route('yarn-component-instances', function() {
     this.route('info', {path: '/:component_name/info'});

+ 22 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app.js

@@ -16,9 +16,30 @@
  * limitations under the License.
  */
 
+import Ember from 'ember';
 import AbstractRoute from './abstract';
+import AppAttemptMixin from 'yarn-ui/mixins/app-attempt';
 
-export default AbstractRoute.extend({
+export default AbstractRoute.extend(AppAttemptMixin, {
+  model(param, transition) {
+    const {service} = transition.queryParams;
+    transition.send('updateBreadcrumbs', param.app_id, service);
+
+    return Ember.RSVP.hash({
+      appId: param.app_id,
+      serviceName: service,
+      app: this.fetchAppInfoFromRMorATS(param.app_id, this.store),
+
+      quicklinks: this.store.queryRecord('yarn-service-info', { appId: param.app_id }).then(function (info) {
+        if (info && info.get('quicklinks')) {
+          return info.get('quicklinks');
+        }
+        return [];
+      }, function () {
+        return [];
+      })
+    });
+  },
   actions: {
     updateBreadcrumbs(appId, serviceName, tailCrumbs) {
       var controller = this.controllerFor('yarn-app');

+ 11 - 4
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/attempts.js

@@ -22,14 +22,21 @@ import AppAttemptMixin from 'yarn-ui/mixins/app-attempt';
 
 export default AbstractRoute.extend(AppAttemptMixin, {
   model(param, transition) {
-    transition.send('updateBreadcrumbs', param.app_id, param.service, [{text: 'Attempts'}]);
+    const {app_id} = this.paramsFor('yarn-app');
+    const {service} = param;
+
+    transition.send('updateBreadcrumbs', app_id, service, [{text: 'Attempts'}]);
     return Ember.RSVP.hash({
-      appId: param.app_id,
-      serviceName: param.service,
-      attempts: this.fetchAttemptListFromRMorATS(param.app_id, this.store)
+      appId: app_id,
+      serviceName: service,
+      attempts: this.fetchAttemptListFromRMorATS(app_id, this.store)
     });
   },
 
+  refresh() {
+    window.location.reload();
+  },
+
   unloadAll() {
     this.store.unloadAll('yarn-app-attempt');
     this.store.unloadAll('yarn-timeline-appattempt');

+ 12 - 6
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/charts.js

@@ -21,15 +21,17 @@ import AbstractRoute from '../abstract';
 
 export default AbstractRoute.extend({
   model(param, transition) {
-    transition.send('updateBreadcrumbs', param.app_id, param.service, [{text: "Charts"}]);
+    const { app_id } = this.paramsFor('yarn-app');
+    const { service } = param;
+    transition.send('updateBreadcrumbs', app_id, service, [{text: "Charts"}]);
     return Ember.RSVP.hash({
-      appId: param.app_id,
-      serviceName: param.service,
+      appId: app_id,
+      serviceName: service,
 
-      app: this.store.find('yarn-app', param.app_id),
+      app: this.store.find('yarn-app', app_id),
 
-      rmContainers: this.store.find('yarn-app', param.app_id).then(function() {
-        return this.store.query('yarn-app-attempt', {appId: param.app_id}).then(function (attempts) {
+      rmContainers: this.store.find('yarn-app', app_id).then(function() {
+        return this.store.query('yarn-app-attempt', {appId: app_id}).then(function (attempts) {
           if (attempts && attempts.get('firstObject')) {
             var appAttemptId = attempts.get('firstObject').get('appAttemptId');
             return this.store.query('yarn-container', {
@@ -44,6 +46,10 @@ export default AbstractRoute.extend({
     });
   },
 
+  refresh() {
+    window.location.reload();
+  },
+
   unloadAll() {
     this.store.unloadAll('yarn-app');
     this.store.unloadAll('yarn-app-attempt');

+ 11 - 5
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/components.js

@@ -21,14 +21,16 @@ import AbstractRoute from '../abstract';
 
 export default AbstractRoute.extend({
   model(param, transition) {
-    transition.send('updateBreadcrumbs', param.app_id, param.service, [{text: 'Components'}]);
+    const { app_id } = this.paramsFor('yarn-app');
+    const { service } = param;
+    transition.send('updateBreadcrumbs', app_id, service, [{text: 'Components'}]);
     return Ember.RSVP.hash({
-      appId: param.app_id,
-      serviceName: param.service,
-      components: this.store.query('yarn-service-component', {appId: param.app_id, type: 'COMPONENT'}).catch(function() {
+      appId: app_id,
+      serviceName: service,
+      components: this.store.query('yarn-service-component', {appId: app_id, type: 'COMPONENT'}).catch(function() {
         return [];
       }),
-      instances: this.store.query('yarn-component-instance', {appId: param.app_id}).catch(function() {
+      instances: this.store.query('yarn-component-instance', {appId: app_id}).catch(function() {
         return [];
       })
     });
@@ -42,6 +44,10 @@ export default AbstractRoute.extend({
     });
   },
 
+  refresh() {
+    window.location.reload();
+  },
+
   unloadAll() {
     this.store.unloadAll('yarn-service-component');
     this.store.unloadAll('yarn-component-instance');

+ 11 - 5
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/configs.js

@@ -21,12 +21,14 @@ import AbstractRoute from '../abstract';
 
 export default AbstractRoute.extend({
   model(param, transition) {
-    transition.send('updateBreadcrumbs', param.app_id, param.service, [{text: "Configurations & Metrics"}]);
+    const { app_id } = this.paramsFor('yarn-app');
+    const { service } = param;
+    transition.send('updateBreadcrumbs', app_id, service, [{text: "Configurations & Metrics"}]);
     return Ember.RSVP.hash({
-      appId: param.app_id,
-      serviceName: param.service,
+      appId: app_id,
+      serviceName: service,
 
-      configs: this.store.queryRecord('yarn-service-info', {appId: param.app_id}).then(function(info) {
+      configs: this.store.queryRecord('yarn-service-info', {appId: app_id}).then(function(info) {
         if (info && info.get('configs')) {
           return info.get('configs');
         }
@@ -35,7 +37,7 @@ export default AbstractRoute.extend({
         return [];
       }),
 
-      metrics: this.store.queryRecord('yarn-service-info', {appId: param.app_id}).then(function(info) {
+      metrics: this.store.queryRecord('yarn-service-info', {appId: app_id}).then(function(info) {
         if (info && info.get('metrics')) {
           return info.get('metrics');
         }
@@ -46,6 +48,10 @@ export default AbstractRoute.extend({
     });
   },
 
+  refresh() {
+    window.location.reload();
+  },
+
   unloadAll() {
     this.store.unloadAll('yarn-service-info');
   }

+ 12 - 5
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/routes/yarn-app/info.js

@@ -22,13 +22,16 @@ import AppAttemptMixin from 'yarn-ui/mixins/app-attempt';
 
 export default AbstractRoute.extend(AppAttemptMixin, {
   model(param, transition) {
-    transition.send('updateBreadcrumbs', param.app_id, param.service);
+    const { app_id } = this.paramsFor('yarn-app');
+    const { service } = param;
+    transition.send('updateBreadcrumbs', app_id, service);
+
     return Ember.RSVP.hash({
-      appId: param.app_id,
-      serviceName: param.service,
-      app: this.fetchAppInfoFromRMorATS(param.app_id, this.store),
+      appId: app_id,
+      serviceName: service,
+      app: this.fetchAppInfoFromRMorATS(app_id, this.store),
 
-      quicklinks: this.store.queryRecord('yarn-service-info', {appId: param.app_id}).then(function(info) {
+      quicklinks: this.store.queryRecord('yarn-service-info', {appId: app_id}).then(function(info) {
         if (info && info.get('quicklinks')) {
           return info.get('quicklinks');
         }
@@ -39,6 +42,10 @@ export default AbstractRoute.extend(AppAttemptMixin, {
     });
   },
 
+  refresh() {
+    window.location.reload();
+  },
+
   unloadAll() {
     this.store.unloadAll('yarn-app');
     this.store.unloadAll('yarn-app-timeline');

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/serializers/yarn-app.js

@@ -52,7 +52,7 @@ export default DS.JSONAPISerializer.extend({
           amContainerLogs: payload.amContainerLogs,
           amHostHttpAddress: payload.amHostHttpAddress,
           logAggregationStatus: payload.logAggregationStatus,
-          unmanagedApplication: payload.unmanagedApplication || 'N/A',
+          unmanagedApplication: payload.unmanagedApplication,
           amNodeLabelExpression: payload.amNodeLabelExpression,
           priority: (payload.priority !== undefined)? payload.priority : 'N/A',
           allocatedMB: payload.allocatedMB,

+ 24 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/app.scss

@@ -1,4 +1,6 @@
 @import 'variables.scss';
+@import 'layout.scss';
+@import 'yarn-app.scss';
 
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -497,6 +499,9 @@ div.attempt-info-panel table > tbody > tr > td:last-of-type {
 .align-center {
   text-align: center !important;
 }
+.align-right {
+  text-align: right;
+}
 
 .bold-text {
   font-weight: bold !important;
@@ -717,7 +722,26 @@ div.service-action-mask img {
     line-height: 2em;
 }
 
+.yarn-app-body > div:not(:first-child) {
+  margin-top: 15px;
+}
+
 .em-table .table-column .table-header-cell {
   background-color: $yarn-header-bg;
   border-bottom: 1px solid $yarn-border-color;
 }
+
+.muted-text {
+  color: $yarn-muted-text;
+}
+
+.glyphicon-gray {
+  color: $yarn-gray-icon;
+}
+
+.btn.btn-unstyled {
+  padding: 5px;
+  background: none;
+  border: none;
+  box-shadow: none;
+}

+ 2 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/colors.scss

@@ -21,6 +21,8 @@ $color-ghost-white: #f1f2f8;
 $color-white: #fff;
 
 $color-gray-20: #333;
+$color-gray-40: #666666;
+$color-gray-60: #999999;
 $color-gray-97: #f7f7f7;
 $color-light-gray: #d5d5d5;
 

+ 42 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/layout.scss

@@ -0,0 +1,42 @@
+/**
+ * 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.
+ */
+
+.flex {
+  display: flex;
+}
+
+/** Pushes the flex element to the right corner **/
+.flex-right {
+  margin-left: auto;
+}
+
+.offset-1 {
+  margin-left: 5px
+}
+
+.offset-2 {
+  margin-left: 10px
+}
+
+.tail-1 {
+  margin-right: 5px
+}
+
+.tail-2 {
+  margin-right: 10px
+}

+ 4 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/variables.scss

@@ -36,5 +36,9 @@ $yarn-info-bg: $color-blue-primary;
 $yarn-warn-border: $color-yellow-secondary;
 $yarn-warn-bg: $color-yellow-primary;
 
+$yarn-gray-icon: $color-gray-40;
+
+$yarn-muted-text: $color-gray-60;
+
 
 //shadows

+ 35 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/styles/yarn-app.scss

@@ -0,0 +1,35 @@
+/**
+ * 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.
+ */
+
+.yarn-app-header {
+  padding: 20px;
+  line-height: 2em;
+
+  h3 {
+    margin: 0px;
+  }
+
+  i.glyphicon {
+    vertical-align: text-top;
+    margin-right: 3px;
+  }
+
+  .yarn-app-final-status .label {
+    vertical-align: middle;
+  }
+}

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/components/timeline-view.hbs

@@ -16,7 +16,7 @@
  * limitations under the License.
 }}
 
-<div class="col-md-12 container-fluid">
+<div class="col-md-12">
   <div class="panel panel-default">
     <div class="panel-heading">
       {{#if attemptModel}}

+ 117 - 32
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app.hbs

@@ -18,46 +18,131 @@
 
 {{breadcrumb-bar breadcrumbs=breadcrumbs}}
 
-<div class="col-md-12 container-fluid">
-  <div class="row">
+<div class="panel-group">
+  <div class="panel panel-default">
+    <div class="yarn-app-header">
+      <div class="flex">
+        <div>
+          <div class="flex">
+            <div>
+              <h3 class="yarn-app-name">
+                {{#if model.app.unmanagedApplication}}
+                  <span title="This is an unmanaged application" class="yarn-tooltip">
+                    <i class="glyphicon glyphicon-exclamation-sign glyphicon-gray" />
+                  </span>
+                {{/if}}
+                {{model.app.appName}}
+              </h3>
+            </div>
+            {{#if model.app.finalStatus}}
+            <div title="Final status" class="yarn-tooltip yarn-app-final-status offset-1">
+              <span class={{model.app.finalStatusStyle}}>
+                {{model.app.finalStatus}}
+              </span>
+            </div>
+            {{/if}}
+          </div>
+          <div title="Application id" class="muted-text">{{model.app.id}}</div>
+          {{em-table-simple-status-cell content=model.app.state}}
+          <div class="flex">
+            <div class="tail-2">
+              <i class="glyphicon glyphicon-user glyphicon-gray" /> {{model.app.user}}
+            </div>
+            {{#if model.app.hasFinishedTime}}
+              <div title="Started at {{model.app.startTime}}. &#10; Ran for {{model.app.formattedElapsedTime}}" class="yarn-tooltip">
+                <i class="glyphicon glyphicon-time glyphicon-gray" />
+                Finished at {{model.app.validatedFinishedTs}}
+              </div>
+            {{else}}
+              <div title="Running for {{model.app.formattedElapsedTime}}" class="yarn-tooltip">
+                <i class="glyphicon glyphicon-time glyphicon-gray" />
+                Started at {{model.app.startTime}}
+              </div>
+            {{/if}}
+          </div>
+        </div>
+
+        <div class="flex-right">
+          <div class="links">
+            <a href="{{model.app.amContainerLogs}}" target="_blank">Master Container Log</a> &nbsp;|&nbsp;
+            <a href="{{amHostHttpAddressFormatted}}" target="_blank">Master Node</a>  &nbsp;|&nbsp;
+            {{#if isRunningService}}
+              <div class="btn-group">
+                <button type="button" class="btn btn-unstyled dropdown-toggle" title="Settings" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                  <i class="glyphicon glyphicon-cog" />
+                </button>
+                <ul class="dropdown-menu dropdown-menu-right">
+                  <li>
+                    <a href="#"  {{action "showStopServiceConfirm"}} target="_blank"><i class="glyphicon glyphicon-stop" /> &nbsp;Stop Service</a>
+                  </li>
+                  <li>
+                    <a href="#" target="_blank" {{action "showDeleteServiceConfirm"}}><i class="glyphicon glyphicon-trash" /> &nbsp;Delete Service </a>
+                  </li>
+                </ul>
+              </div>
+            {{/if}}
 
-    <div class="col-md-2 container-fluid">
-      <div class="panel panel-default">
-        <div class="panel-heading">
-          {{#if serviceName}}
-            Service
-          {{else}}
-            Application
-          {{/if}}
+            {{#if model.serviceName}}
+              {{#if model.quicklinks}}
+              <div class="btn-group">
+                <button type="button" class="btn btn-unstyled dropdown-toggle" title="Quick links" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                  <i class="glyphicon glyphicon-option-vertical" />
+                </button>
+                <ul class="dropdown-menu dropdown-menu-right">
+                  {{#each model.quicklinks as |link|}}
+                    <li><a href="{{link.value}}" target="_blank">{{link.name}}</a></li>
+                  {{/each}}
+                </ul>
+              </div>
+              {{/if}}
+            {{/if}}
+          </div>
+          <div>
+            <span title="Queue" class="yarn-tooltip"><i class="glyphicon glyphicon-tasks glyphicon-gray" />{{model.app.queue}}</span>
+          </div>
+          <div>Priority {{model.app.priority}}</div>
         </div>
-        <div class="panel-body">
-          <ul class="nav nav-pills nav-stacked" id="stacked-menu">
-            <ul class="nav nav-pills nav-stacked collapse in">
-              {{#link-to 'yarn-app.info' tagName="li" class=(if (eq target.currentPath 'yarn-app.info') "active")}}
-                {{#link-to 'yarn-app.info' appId (query-params service=serviceName)}}Information{{/link-to}}
-              {{/link-to}}
-              {{#link-to 'yarn-app.attempts' tagName="li" class=(if (eq target.currentPath 'yarn-app.attempts') "active")}}
-                {{#link-to 'yarn-app.attempts' appId (query-params service=serviceName)}}Attempts List{{/link-to}}
+      </div>
+    </div>
+    <div class="panel-heading">
+      <div class="clearfix">
+        <ul class="nav nav-pills">
+          <ul class="nav nav-pills collapse in">
+            {{#link-to 'yarn-app.attempts' tagName="li" class=(if (eq target.currentPath 'yarn-app.attempts') "active")}}
+              {{#link-to 'yarn-app.attempts' appId (query-params service=model.serviceName)}}Attempts List{{/link-to}}
+            {{/link-to}}
+            {{#link-to 'yarn-app.charts' tagName="li" class=(if (eq target.currentPath 'yarn-app.charts') "active")}}
+              {{#link-to 'yarn-app.charts' appId (query-params service=model.serviceName)}}Resource Usage{{/link-to}}
+            {{/link-to}}
+            {{#if model.serviceName}}
+              {{#link-to 'yarn-app.components' tagName="li" class=(if (eq target.currentPath 'yarn-app.components') "active")}}
+                {{#link-to 'yarn-app.components' appId (query-params service=model.serviceName)}}Components{{/link-to}}
               {{/link-to}}
-              {{#link-to 'yarn-app.charts' tagName="li" class=(if (eq target.currentPath 'yarn-app.charts') "active")}}
-                {{#link-to 'yarn-app.charts' appId (query-params service=serviceName)}}Resource Usage{{/link-to}}
+              {{#link-to 'yarn-app.configs' tagName="li" class=(if (eq target.currentPath 'yarn-app.configs') "active")}}
+                {{#link-to 'yarn-app.configs' appId (query-params service=model.serviceName)}}Configurations &amp; Metrics{{/link-to}}
               {{/link-to}}
-              {{#if serviceName}}
-                {{#link-to 'yarn-app.components' tagName="li" class=(if (eq target.currentPath 'yarn-app.components') "active")}}
-                  {{#link-to 'yarn-app.components' appId (query-params service=serviceName)}}Components{{/link-to}}
-                {{/link-to}}
-                {{#link-to 'yarn-app.configs' tagName="li" class=(if (eq target.currentPath 'yarn-app.configs') "active")}}
-                  {{#link-to 'yarn-app.configs' appId (query-params service=serviceName)}}Configurations &amp; Metrics{{/link-to}}
-                {{/link-to}}
-              {{/if}}
-            </ul>
+            {{/if}}
+            {{#link-to 'yarn-app.info' tagName="li" class=(if (eq target.currentPath 'yarn-app.info') "active")}}
+              {{#link-to 'yarn-app.info' appId (query-params service=model.serviceName)}}Diagnostics{{/link-to}}
+            {{/link-to}}
           </ul>
-        </div>
+        </ul>
       </div>
     </div>
-
-    <div class="col-md-10 container-fluid">
+    <div class="panel-body yarn-app-body">
       {{outlet}}
     </div>
   </div>
 </div>
+
+{{confirm-dialog
+  dialogId="stopServiceConfirmDialog"
+  message=(concat 'Are you sure you want to stop service "' model.serviceName '" ?')
+  action="stopService"
+}}
+
+{{confirm-dialog
+  dialogId="deleteServiceConfirmDialog"
+  message=(concat 'Are you sure you want to delete service "' model.serviceName '" ?')
+  action="deleteService"
+}}

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/attempts.hbs

@@ -16,7 +16,7 @@
  * limitations under the License.
 }}
 
-<div class="row">
+<div>
   {{timeline-view
     parent-id="attempt-timeline-div"
     my-id="timeline-view"

+ 22 - 24
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/charts.hbs

@@ -16,28 +16,26 @@
  * limitations under the License.
 }}
 
-<div class="row">
-  <div class="col-md-12 container-fluid">
-    {{#if isRunningApp}}
-      <div class="row" id="stackd-bar-chart-mem">
-        {{per-app-memusage-by-nodes-stacked-barchart
-        nodes=model.nodes
-        rmContainers=model.rmContainers
-        parentId="stackd-bar-chart-mem"
-        title=(concat 'Memory usage by nodes for: [' model.appId ']')}}
-      </div>
-      <hr>
-      <div class="row" id="stackd-bar-chart-ncontainer">
-        {{per-app-ncontainers-by-nodes-stacked-barchart
-        nodes=model.nodes
-        rmContainers=model.rmContainers
-        parentId="stackd-bar-chart-ncontainer"
-        title=(concat 'Running #Containers by nodes for: [' model.appId ']')}}
-      </div>
-    {{else}}
-      <div class="panel panel-default">
-        <h4 class="text-center">No resource usage data is available for this application!</h4>
-      </div>
-    {{/if}}
-  </div>
+<div class="col-md-12">
+  {{#if isRunningApp}}
+    <div class="row" id="stackd-bar-chart-mem">
+      {{per-app-memusage-by-nodes-stacked-barchart
+      nodes=model.nodes
+      rmContainers=model.rmContainers
+      parentId="stackd-bar-chart-mem"
+      title=(concat 'Memory usage by nodes for: [' model.appId ']')}}
+    </div>
+    <hr>
+    <div class="row" id="stackd-bar-chart-ncontainer">
+      {{per-app-ncontainers-by-nodes-stacked-barchart
+      nodes=model.nodes
+      rmContainers=model.rmContainers
+      parentId="stackd-bar-chart-ncontainer"
+      title=(concat 'Running #Containers by nodes for: [' model.appId ']')}}
+    </div>
+  {{else}}
+    <div class="panel panel-default">
+      <h4 class="text-center">No resource usage data is available for this application!</h4>
+    </div>
+  {{/if}}
 </div>

+ 2 - 4
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/components.hbs

@@ -16,8 +16,6 @@
  * limitations under the License.
 }}
 
-<div class="row">
-  <div class="col-md-12">
-    {{em-table columns=tableColumns rows=model.components}}
-  </div>
+<div class="col-md-12 yarn-applications-container">
+  {{em-table columns=tableColumns rows=model.components}}
 </div>

+ 28 - 30
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/configs.hbs

@@ -20,38 +20,36 @@
   {{metrics-table metrics=model.metrics type="Service"}}
 </div>
 
-<div class="row">
-  {{#if model.configs}}
-    <div class="col-md-12">
-      <div class="panel panel-default">
-        <div class="panel-heading">
-          <div class="panel-title">Service Configurations</div>
-        </div>
-        <div class="">
-          <table class="table table-hover table-custom-bordered table-custom-stripped table-radius-none table-border-none">
-            <thead>
+{{#if model.configs}}
+  <div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <div class="panel-title">Service Configurations</div>
+      </div>
+      <div class="">
+        <table class="table table-hover table-custom-bordered table-custom-stripped table-radius-none table-border-none">
+          <thead>
+            <tr>
+              <th>Name</th>
+              <th>Value</th>
+            </tr>
+          </thead>
+          <tbody>
+            {{#each model.configs as |config|}}
               <tr>
-                <th>Name</th>
-                <th>Value</th>
+                <td>{{config.name}}</td>
+                <td>{{config.value}}</td>
               </tr>
-            </thead>
-            <tbody>
-              {{#each model.configs as |config|}}
-                <tr>
-                  <td>{{config.name}}</td>
-                  <td>{{config.value}}</td>
-                </tr>
-              {{/each}}
-            </tbody>
-          </table>
-        </div>
+            {{/each}}
+          </tbody>
+        </table>
       </div>
     </div>
-  {{else}}
-    <div class="col-md-12">
-      <div class="panel panel-default">
-        <h4 class="text-center">No service configurations available!</h4>
-      </div>
+  </div>
+{{else}}
+  <div class="col-md-12">
+    <div class="panel panel-default">
+      <h4 class="text-center">No service configurations available!</h4>
     </div>
-  {{/if}}
-</div>
+  </div>
+{{/if}}

+ 76 - 205
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/info.hbs

@@ -16,19 +16,15 @@
  * limitations under the License.
 }}
 
-<div class="row">
-  <div class="col-md-12">
-    {{app-timeout-bar app=model.app}}
-  </div>
+<div class="col-md-12">
+  {{app-timeout-bar app=model.app}}
 </div>
 
 {{#if actionResponse}}
-  <div class="row">
-    <div class="col-md-12">
-      <div class="alert alert-dismissible {{if (eq actionResponse.type 'error') 'alert-danger' 'alert-success'}}" role="alert">
-        <button class="close" data-dismiss="alert" aria-label="Close" {{action "resetActionResponse"}}><span aria-hidden="true">&times;</span></button>
-        <strong>{{actionResponse.msg}}</strong>
-      </div>
+  <div class="col-md-12">
+    <div class="alert alert-dismissible {{if (eq actionResponse.type 'error') 'alert-danger' 'alert-success'}}" role="alert">
+      <button class="close" data-dismiss="alert" aria-label="Close" {{action "resetActionResponse"}}><span aria-hidden="true">&times;</span></button>
+      <strong>{{actionResponse.msg}}</strong>
     </div>
   </div>
 {{/if}}
@@ -39,220 +35,95 @@
   </div>
 {{/if}}
 
-<div class="row">
-  <div class="col-md-12 container-fluid">
-    <div class="panel panel-default">
-      <div class="panel-heading">
-        Basic Info
-        {{#if isRunningService}}
-          <div class="pull-right" style="display: inline-block; margin: -4px -10px 0 0;">
-            <button class="btn btn-sm btn-danger" disabled="{{if isLoading 'disabled'}}" {{action "showStopServiceConfirm"}}> Stop </button>
-            <button class="btn btn-sm btn-danger" disabled="{{if isLoading 'disabled'}}" {{action "showDeleteServiceConfirm"}}> Delete </button>
-          </div>
-        {{/if}}
+{{#if model.app.diagnostics}}
+  <div class="col-md-12">
+    {{#if model.app.isFailed}}
+      <div class="panel panel-danger">
+        <div class="panel-heading">
+          Diagnostics
+        </div>
+        <div class="panel-body">{{model.app.diagnostics}}</div>
       </div>
-      <div class="x-scroll">
-        <table class="display table table-striped table-bordered"
-               cellspacing="0" width="100%">
-          <thead>
-            <tr>
-              <th>Application ID</th>
-              <th>Name</th>
-              <th>User</th>
-              <th>Queue</th>
-              <th>State</th>
-              <th>Final Status</th>
-              <th>Start Time</th>
-              <th>Elapsed Time</th>
-              <th>Finished Time</th>
-              <th>Priority</th>
-              {{#unless model.serviceName}}
-                <th>Progress</th>
-                <th>Is Unmanaged AM</th>
-              {{/unless}}
-            </tr>
-          </thead>
-
-          <tbody>
-            <tr>
-              <td>{{model.app.id}}</td>
-              <td>{{model.app.appName}}</td>
-              <td>{{model.app.user}}</td>
-              <td>{{model.app.queue}}</td>
-              <td>{{model.app.state}}</td>
-              <td>
-                <span class={{model.app.finalStatusStyle}}>
-                  {{model.app.finalStatus}}
-                </span>
-              </td>
-              <td>{{model.app.startTime}}</td>
-              <td>{{model.app.formattedElapsedTime}}</td>
-              <td>{{model.app.validatedFinishedTs}}</td>
-              <td>{{model.app.priority}}</td>
-              {{#unless model.serviceName}}
-                <td>
-                  <div class="progress" style="margin-bottom: 0;">
-                    <div class="progress-bar" role="progressbar"
-                     aria-valuenow="60" aria-valuemin="0"
-                     aria-valuemax="100"
-                     style={{model.app.progressStyle}}>
-                    {{model.app.progress}}%
-                    </div>
-                  </div>
-                </td>
-                <td>{{model.app.unmanagedApplication}}</td>
-              {{/unless}}
-            </tr>
-          </tbody>
-        </table>
+    {{else}}
+      <div class="panel panel-default">
+        <div class="panel-heading">
+          Diagnostics
+        </div>
+        <div class="panel-body">{{model.app.diagnostics}}</div>
       </div>
-    </div>
+    {{/if}}
   </div>
-</div>
-
-<div class="row">
-  {{#if model.app.diagnostics}}
-    <div class="col-md-12 container-fluid">
-      {{#if model.app.isFailed}}
-        <div class="panel panel-danger">
-          <div class="panel-heading">
-            Diagnostics
-          </div>
-          <div class="panel-body">{{model.app.diagnostics}}</div>
-        </div>
-      {{else}}
-        <div class="panel panel-default">
-          <div class="panel-heading">
-            Diagnostics
-          </div>
-          <div class="panel-body">{{model.app.diagnostics}}</div>
-        </div>
-      {{/if}}
-    </div>
-  {{/if}}
-</div>
+{{/if}}
 
 {{#unless model.serviceName}}
-  <div class="row">
-    <div class="col-md-12 container-fluid">
-      <div class="panel panel-default">
-        <div class="panel-heading">Outstanding Resource Requests</div>
-        <table class="display table table-striped table-bordered"
-              cellspacing="0" width="100%">
-          <thead>
-          <tr>
-            <th>Scheduler Key</th>
-            <th>Resource Name</th>
-            <th>Capability</th>
-            <th># Containers</th>
-            <th>Relax Locality</th>
-            <th>Node Label Expression</th>
-          </tr>
-          </thead>
-          <tbody>
-          {{#each model.app.resourceRequests as |request|}}
-            <tr>
-              <td>{{request.priority}}</td>
-              <td>{{request.resourceName}}</td>
-              <td>&lt;Memory:{{request.capability.memory}};vCores:{{request.capability.virtualCores}}&gt;</td>
-              <td>{{request.numContainers}}</td>
-              <td>{{request.relaxLocality}}</td>
-              <td>
-                {{#if request.nodeLabelExpression}}
-                  {{request.nodeLabelExpression}}
-                {{else}}
-                  N/A
-                {{/if}}
-              </td>
-            </tr>
-          {{else}}
-            <div class="panel-body">No data available!</div>
-          {{/each}}
-          </tbody>
-        </table>
-      </div>
-    </div>
-  </div>
-{{/unless}}
-<div class="row">
-  <div class="col-md-12 container-fluid">
+  <div class="col-md-12">
     <div class="panel panel-default">
-      <div class="panel-heading">Scheduling Info</div>
+      <div class="panel-heading">Outstanding Resource Requests</div>
       <table class="display table table-striped table-bordered"
-             cellspacing="0" width="100%">
+            cellspacing="0" width="100%">
         <thead>
         <tr>
-          <th>Allocated Resource</th>
-          <th>Running Containers</th>
-          <th>Preempted Resource</th>
-          <th>Num Non-AM container preempted</th>
-          <th>Num AM container preempted</th>
-          <th>Aggregated Resource Usage</th>
+          <th>Scheduler Key</th>
+          <th>Resource Name</th>
+          <th>Capability</th>
+          <th># Containers</th>
+          <th>Relax Locality</th>
+          <th>Node Label Expression</th>
         </tr>
         </thead>
-
         <tbody>
-        <tr>
-          <td>{{model.app.allocatedResource}}</td>
-          <td>{{model.app.runningContainersNumber}}</td>
-          <td>{{model.app.preemptedResource}}</td>
-          <td>{{model.app.numAMContainerPreempted}}</td>
-          <td>{{model.app.numAMContainerPreempted}}</td>
-          <td>{{model.app.aggregatedResourceUsage}}</td>
-        </tr>
+        {{#each model.app.resourceRequests as |request|}}
+          <tr>
+            <td>{{request.priority}}</td>
+            <td>{{request.resourceName}}</td>
+            <td>&lt;Memory:{{request.capability.memory}};vCores:{{request.capability.virtualCores}}&gt;</td>
+            <td>{{request.numContainers}}</td>
+            <td>{{request.relaxLocality}}</td>
+            <td>
+              {{#if request.nodeLabelExpression}}
+                {{request.nodeLabelExpression}}
+              {{else}}
+                N/A
+              {{/if}}
+            </td>
+          </tr>
+        {{else}}
+          <div class="panel-body">No data available!</div>
+        {{/each}}
         </tbody>
       </table>
     </div>
   </div>
+{{/unless}}
 
-</div>
+<div class="col-md-12">
+  <div class="panel panel-default">
+    <div class="panel-heading">Scheduling Info</div>
+    <table class="display table table-striped table-bordered"
+            cellspacing="0" width="100%">
+      <thead>
+      <tr>
+        <th>Allocated Resource</th>
+        <th>Running Containers</th>
+        <th>Preempted Resource</th>
+        <th>Num Non-AM container preempted</th>
+        <th>Num AM container preempted</th>
+        <th>Aggregated Resource Usage</th>
+      </tr>
+      </thead>
 
-<div class="row">
-  <div class="col-md-6 container-fluid">
-    <div class="panel panel-default">
-      <div class="panel-heading">Application Master Info</div>
-      <table class="display table table-striped table-bordered"
-             cellspacing="0" width="100%">
-        <thead>
-        <tr>
-          <th>Master Container Log</th>
-          <th>Master Node</th>
-          <th>Master Node Label Expression</th>
-        </tr>
-        </thead>
-
-        <tbody>
-        <tr>
-          <td><a href="{{model.app.amContainerLogs}}" target="_blank">Link</a></td>
-          <td><a href="{{amHostHttpAddressFormatted}}" target="_blank">Link</a></td>
-          <td>{{model.app.amNodeLabelExpression}}</td>
-        </tr>
-        </tbody>
-      </table>
-    </div>
+      <tbody>
+      <tr>
+        <td>{{model.app.allocatedResource}}</td>
+        <td>{{model.app.runningContainersNumber}}</td>
+        <td>{{model.app.preemptedResource}}</td>
+        <td>{{model.app.numAMContainerPreempted}}</td>
+        <td>{{model.app.numAMContainerPreempted}}</td>
+        <td>{{model.app.aggregatedResourceUsage}}</td>
+      </tr>
+      </tbody>
+    </table>
   </div>
-
-  {{#if model.serviceName}}
-    <div class="col-md-6 container-fluid">
-      <div class="panel panel-default">
-        <div class="panel-heading">Quick Links</div>
-        <table class="display table table-striped table-bordered">
-          <tbody>
-            {{#each model.quicklinks as |link|}}
-              <tr>
-                <td>{{link.name}}</td>
-                <td><a href="{{link.value}}" target="_blank">{{link.value}}</a></td>
-              </tr>
-            {{else}}
-              <tr class="align-center">
-                <td colspan="2">No quicklinks available!</td>
-              </tr>
-            {{/each}}
-          </tbody>
-        </table>
-      </div>
-    </div>
-  {{/if}}
 </div>
 
 {{confirm-dialog

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-app/loading.hbs

@@ -16,7 +16,7 @@
  * limitations under the License.
 }}
 
-<div class="col-md-12 container-fluid">
+<div class="col-md-12">
   <div class="loading-mask">
     <img src="assets/images/spinner.gif" alt="Loading...">
   </div>

+ 1 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/app/templates/yarn-services.hbs

@@ -17,7 +17,7 @@
 --}}
 
 {{breadcrumb-bar breadcrumbs=breadcrumbs}}
-
+<a class="btn btn-primary pull-right" href="#/yarn-deploy-service">New Service</a>
 <div class="col-md-12 container-fluid yarn-applications-container">
   {{#if model.apps}}
     {{em-table columns=serviceColumns rows=model.apps definition=tableDefinition}}