Browse Source

[jb] Split the Job Browser mako into a regular and mini version and extract inline js (#3646)

In order to be able to extract all job browser inline JS I had to split the job_browser.mako in two, one for the regular job browser and one for the mini version (used in the upper right jobs panel throughout Hue). Apart from separating it into two mako files there's also two corresponding webpack entries added. In short there was a mako page variable, is_mini, from a URL parameter that decided how to render the page and having that variable around would require inline JS.

Common Knockout templates, shared between the main Job Browser and the mini version, are now included in the main hue.mako and shared between the two instances, this reduces the time to load the main Job Browser slightly but the main benefit is that there's no duplication.

As part of this effort I've modernized a large chunk of the JS, a huge win is that this code is now under webpack control. I've fixed multiple bugs that became apparent in the IDE and linter once the mako inline js ended up in proper .js files. While testing I also improved some error handling throughout.

Note that there are no unit tests as part of this commit, the Job Browser had zero coverage when I started and I feel the effort would be considerable to bring it up to a reasonable level. It makes more sense to me to introduce tests once we convert these pages to React.
Johan Åhlén 1 year ago
parent
commit
b9957215af

+ 303 - 4333
apps/jobbrowser/src/jobbrowser/templates/job_browser.mako

@@ -17,14 +17,9 @@
 import sys
 
 from desktop.conf import CUSTOM, IS_K8S_ONLY
-from desktop.views import commonheader, commonfooter, _ko
+from desktop.views import commonheader, commonfooter
 from desktop.webpack_utils import get_hue_bundles
-from impala.conf import COORDINATOR_URL as IMPALA_COORDINATOR_URL
-from metadata.conf import PROMETHEUS
-from notebook.conf import ENABLE_QUERY_SCHEDULING
-
-from jobbrowser.conf import DISABLE_KILLING_JOBS, MAX_JOB_FETCH, ENABLE_QUERY_BROWSER, ENABLE_HIVE_QUERY_BROWSER, QUERY_STORE, ENABLE_HISTORY_V2
-
+from jobbrowser.conf import MAX_JOB_FETCH, ENABLE_QUERY_BROWSER
 from webpack_loader.templatetags.webpack_loader import render_bundle
 
 if sys.version_info[0] > 2:
@@ -33,4408 +28,383 @@ else:
   from django.utils.translation import ugettext as _
 %>
 
-<%
-SUFFIX = is_mini and "-mini" or ""
-%>
-
 % if not is_embeddable:
 ${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
 % endif
 
 <span class="notebook">
 
-% if not is_embeddable:
-<link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
-<link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
-<style type="text/css">
-% if CUSTOM.BANNER_TOP_HTML.get():
-  .show-assist {
-    top: 110px!important;
-  }
-  .main-content {
-    top: 112px!important;
-  }
-% endif
-</style>
-% endif
-
-<link rel="stylesheet" href="${ static('jobbrowser/css/jobbrowser-embeddable.css') }">
-
-<link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-datepicker.min.css') }">
-<link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-timepicker.min.css') }">
-<link rel="stylesheet" href="${ static('desktop/css/bootstrap-spinedit.css') }">
-<link rel="stylesheet" href="${ static('desktop/css/bootstrap-slider.css') }">
-
-% for bundle in get_hue_bundles('jobBrowser'):
-  ${ render_bundle(bundle) | n,unicode }
-% endfor
-
-<script src="${ static('desktop/ext/js/bootstrap-datepicker.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/ext/js/bootstrap-timepicker.min.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/bootstrap-spinedit.js') }" type="text/javascript" charset="utf-8"></script>
-<script src="${ static('desktop/js/bootstrap-slider.js') }" type="text/javascript" charset="utf-8"></script>
-
-
-<script src="${ static('oozie/js/dashboard-utils.js') }" type="text/javascript" charset="utf-8"></script>
-
-% if ENABLE_QUERY_BROWSER.get():
-<script src="${ static('desktop/ext/js/dagre-d3-min.js') }"></script>
-<script src="${ static('jobbrowser/js/impala_dagre.js') }"></script>
-% endif
-
-% if not is_mini:
-<div id="jobbrowserComponents" class="jobbrowser-components jobbrowser-full jb-panel">
-% else:
-<div id="jobbrowserMiniComponents" class="jobbrowser-components jobbrowser-mini jb-panel">
-% endif
-
-% if not is_embeddable:
-  <a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">
-    <i class="fa fa-chevron-right"></i>
-  </a>
-% endif
-
-
-% if is_mini:
-  <div class="mini-jb-context-selector">
-    <!-- ko component: {
-      name: 'hue-context-selector',
-      params: {
-        connector: { id: 'impala' },
-        compute: compute,
-        ##namespace: namespace,
-        ##availableDatabases: availableDatabases,
-        ##database: database,
-        hideDatabases: true
-      }
-    } --><!-- /ko -->
-
-    % if not IS_K8S_ONLY.get():
-    <!-- ko component: {
-      name: 'hue-context-selector',
-      params: {
-        connector: { id: 'jobs' },
-        cluster: cluster,
-        onClusterSelect: onClusterSelect,
-        hideLabels: true
-      }
-    } --><!-- /ko -->
-    % endif
-  </div>
-  <ul class="nav nav-pills">
-    <!-- ko foreach: availableInterfaces -->
-      <li data-bind="css: {'active': $parent.interface() === interface}, visible: condition()">
-        <a class="pointer" data-bind="click: function(){ $parent.selectInterface(interface); }, text: label"></a>
-      </li>
-    <!-- /ko -->
-  </ul>
-% else:
-  <div class="navbar hue-title-bar">
-    <div class="navbar-inner">
-      <div class="container-fluid">
-        <div class="nav-collapse">
-          <ul class="nav">
-            <li class="app-header">
-              % if IS_K8S_ONLY.get():
-                <a data-bind="click: function() { selectInterface('dataware2-clusters'); }">
-                  <i class="altus-icon altus-adb-cluster"></i>
-                  ${ _('Warehouses') }
+  % if not is_embeddable:
+  <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
+  <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
+  <style type="text/css">
+  % if CUSTOM.BANNER_TOP_HTML.get():
+    .show-assist {
+      top: 110px!important;
+    }
+    .main-content {
+      top: 112px!important;
+    }
+  % endif
+  </style>
+  % endif
+
+  <link rel="stylesheet" href="${ static('jobbrowser/css/jobbrowser-embeddable.css') }">
+  <link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-datepicker.min.css') }">
+  <link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-timepicker.min.css') }">
+  <link rel="stylesheet" href="${ static('desktop/css/bootstrap-spinedit.css') }">
+  <link rel="stylesheet" href="${ static('desktop/css/bootstrap-slider.css') }">
+
+  % for bundle in get_hue_bundles('jobBrowser'):
+    ${ render_bundle(bundle) | n,unicode }
+  % endfor
+
+  <script src="${ static('desktop/ext/js/bootstrap-datepicker.min.js') }"></script>
+  <script src="${ static('desktop/ext/js/bootstrap-timepicker.min.js') }"></script>
+  <script src="${ static('desktop/js/bootstrap-spinedit.js') }"></script>
+  <script src="${ static('desktop/js/bootstrap-slider.js') }"></script>
+  <script src="${ static('oozie/js/dashboard-utils.js') }"></script>
+
+  % if ENABLE_QUERY_BROWSER.get():
+  <script src="${ static('desktop/ext/js/dagre-d3-min.js') }"></script>
+  <script src="${ static('jobbrowser/js/impala_dagre.js') }"></script>
+  % endif
+
+  <div id="jobbrowserComponents" class="jobbrowser-components jobbrowser-full jb-panel">
+
+  % if not is_embeddable:
+    <a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">
+      <i class="fa fa-chevron-right"></i>
+    </a>
+  % endif
+
+    <div class="navbar hue-title-bar">
+      <div class="navbar-inner">
+        <div class="container-fluid">
+          <div class="nav-collapse">
+            <ul class="nav">
+              <li class="app-header">
+                % if IS_K8S_ONLY.get():
+                  <a data-bind="click: function() { selectInterface('dataware2-clusters'); }">
+                    <i class="altus-icon altus-adb-cluster"></i>
+                    ${ _('Warehouses') }
+                  </a>
+                % else:
+                <!-- ko ifnot: $root.cluster() && $root.cluster()['type'].indexOf("altus-dw") !== -1 -->
+                <a href="/jobbrowser">
+                  <img src="${ static('jobbrowser/art/icon_jobbrowser_48.png') }" class="app-icon" alt="${ _('Job browser icon') }"/>
+                  <!-- ko if: !$root.cluster() || $root.cluster()['type'].indexOf("altus") == -1 -->
+                    ${ _('Job Browser') }
+                  <!-- /ko -->
+                  <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-engines") !== -1 -->
+                    ${ _('Clusters') }
+                  <!-- /ko -->
+                  <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-de") !== -1 -->
+                    ${ _('Data Engineering') }
+                  <!-- /ko -->
                 </a>
-              % else:
-              <!-- ko ifnot: $root.cluster() && $root.cluster()['type'].indexOf("altus-dw") !== -1 -->
-              <a href="/jobbrowser">
-                <img src="${ static('jobbrowser/art/icon_jobbrowser_48.png') }" class="app-icon" alt="${ _('Job browser icon') }"/>
-                <!-- ko if: !$root.cluster() || $root.cluster()['type'].indexOf("altus") == -1 -->
-                  ${ _('Job Browser') }
-                <!-- /ko -->
-                <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-engines") !== -1 -->
-                  ${ _('Clusters') }
                 <!-- /ko -->
-                <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-de") !== -1 -->
-                  ${ _('Data Engineering') }
+                <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-dw") !== -1 -->
+                  <span>
+                    <a data-bind="click: function() { huePubSub.publish('context.selector.set.cluster', 'engines'); }">
+                      <img src="${ static('jobbrowser/art/icon_jobbrowser_48.png') }" class="app-icon" alt="${ _('Job browser icon') }"/>
+                      ${ _('Clusters') }
+                    </a>
+                    > gke_gcp-eng-dsdw_us-west2-b_impala-demo
+                  </span>
                 <!-- /ko -->
-              </a>
-              <!-- /ko -->
-              <!-- ko if: $root.cluster() && $root.cluster()['type'].indexOf("altus-dw") !== -1 -->
-                <span>
-                  <a data-bind="click: function() { huePubSub.publish('context.selector.set.cluster', 'engines'); }">
-                    <img src="${ static('jobbrowser/art/icon_jobbrowser_48.png') }" class="app-icon" alt="${ _('Job browser icon') }"/>
-                    ${ _('Clusters') }
-                  </a>
-                  > gke_gcp-eng-dsdw_us-west2-b_impala-demo
-                </span>
-              <!-- /ko -->
-              % endif
-            </li>
-            <!-- ko foreach: availableInterfaces -->
-              <li data-bind="css: {'active': $parent.interface() === interface}, visible: condition()">
-                <a class="pointer" data-bind="click: function(){ $parent.selectInterface(interface); }, text: label, visible: label"></a>
+                % endif
               </li>
-            <!-- /ko -->
-          </ul>
-          <div class="pull-right" style="padding-top: 15px">
-            <!-- ko component: {
-              name: 'hue-context-selector',
-              params: {
-                connector: { id: 'jobs' },
-                cluster: cluster,
-                onClusterSelect: onClusterSelect
-              }
-            } --><!-- /ko -->
+              <!-- ko foreach: availableInterfaces -->
+                <li data-bind="css: {'active': $parent.interface() === interface}, visible: condition()">
+                  <a class="pointer" data-bind="click: function(){ $parent.selectInterface(interface); }, text: label, visible: label"></a>
+                </li>
+              <!-- /ko -->
+            </ul>
+            <div class="pull-right" style="padding-top: 15px">
+              <!-- ko component: {
+                name: 'hue-context-selector',
+                params: {
+                  connector: { id: 'jobs' },
+                  cluster: cluster,
+                  onClusterSelect: onClusterSelect
+                }
+              } --><!-- /ko -->
+            </div>
+            % if not hiveserver2_impersonation_enabled:
+              <div class="pull-right label label-warning" style="margin-top: 16px">${ _("Hive jobs are running as the 'hive' user") }</div>
+            % endif
           </div>
-          % if not hiveserver2_impersonation_enabled:
-            <div class="pull-right label label-warning" style="margin-top: 16px">${ _("Hive jobs are running as the 'hive' user") }</div>
-          % endif
         </div>
       </div>
     </div>
-  </div>
-% endif
-
 
-  <script type="text/html" id="apps-list${ SUFFIX }">
-    <table data-bind="attr: {id: tableId}" class="datatables table table-condensed status-border-container hue-jobs-table">
-      <thead>
-      <tr>
-        <th width="1%" class="vertical-align-middle">
-          <div class="select-all hue-checkbox fa" data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div>
-        </th>
-        <th width="20%">${_('Name')}</th>
-        <th width="6%">${_('User')}</th>
-        <th width="6%">${_('Type')}</th>
-        <th width="5%">${_('Status')}</th>
-        <th width="3%">${_('Progress')}</th>
-        <th width="5%">${_('Group')}</th>
-        <th width="10%" data-bind="text: $root.interface() != 'schedules' ? '${_('Started')}' : '${_('Modified')}'"></th>
-        <th width="6%">${_('Duration')}</th>
-        <th width="15%">${_('Id')}</th>
-      </tr>
-      </thead>
-      <tbody data-bind="foreach: apps">
-        <tr class="status-border pointer" data-bind="
-          css: {
-            'completed': apiStatus() == 'SUCCEEDED',
-            'info': apiStatus() == 'PAUSED',
-            'running': apiStatus() == 'RUNNING',
-            'failed': apiStatus() == 'FAILED'
-          },
-          click: function (data, event) {
-            onTableRowClick(event, id());
-          }">
-          <td data-bind="click: function() {}, clickBubble: false">
-            <div class="hue-checkbox fa" data-bind="click: function() {}, clickBubble: false, multiCheck: '#' + $parent.tableId, value: $data, hueChecked: $parent.selectedJobs"></div>
-          </td>
-          <td data-bind="text: name"></td>
-          <td data-bind="text: user"></td>
-          <td data-bind="text: type"></td>
-          <td><span class="label job-status-label" data-bind="text: status"></span></td>
-          <!-- ko if: progress() !== '' -->
-          <td data-bind="text: $root.formatProgress(progress)"></td>
-          <!-- /ko -->
-          <!-- ko if: progress() === '' -->
-          <td data-bind="text: ''"></td>
-          <!-- /ko -->
-          <td data-bind="text: queue"></td>
-          <td data-bind="moment: {data: submitted, format: 'LLL'}"></td>
-          <td data-bind="text: duration().toHHMMSS()"></td>
-          <td data-bind="text: id"></td>
-        </tr>
-      </tbody>
-    </table>
-  </script>
-
-  <script type="text/html" id="create-cluster-content">
-    <form>
-      <fieldset>
-        <label for="clusterCreateName">${ _('Name') }</label>
-        <input id="clusterCreateName" type="text" placeholder="${ _('Name') }" data-bind="clearable: jobs.createClusterName, valueUpdate: 'afterkeydown'">
+    <div class="main-content">
+      <div class="vertical-full container-fluid" data-bind="style: { 'padding-left' : $root.isLeftPanelVisible() ? '0' : '20px' }">
+        <div class="vertical-full">
+          <div class="vertical-full row-fluid panel-container">
+            % if not is_embeddable:
+            <div class="assist-container left-panel" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable()">
+              <a title="${_('Toggle Assist')}" class="pointer hide-assist" data-bind="click: function() { $root.isLeftPanelVisible(false) }">
+                <i class="fa fa-chevron-left"></i>
+              </a>
+              <div class="assist" data-bind="component: {
+                  name: 'assist-panel',
+                  params: {
+                    user: '${user.username}',
+                    sql: {
+                      navigationSettings: {
+                        openItem: false,
+                        showStats: true
+                      }
+                    },
+                    visibleAssistPanels: ['sql']
+                  }
+                }"></div>
+            </div>
+            <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
+            % endif
 
-        <!-- ko if: $root.interface() == 'dataware2-clusters' -->
-        <label for="clusterCreateWorkers">${ _('Workers') }</label>
-        <input id="clusterCreateWorkers" type="number" min="1" data-bind="value: jobs.createClusterWorkers, valueUpdate: 'afterkeydown'" class="input-mini" placeholder="${_('Size')}">
-        <label class="checkbox" style="float: right;">
-          <input type="checkbox" data-bind="checked: jobs.createClusterAutoPause"> ${ _('Auto pause') }
-        </label>
-        <label class="checkbox" style="margin-right: 10px; float: right;">
-          <input type="checkbox" data-bind="checked: jobs.createClusterAutoResize"> ${ _('Auto resize') }
-        </label>
-        <!-- /ko -->
-        <!-- ko if: $root.cluster() && $root.cluster()['type'] == 'altus-engines' -->
-        <label for="clusterCreateSize">${ _('Size') }</label>
-        <select id="clusterCreateSize" class="input-small" data-bind="visible: !jobs.createClusterShowWorkers()">
-          <option>${ _('X-Large') }</option>
-          <option>${ _('Large') }</option>
-          <option>${ _('Medium') }</option>
-          <option>${ _('Small') }</option>
-          <option>${ _('X-Small') }</option>
-        </select>
-        <label for="clusterCreateEnvironment">${ _('Environment') }</label>
-        <select id="clusterCreateEnvironment">
-          <option>AWS-finance-secure</option>
-          <option>GCE-east</option>
-          <option>OpenShift-prem</option>
-        </select>
-        <!-- /ko -->
-      </fieldset>
-    </form>
-    <div style="width: 100%; text-align: right;">
-      <button class="btn close-template-popover" title="${ _('Cancel') }">${ _('Cancel') }</button>
-      <button class="btn btn-primary close-template-popover" data-bind="click: jobs.createCluster, enable: jobs.createClusterName().length > 0 && jobs.createClusterWorkers() > 0" title="${ _('Start creation') }">
-        ${ _('Create') }
-      </button>
-    </div>
-  </script>
+            <div class="content-panel">
+              <div class="content-panel-inner">
+                <!-- ko if: $root.job() -->
+                <div data-bind="template: { name: 'jb-breadcrumbs' }"></div>
+                <!-- /ko -->
 
-  <script type="text/html" id="configure-cluster-content">
-    <form>
-      <fieldset>
-        <label for="clusterConfigureWorkers">${ _('Workers') }</label>
-        <span data-bind="visible: !updateClusterAutoResize()">
-          <input id="clusterConfigureWorkers" type="number" min="1" data-bind="value: updateClusterWorkers, valueUpdate: 'afterkeydown'" class="input-mini" placeholder="${_('Size')}">
-        </span>
-        <span data-bind="visible: updateClusterAutoResize()">
-          <input type="number" min="0" data-bind="value: updateClusterAutoResizeMin, valueUpdate: 'afterkeydown'" class="input-mini" placeholder="${_('Min')}">
-          <input type="number" min="0" data-bind="value: updateClusterAutoResizeMax, valueUpdate: 'afterkeydown'" class="input-mini" placeholder="${_('Max')}">
-          <input type="number" min="0" data-bind="value: updateClusterAutoResizeCpu, valueUpdate: 'afterkeydown'" class="input-mini" placeholder="${_('CPU')}">
-        </span>
+                <!-- ko if: interface() !== 'slas' && interface() !== 'oozie-info' && interface() !== 'hive-queries' && interface() !== 'impala-queries'-->
+                <!-- ko if: !$root.job() -->
+                <form class="form-inline">
+                  <!-- ko if: interface() == 'queries-impala' -->
+                    ${ _('Impala queries from') }
+                  <!-- /ko -->
+                  <!-- ko if: interface() != 'dataware2-clusters' && interface() != 'engines' -->
+                  <input type="text" class="input-large" data-bind="clearable: jobs.textFilter, valueUpdate: 'afterkeydown'" placeholder="${_('Filter by id, name, user...')}" />
+                    <!-- ko if: jobs.statesValuesFilter -->
+                    <span data-bind="foreach: jobs.statesValuesFilter">
+                      <label class="checkbox">
+                        <div class="pull-left margin-left-5 status-border status-content" data-bind="css: value, hueCheckbox: checked"></div>
+                        <div class="inline-block" data-bind="text: name, toggle: checked"></div>
+                      </label>
+                    </span>
+                    <!-- /ko -->
+                  <!-- /ko -->
 
-        <label class="checkbox" style="margin-right: 10px; float: right;">
-          <input type="checkbox" data-bind="checked: updateClusterAutoResize"> ${ _('Auto resize') }
-        </label>
-      </fieldset>
-    </form>
-    <div style="width: 100%; text-align: right;">
-      <button class="btn close-template-popover" title="${ _('Cancel') }">${ _('Cancel') }</button>
-      <button class="btn btn-primary close-template-popover" data-bind="click: updateCluster, enable: clusterConfigModified" title="${ _('Update') }">
-        ${ _('Update') }
-      </button>
-    </div>
-  </script>
+                  <!-- ko if: $root.interface() !== 'schedules' && $root.interface() !== 'bundles' -->
 
-  <div class="main-content">
-    <div class="vertical-full container-fluid" data-bind="style: { 'padding-left' : $root.isLeftPanelVisible() ? '0' : '20px' }">
-      <div class="vertical-full">
-        <div class="vertical-full row-fluid panel-container">
-          % if not is_embeddable:
-          <div class="assist-container left-panel" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable()">
-            <a title="${_('Toggle Assist')}" class="pointer hide-assist" data-bind="click: function() { $root.isLeftPanelVisible(false) }">
-              <i class="fa fa-chevron-left"></i>
-            </a>
-            <div class="assist" data-bind="component: {
-                name: 'assist-panel',
-                params: {
-                  user: '${user.username}',
-                  sql: {
-                    navigationSettings: {
-                      openItem: false,
-                      showStats: true
-                    }
-                  },
-                  visibleAssistPanels: ['sql']
-                }
-              }"></div>
-          </div>
-          <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
-          % endif
+                    <!-- ko if: $root.interface() && $root.interface().indexOf('engines') === -1 && $root.interface().indexOf('cluster') === -1 -->
+                    ${_('in the last')} <input class="input-mini no-margin" type="number" min="1" max="3650" data-bind="value: jobs.timeValueFilter">
+                    <select class="input-small no-margin" data-bind="value: jobs.timeUnitFilter, options: jobs.timeUnitFilterUnits, optionsText: 'name', optionsValue: 'value'">
+                      <option value="days">${_('days')}</option>
+                      <option value="hours">${_('hours')}</option>
+                      <option value="minutes">${_('minutes')}</option>
+                    </select>
 
-          <div class="content-panel">
-            <div class="content-panel-inner">
-              <!-- ko if: $root.job() -->
-              <div data-bind="template: { name: 'breadcrumbs${ SUFFIX }' }"></div>
-              <!-- /ko -->
+                    <a class="btn" title="${ _('Refresh') }" data-bind="click: function() { $root.jobs.updateJobs() }">
+                      <i class="fa fa-refresh"></i>
+                    </a>
+                    <!-- /ko -->
 
-              <!-- ko if: interface() !== 'slas' && interface() !== 'oozie-info' && interface() !== 'hive-queries' && interface() !== 'impala-queries'-->
-              <!-- ko if: !$root.job() -->
-              <form class="form-inline">
-                <!-- ko if: !$root.isMini() && interface() == 'queries-impala' -->
-                  ${ _('Impala queries from') }
-                <!-- /ko -->
-                <!-- ko if: interface() != 'dataware2-clusters' && interface() != 'engines' -->
-                <input type="text" class="input-large" data-bind="clearable: jobs.textFilter, valueUpdate: 'afterkeydown'" placeholder="${_('Filter by id, name, user...')}" />
-                  <!-- ko if: jobs.statesValuesFilter -->
-                  <span data-bind="foreach: jobs.statesValuesFilter">
-                    <label class="checkbox">
-                      <div class="pull-left margin-left-5 status-border status-content" data-bind="css: value, hueCheckbox: checked"></div>
-                      <div class="inline-block" data-bind="text: name, toggle: checked"></div>
-                    </label>
-                  </span>
+                    <a class="btn" title="${ _('Create cluster') }" data-bind="visible: $root.interface() == 'dataware2-clusters', templatePopover : { placement: 'bottom', contentTemplate: 'jb-create-cluster-content', minWidth: '320px', trigger: 'click' }, click: jobs.createClusterFormReset">
+                      <!-- ko if: $root.cluster() && $root.cluster()['type'] != 'altus-engines' -->
+                        ${ _('Add Warehouse') }
+                      <!-- /ko -->
+                      <!-- ko if: $root.cluster() && $root.cluster()['type'] == 'altus-engines' -->
+                        ${ _('Create / Register') }
+                      <!-- /ko -->
+                      <i class="fa fa-chevron-down"></i>
+                    </a>
                   <!-- /ko -->
-                <!-- /ko -->
 
-                <!-- ko ifnot: $root.isMini -->
-                <!-- ko if: $root.interface() !== 'schedules' && $root.interface() !== 'bundles' -->
+                  <div data-bind="template: { name: 'jb-job-actions', 'data': jobs }" class="pull-right"></div>
+                </form>
 
-                  <!-- ko if: $root.interface() && $root.interface().indexOf('engines') === -1 && $root.interface().indexOf('cluster') === -1 -->
-                  ${_('in the last')} <input class="input-mini no-margin" type="number" min="1" max="3650" data-bind="value: jobs.timeValueFilter">
-                  <select class="input-small no-margin" data-bind="value: jobs.timeUnitFilter, options: jobs.timeUnitFilterUnits, optionsText: 'name', optionsValue: 'value'">
-                    <option value="days">${_('days')}</option>
-                    <option value="hours">${_('hours')}</option>
-                    <option value="minutes">${_('minutes')}</option>
-                  </select>
+                <div data-bind="visible: jobs.showJobCountBanner" class="pull-center alert alert-warning">
+                  ${ _("Showing oldest %s jobs. Use days filter to get the recent ones.") % MAX_JOB_FETCH.get() }
+                </div>
 
-                  <a class="btn" title="${ _('Refresh') }" data-bind="click: jobs.updateJobs">
-                    <i class="fa fa-refresh"></i>
-                  </a>
+                <div class="card card-small">
+                  <!-- ko hueSpinner: { spin: jobs.loadingJobs(), center: true, size: 'xlarge' } --><!-- /ko -->
+                  <!-- ko ifnot: jobs.loadingJobs() -->
+                    <h4>${ _('Running') }</h4>
+                    <div data-bind="template: { name: 'jb-apps-list', data: { apps: jobs.runningApps, tableId: 'runningJobsTable', selectedJobs: jobs.selectedJobs} }" class="hue-horizontally-scrollable"></div>
+                    <h4>${ _('Completed') }</h4>
+                    <div data-bind="template: { name: 'jb-apps-list', data: { apps: jobs.finishedApps, tableId: 'completedJobsTable', selectedJobs: jobs.selectedJobs } }" class="hue-horizontally-scrollable"></div>
                   <!-- /ko -->
-
-                  <a class="btn" title="${ _('Create cluster') }" data-bind="visible: $root.interface() == 'dataware2-clusters', templatePopover : { placement: 'bottom', contentTemplate: 'create-cluster-content', minWidth: '320px', trigger: 'click' }, click: jobs.createClusterFormReset">
-                    <!-- ko if: $root.cluster() && $root.cluster()['type'] != 'altus-engines' -->
-                      ${ _('Add Warehouse') }
-                    <!-- /ko -->
-                    <!-- ko if: $root.cluster() && $root.cluster()['type'] == 'altus-engines' -->
-                      ${ _('Create / Register') }
-                    <!-- /ko -->
-                    <i class="fa fa-chevron-down"></i>
-                  </a>
+                </div>
                 <!-- /ko -->
 
-                <div data-bind="template: { name: 'job-actions${ SUFFIX }', 'data': jobs }" class="pull-right"></div>
-                <!-- /ko -->
-              </form>
+                <!-- ko if: $root.job() -->
+                <!-- ko with: $root.job() -->
+                  <!-- ko if: mainType() == 'history' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-history-page' }"></div>
+                  <!-- /ko -->
 
-              <div data-bind="visible: jobs.showJobCountBanner" class="pull-center alert alert-warning">
-                ${ _("Showing oldest %s jobs. Use days filter to get the recent ones.") % MAX_JOB_FETCH.get() }
-              </div>
+                  <!-- ko if: mainType() == 'jobs' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-job-page' }"></div>
+                  <!-- /ko -->
 
-              <div class="card card-small">
-                <!-- ko hueSpinner: { spin: jobs.loadingJobs(), center: true, size: 'xlarge' } --><!-- /ko -->
-                <!-- ko ifnot: jobs.loadingJobs() -->
-                  <!-- ko if: $root.isMini -->
-                  <ul class="unstyled status-border-container" id="jobsTable" data-bind="foreach: jobs.apps">
-                    <li class="status-border pointer" data-bind="
-                      css: {
-                        'completed': apiStatus() == 'SUCCEEDED',
-                        'info': apiStatus() === 'PAUSED',
-                        'running': apiStatus() === 'RUNNING',
-                        'failed': apiStatus() == 'FAILED'
-                      },
-                      click: function (data, event) {
-                        onTableRowClick(event, id());
-                      }">
-                      <span class="muted pull-left" data-bind="momentFromNow: {data: submitted, interval: 10000, titleFormat: 'LLL'}"></span><span class="muted">&nbsp;-&nbsp;</span><span class="muted" data-bind="text: status"></span></td>
-                      <span class="muted pull-right" data-bind="text: duration().toHHMMSS()"></span>
-                      <div class="clearfix"></div>
-                      <strong class="pull-left" data-bind="text: type"></strong>
-                      <div class="inline-block pull-right"><i class="fa fa-user muted"></i> <span data-bind="text: user"></span></div>
-                      <div class="clearfix"></div>
-                      <div class="pull-left" data-bind="ellipsis: {data: name(), length: 40 }"></div>
-                      <div class="pull-right muted" data-bind="ellipsis: {data: id(), length: 32 }"></div>
-                      <div class="clearfix"></div>
-                    </li>
-                    <div class="status-bar status-background" data-bind="css: {'running': isRunning()}, style: {'width': progress() + '%'}"></div>
-                  </ul>
+                  <!-- ko if: mainType() == 'queries-impala' || mainType() == 'queries-hive' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-queries-page' }"></div>
                   <!-- /ko -->
-                  <!-- ko ifnot: $root.isMini -->
-                  <h4>${ _('Running') }</h4>
-                  <div data-bind="template: { name: 'apps-list${ SUFFIX }', data: { apps: jobs.runningApps, tableId: 'runningJobsTable', selectedJobs: jobs.selectedJobs} }" class="hue-horizontally-scrollable"></div>
-                  <h4>${ _('Completed') }</h4>
-                  <div data-bind="template: { name: 'apps-list${ SUFFIX }', data: { apps: jobs.finishedApps, tableId: 'completedJobsTable', selectedJobs: jobs.selectedJobs } }" class="hue-horizontally-scrollable"></div>
+
+                  <!-- ko if: mainType() == 'celery-beat' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-celery-beat-page' }"></div>
                   <!-- /ko -->
-                <!-- /ko -->
-              </div>
-              <!-- /ko -->
 
-              <!-- ko if: $root.job() -->
-              <!-- ko with: $root.job() -->
-                <!-- ko if: mainType() == 'history' -->
-                  <div class="jb-panel" data-bind="template: { name: 'history-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                  <!-- ko if: mainType() == 'schedule-hive' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-schedule-hive-page' }"></div>
+                  <!-- /ko -->
 
-                <!-- ko if: mainType() == 'jobs' -->
-                  <div class="jb-panel" data-bind="template: { name: 'job-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                  <!-- ko if: mainType() == 'workflows' -->
+                    <!-- ko if: type() == 'workflow' -->
+                      <div class="jb-panel" data-bind="template: { name: 'jb-workflow-page' }"></div>
+                    <!-- /ko -->
 
-                <!-- ko if: mainType() == 'queries-impala' || mainType() == 'queries-hive' -->
-                  <div class="jb-panel" data-bind="template: { name: 'queries-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                    <!-- ko if: type() == 'workflow-action' -->
+                      <div class="jb-panel" data-bind="template: { name: 'jb-workflow-action-page' }"></div>
+                    <!-- /ko -->
+                  <!-- /ko -->
 
-                <!-- ko if: mainType() == 'celery-beat' -->
-                  <div class="jb-panel" data-bind="template: { name: 'celery-beat-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                  <!-- ko if: mainType() == 'schedules' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-schedule-page' }"></div>
+                  <!-- /ko -->
 
-                <!-- ko if: mainType() == 'schedule-hive' -->
-                  <div class="jb-panel" data-bind="template: { name: 'schedule-hive-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                  <!-- ko if: mainType() == 'bundles' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-bundle-page' }"></div>
+                  <!-- /ko -->
 
-                <!-- ko if: mainType() == 'workflows' -->
-                  <!-- ko if: type() == 'workflow' -->
-                    <div class="jb-panel" data-bind="template: { name: 'workflow-page${ SUFFIX }' }"></div>
+                  <!-- ko if: mainType().startsWith('dataeng-job') -->
+                    <div data-bind="template: { name: 'jb-dataeng-job-page' }"></div>
                   <!-- /ko -->
 
-                  <!-- ko if: type() == 'workflow-action' -->
-                    <div class="jb-panel" data-bind="template: { name: 'workflow-action-page${ SUFFIX }' }"></div>
+                  <!-- ko if: mainType() == 'dataeng-clusters' || mainType() == 'dataware-clusters' -->
+                    <div data-bind="template: { name: 'jb-dataware-clusters-page' }"></div>
                   <!-- /ko -->
-                <!-- /ko -->
 
-                <!-- ko if: mainType() == 'schedules' -->
-                  <div class="jb-panel" data-bind="template: { name: 'schedule-page${ SUFFIX }' }"></div>
-                <!-- /ko -->
+                  <!-- ko if: mainType() == 'dataware2-clusters' -->
+                    <div data-bind="template: { name: 'jb-dataware2-clusters-page' }"></div>
+                  <!-- /ko -->
 
-                <!-- ko if: mainType() == 'bundles' -->
-                  <div class="jb-panel" data-bind="template: { name: 'bundle-page${ SUFFIX }' }"></div>
+                  <!-- ko if: mainType() == 'livy-sessions' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-livy-session-page' }"></div>
+                  <!-- /ko -->
                 <!-- /ko -->
-
-                <!-- ko if: mainType().startsWith('dataeng-job') -->
-                  <div data-bind="template: { name: 'dataeng-job-page${ SUFFIX }' }"></div>
                 <!-- /ko -->
 
-                <!-- ko if: mainType() == 'dataeng-clusters' || mainType() == 'dataware-clusters' -->
-                  <div data-bind="template: { name: 'dataware-clusters-page${ SUFFIX }' }"></div>
+                <!-- ko if: $root.job() && !$root.job().forceUpdatingJob() && $root.job().hasPagination() && interface() === 'schedules' -->
+                <div data-bind="template: { name: 'jb-pagination', data: $root.job() }, visible: !jobs.loadingJobs()"></div>
                 <!-- /ko -->
-
-                <!-- ko if: mainType() == 'dataware2-clusters' -->
-                  <div data-bind="template: { name: 'dataware2-clusters-page${ SUFFIX }' }"></div>
+                <div data-bind="template: { name: 'jb-pagination', data: $root.jobs }, visible: !$root.job() && !jobs.loadingJobs()"></div>
                 <!-- /ko -->
 
-                <!-- ko if: mainType() == 'livy-sessions' -->
-                  <div class="jb-panel" data-bind="template: { name: 'livy-session-page${ SUFFIX }' }"></div>
+                <!-- ko if: interface() === 'slas' -->
+                  <!-- ko hueSpinner: { spin: slasLoading(), center: true, size: 'xlarge' } --><!-- /ko -->
                 <!-- /ko -->
-              <!-- /ko -->
-              <!-- /ko -->
-
-              <!-- ko if: $root.job() && !$root.job().forceUpdatingJob() && $root.job().hasPagination() && interface() === 'schedules' -->
-              <div data-bind="template: { name: 'pagination${ SUFFIX }', data: $root.job() }, visible: !jobs.loadingJobs()"></div>
-              <!-- /ko -->
-              <div data-bind="template: { name: 'pagination${ SUFFIX }', data: $root.jobs }, visible: !$root.job() && !jobs.loadingJobs()"></div>
-              <!-- /ko -->
-
-              % if not is_mini:
-              <!-- ko if: interface() === 'slas' -->
-                <!-- ko hueSpinner: { spin: slasLoading(), center: true, size: 'xlarge' } --><!-- /ko -->
-              <!-- /ko -->
-              <div id="slas" data-bind="visible: interface() === 'slas'"></div>
+                <div id="slas" data-bind="visible: interface() === 'slas'"></div>
 
-              <div data-bind="template: { name: 'hive-queries-template', if: interface() === 'hive-queries' }"></div>
+                <div data-bind="template: { name: 'jb-hive-queries-template', if: interface() === 'hive-queries' }"></div>
 
-              <div data-bind="template: { name: 'impala-queries-template', if: interface() === 'impala-queries' }"></div>
+                <div data-bind="template: { name: 'jb-impala-queries-template', if: interface() === 'impala-queries' }"></div>
 
-              <!-- ko if: interface() === 'oozie-info' -->
-                <!-- ko hueSpinner: { spin: oozieInfoLoading(), center: true, size: 'xlarge' } --><!-- /ko -->
-              <!-- /ko -->
-              <div id="oozieInfo" data-bind="visible: interface() === 'oozie-info'"></div>
-              %endif
+                <!-- ko if: interface() === 'oozie-info' -->
+                  <!-- ko hueSpinner: { spin: oozieInfoLoading(), center: true, size: 'xlarge' } --><!-- /ko -->
+                <!-- /ko -->
+                <div id="oozieInfo" data-bind="visible: interface() === 'oozie-info'"></div>
+              </div>
             </div>
           </div>
         </div>
       </div>
     </div>
-  </div>
 
-  <!-- ko if: $root.job() -->
-    <div id="rerun-modal${ SUFFIX }" class="modal hide" data-bind="htmlUnsecure: $root.job().rerunModalContent"></div>
-  <!-- /ko -->
-
-  <!-- ko if: ($root.job() && $root.job().hasKill()) || (!$root.job() && $root.jobs.hasKill()) -->
-    <div id="killModal${ SUFFIX }" class="modal hide">
-      <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
-        <h2 class="modal-title">${_('Confirm Kill')}</h2>
-      </div>
-      <div class="modal-body">
-        <p>${_('Are you sure you want to kill the selected job(s)?')}</p>
-      </div>
-      <div class="modal-footer">
-        <a class="btn" data-dismiss="modal">${_('No')}</a>
-        <a id="killJobBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ if (job()) { job().control('kill'); } else { jobs.control('kill'); } }">${_('Yes')}</a>
-      </div>
-    </div>
-  <!-- /ko -->
+    <!-- ko if: $root.job() -->
+      <div id="rerun-modal" class="modal hide" data-bind="htmlUnsecure: $root.job().rerunModalContent"></div>
+    <!-- /ko -->
 
-  <!-- ko if: $root.job() && $root.job().type() === 'schedule' -->
-    <div id="syncCoordinatorModal${ SUFFIX }" class="modal hide">
-      <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
-        <h2 class="modal-title confirmation_header">${ _('Update Coordinator Job Properties') }</h2>
-      </div>
-      <div id="update-coord" class="span10">
-        <div class="control-group">
-          <label class="control-label">${ _('End Time') }</label>
-          <div class="controls">
-            <div class="input-prepend input-group">
-              <span class="add-on input-group-addon">
-                <i class="fa fa-calendar"></i>
-              </span>
-              <input id="endTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeDateUI, datepicker: {}" />
-            </div>
-            <div class="input-prepend input-group">
-              <span class="add-on input-group-addon">
-                <i class="fa fa-clock-o"></i>
-              </span>
-              <input id="endTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeTimeUI, timepicker: {}" />
-            </div>
-            <span class="help-inline"></span>
-          </div>
-        </div>
-        <div class="control-group">
-          <label class="control-label">${ _('Pause Time') }</label>
-          <div class="controls">
-            <div class="input-prepend input-group">
-              <span class="add-on input-group-addon">
-                <i class="fa fa-calendar"></i>
-              </span>
-              <input id="pauseTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeDateUI, datepicker: {}" />
-            </div>
-            <div class="input-prepend input-group">
-              <span class="add-on input-group-addon">
-                <i class="fa fa-clock-o"></i>
-              </span>
-              <input id="pauseTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeTimeUI, timepicker: {}" />
-            </div>
-            <span class="help-inline"></span>
-          </div>
+    <!-- ko if: ($root.job() && $root.job().hasKill()) || (!$root.job() && $root.jobs.hasKill()) -->
+      <div id="killModal" class="modal hide">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
+          <h2 class="modal-title">${_('Confirm Kill')}</h2>
         </div>
-        <div class="control-group ">
-          <label class="control-label">Clear Pause Time</label>
-          <div class="controls">
-            <input id="id_clearPauseTime" class="disable-autofocus" name="clearPauseTime" type="checkbox">
-          </div>
+        <div class="modal-body">
+          <p>${_('Are you sure you want to kill the selected job(s)?')}</p>
         </div>
-        <div class="control-group ">
-          <label class="control-label">Concurrency</label>
-          <div class="controls">
-            <input id="id_concurrency" class="disable-autofocus" name="concurrency" type="number" data-bind="value: $root.job().syncCoorConcurrency">
-          </div>
+        <div class="modal-footer">
+          <a class="btn" data-dismiss="modal">${_('No')}</a>
+          <a id="killJobBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ if (job()) { job().control('kill'); } else { jobs.control('kill'); } }">${_('Yes')}</a>
         </div>
       </div>
-      <div class="modal-body">
-          <p class="confirmation_body"></p>
-      </div>
-      <div class="modal-footer update">
-        <a href="#" class="btn" data-dismiss="modal">Cancel</a>
-        <a id="syncCoorBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ job().control('sync_coordinator'); }">${_('Update')}</a>
-      </div>
-    </div>
-
-    <div id="syncWorkflowModal${ SUFFIX }" class="modal hide">
-    </div>
-  <!-- /ko -->
-</div>
-
-<script type="text/html" id="hive-queries-template">
-  <queries-list></queries-list>
-</script>
-
-<script type="text/html" id="impala-queries-template">
-  <impala-queries></impala-queries>
-</script>
-
-<script type="text/html" id="breadcrumbs-icons${ SUFFIX }">
-<!-- ko switch: type -->
-  <!-- ko case: 'workflow' -->
-    <img src="${ static('oozie/art/icon_oozie_workflow_48.png') }" class="app-icon" alt="${ _('Oozie workflow icon') }"/>
-  <!-- /ko -->
-  <!-- ko case: 'workflow-action' -->
-    <i class="fa fa-fw fa-code-fork"></i>
-  <!-- /ko -->
-  <!-- ko case: 'schedule' -->
-    <img src="${ static('oozie/art/icon_oozie_coordinator_48.png') }" class="app-icon" alt="${ _('Oozie coordinator icon') }"/>
-  <!-- /ko -->
-  <!-- ko case: 'bundle' -->
-    <img src="${ static('oozie/art/icon_oozie_bundle_48.png') }" class="app-icon" alt="${ _('Oozie bundle icon') }"/>
-  <!-- /ko -->
-<!-- /ko -->
-</script>
-
-
-<script type="text/html" id="breadcrumbs${ SUFFIX }">
-  <h3 class="jb-breadcrumbs">
-    <ul class="inline hue-breadcrumbs-bar">
-      <!-- ko foreach: breadcrumbs -->
-        <li>
-        <!-- ko if: $index() > 1 -->
-          <span class="divider">&gt;</span>
-        <!-- /ko -->
-
-        <!-- ko if: $index() != 0 -->
-          <!-- ko if: $index() != $parent.breadcrumbs().length - 1 -->
-            <a href="javascript:void(0)" data-bind="click: function() { $parent.breadcrumbs.splice($index()); $root.job().id(id); $root.job().fetchJob(); }">
-            <span data-bind="template: 'breadcrumbs-icons${ SUFFIX }'"></span>
-            <span data-bind="text: name"></span></a>
-          <!-- /ko -->
-          <!-- ko if: $index() == $parent.breadcrumbs().length - 1 -->
-            <span data-bind="template: 'breadcrumbs-icons${ SUFFIX }'"></span>
-            <span data-bind="text: name, attr: { title: id }"></span>
-          <!-- /ko -->
-        <!-- /ko -->
-        </li>
-      <!-- /ko -->
-
-      <!-- ko if: !$root.isMini() -->
-        <!-- ko if: ['workflows', 'schedules', 'bundles', 'slas'].indexOf(interface()) > -1 -->
-        <li class="pull-right">
-          <a href="javascript:void(0)" data-bind="click: function() { $root.selectInterface('oozie-info') }">${ _('Configuration') }</a>
-        </li>
-        <!-- /ko -->
-      <!-- /ko -->
-    </ul>
-  </h3>
-</script>
-
-
-<script type="text/html" id="pagination${ SUFFIX }">
-  <!-- ko ifnot: hasPagination -->
-  <div class="inline">
-    <span data-bind="text: totalApps()"></span>
-    <!-- ko if: $root.interface() === 'dataware2-clusters' -->
-    ${ _('warehouses') }
     <!-- /ko -->
-    <!-- ko if: $root.interface() !== 'dataware2-clusters' -->
-    ${ _('jobs') }
-    <!-- /ko -->
-  </div>
-  <!-- /ko -->
-
-  <!-- ko if: hasPagination -->
-  <div class="inline">
-    <div class="inline">
-      ${ _('Showing') }
-      <span data-bind="text: Math.min(paginationOffset(), totalApps())"></span>
-      ${ _('to')}
-      <span data-bind="text: Math.min(paginationOffset() - 1 + paginationResultPage(), totalApps())"></span>
-      ${ _('of') }
-      <span data-bind="text: totalApps"></span>
-
-      ##${ _('Show')}
-      ##<span data-bind="text: paginationResultPage"></span>
-      ##${ _('results by page.') }
-    </div>
-
-    <ul class="inline">
-      <li class="previous-page" data-bind="visible: showPreviousPage">
-        <a href="javascript:void(0);" data-bind="click: previousPage" title="${_('Previous page')}"><i class="fa fa-backward"></i></a>
-      </li>
-      <li class="next-page" data-bind="visible: showNextPage">
-        <a href="javascript:void(0);" data-bind="click: nextPage" title="${_('Next page')}"><i class="fa fa-forward"></i></a>
-      </li>
-    </ul>
-  </div>
-  <!-- /ko -->
-</script>
-
-<script type="text/html" id="job-page${ SUFFIX }">
-  <!-- ko if: type() == 'MAPREDUCE' -->
-    <div data-bind="template: { name: 'job-mapreduce-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'MAP' || type() == 'REDUCE' -->
-    <div data-bind="template: { name: 'job-mapreduce-task-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'MAP_ATTEMPT' || type() == 'REDUCE_ATTEMPT' -->
-    <div data-bind="template: { name: 'job-mapreduce-task-attempt-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'YARN' -->
-    <div data-bind="template: { name: 'job-yarn-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'YarnV2' -->
-    <div data-bind="template: { name: 'job-yarnv2-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'YarnV2_ATTEMPT' -->
-    <div data-bind="template: { name: 'job-yarnv2-attempt-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'SPARK' -->
-    <div data-bind="template: { name: 'job-spark-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-
-  <!-- ko if: type() == 'SPARK_EXECUTOR' -->
-    <div data-bind="template: { name: 'job-spark-executor-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-</script>
-
-
-<script type="text/html" id="history-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
-              <span data-bind="text: name"></span>
-            </a>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        <li>
-          <a href="#history-page-statements${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#history-page-statements${ SUFFIX }\']').tab('show'); }">
-            ${ _('Properties') }
-          </a>
-        </li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
 
-      <div class="tab-content">
-        <div class="tab-pane active" id="history-page-statements${ SUFFIX }">
-          <table id="actionsTable" class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Id')}</th>
-              <th>${_('State')}</th>
-              <th>${_('Output')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['statements']">
-              <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
-                <td>
-                  <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
-                    <i class="fa fa-tasks"></i>
-                  </a>
-                </td>
-                <td data-bind="text: state"></td>
-                <td data-bind="text: output"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="job-yarn-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+    <!-- ko if: $root.job() && $root.job().type() === 'schedule' -->
+      <div id="syncCoordinatorModal" class="modal hide">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
+          <h2 class="modal-title confirmation_header">${ _('Update Coordinator Job Properties') }</h2>
+        </div>
+        <div id="update-coord" class="span10">
+          <div class="control-group">
+            <label class="control-label">${ _('End Time') }</label>
+            <div class="controls">
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-calendar"></i>
+                </span>
+                <input id="endTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeDateUI, datepicker: {}" />
+              </div>
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-clock-o"></i>
+                </span>
+                <input id="endTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeTimeUI, timepicker: {}" />
+              </div>
+              <span class="help-inline"></span>
             </div>
-          </li>
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="text: submitted"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <div class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="job-mapreduce-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini()}">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li><span data-bind="text: id"></span></li>
-          <li data-bind="visible: id() != name() && ! $root.isMini()" class="nav-header">${ _('Name') }</li>
-          <li data-bind="visible: id() != name() && ! $root.isMini(), attr: {title: name}"><span data-bind="text: name"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li data-bind="visible: ! $root.isMini()" class="nav-header">${ _('Status') }</li>
-          <li data-bind="visible: ! $root.isMini()"><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+          </div>
+          <div class="control-group">
+            <label class="control-label">${ _('Pause Time') }</label>
+            <div class="controls">
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-calendar"></i>
+                </span>
+                <input id="pauseTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeDateUI, datepicker: {}" />
+              </div>
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-clock-o"></i>
+                </span>
+                <input id="pauseTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeTimeUI, timepicker: {}" />
+              </div>
+              <span class="help-inline"></span>
             </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-          <!-- ko with: properties -->
-            <li class="nav-header">${ _('Map') }</li>
-            <li><span data-bind="text: maps_percent_complete"></span>% <span data-bind="text: finishedMaps"></span> /<span data-bind="text: desiredMaps"></span></li>
-            <li class="nav-header">${ _('Reduce') }</li>
-            <li><span data-bind="text: reduces_percent_complete"></span>% <span data-bind="text: finishedReduces"></span> / <span data-bind="text: desiredReduces"></span></li>
-            <li class="nav-header">${ _('Duration') }</li>
-            <li><span data-bind="text: durationFormatted"></span></li>
-            <li class="nav-header">${ _('Submitted') }</li>
-            <li><span data-bind="text: startTimeFormatted"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="jb-logs-link" href="#job-mapreduce-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#job-mapreduce-page-tasks${ SUFFIX }" data-bind="click: function(){ fetchProfile('tasks'); $('a[href=\'#job-mapreduce-page-tasks${ SUFFIX }\']').tab('show'); }">${ _('Tasks') }</a></li>
-        <li><a href="#job-mapreduce-page-metadata${ SUFFIX }" data-bind="click: function(){ fetchProfile('metadata'); $('a[href=\'#job-mapreduce-page-metadata${ SUFFIX }\']').tab('show'); }">${ _('Metadata') }</a></li>
-        <li><a href="#job-mapreduce-page-counters${ SUFFIX }" data-bind="click: function(){ fetchProfile('counters'); $('a[href=\'#job-mapreduce-page-counters${ SUFFIX }\']').tab('show'); }">${ _('Counters') }</a></li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-mapreduce-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs">
-          % for name in ['default', 'stdout', 'stderr', 'syslog']:
-            <li class="${ name == 'default' and 'active' or '' }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $(e.currentTarget).parent().siblings().removeClass('active'); $(e.currentTarget).parent().addClass('active'); fetchLogs('${ name }'); logActive('${ name }'); }, text: '${ name }'"></a></li>
-          % endfor
-          </ul>
-          <!-- ko if: properties.diagnostics() -->
-            <pre data-bind="text: properties.diagnostics"></pre>
-          <!-- /ko -->
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane" id="job-mapreduce-page-tasks${ SUFFIX }">
-          <form class="form-inline">
-            <input data-bind="textFilter: textFilter, clearable: {value: textFilter}, valueUpdate: 'afterkeydown'" type="text" class="input-xlarge search-query" placeholder="${_('Filter by name')}">
-
-            <span data-bind="foreach: statesValuesFilter">
-              <label class="checkbox">
-                <div class="pull-left margin-left-5 status-border status-content" data-bind="css: value, hueCheckbox: checked"></div>
-                <div class="inline-block" data-bind="text: name, toggle: checked"></div>
-              </label>
-            </span>
-
-            <span data-bind="foreach: typesValuesFilter" class="margin-left-30">
-              <label class="checkbox">
-                <div class="pull-left margin-left-5" data-bind="css: value, hueCheckbox: checked"></div>
-                <div class="inline-block" data-bind="text: name, toggle: checked"></div>
-              </label>
-            </span>
-          </form>
-
-          <table class="table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Type')}</th>
-              <th>${_('Id')}</th>
-              <th>${_('Elapsed Time')}</th>
-              <th>${_('Progress')}</th>
-              <th>${_('State')}</th>
-              <th>${_('Start Time')}</th>
-              <th>${_('Successful Attempt')}</th>
-              <th>${_('Finish Time')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['tasks']()['task_list']">
-              <tr data-bind="click: function() { $root.job().id(id); $root.job().fetchJob(); }, css: {'completed': apiStatus == 'SUCCEEDED', 'running': apiStatus == 'RUNNING', 'failed': apiStatus == 'FAILED'}" class="status-border pointer">
-                <td data-bind="text: type"></td>
-                <td data-bind="text: id"></td>
-                <td data-bind="text: elapsedTime.toHHMMSS()"></td>
-                <td data-bind="text: progress"></td>
-                <td><span class="label job-status-label" data-bind="text: state"></span></td>
-                <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
-                <td data-bind="text: successfulAttempt"></td>
-                <td data-bind="moment: {data: finishTime, format: 'LLL'}"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="tab-pane" id="job-mapreduce-page-metadata${ SUFFIX }">
-          <div data-bind="template: { name: 'render-metadata${ SUFFIX }', data: properties['metadata'] }"></div>
-        </div>
-
-        <div class="tab-pane" id="job-mapreduce-page-counters${ SUFFIX }">
-          <div data-bind="template: { name: 'render-page-counters${ SUFFIX }', data: properties['counters'] }"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="job-mapreduce-task-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+          </div>
+          <div class="control-group ">
+            <label class="control-label">Clear Pause Time</label>
+            <div class="controls">
+              <input id="id_clearPauseTime" class="disable-autofocus" name="clearPauseTime" type="checkbox">
             </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-          <!-- ko with: properties -->
-            <li data-bind="visible: ! $root.isMini()" class="nav-header">${ _('State') }</li>
-            <li data-bind="visible: ! $root.isMini()"><span data-bind="text: state"></span></li>
-            <li class="nav-header">${ _('Start time') }</li>
-            <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Successful attempt') }</li>
-            <li><span data-bind="text: successfulAttempt"></span></li>
-            <li class="nav-header">${ _('Finish time') }</li>
-            <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Elapsed time') }</li>
-            <li><span data-bind="text: elapsedTime().toHHMMSS()"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="jb-logs-link" href="#job-mapreduce-task-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#job-mapreduce-task-page-attempts${ SUFFIX }" data-bind="click: function(){ fetchProfile('attempts'); $('a[href=\'#job-mapreduce-task-page-attempts${ SUFFIX }\']').tab('show'); }">${ _('Attempts') }</a></li>
-        <li><a href="#job-mapreduce-task-page-counters${ SUFFIX }" data-bind="click: function(){ fetchProfile('counters'); $('a[href=\'#job-mapreduce-task-page-counters${ SUFFIX }\']').tab('show'); }">${ _('Counters') }</a></li>
-      </ul>
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-mapreduce-task-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs">
-          % for name in ['stdout', 'stderr', 'syslog']:
-            <li class="${ name == 'stdout' and 'active' or '' }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $(e.currentTarget).parent().siblings().removeClass('active'); $(e.currentTarget).parent().addClass('active'); fetchLogs('${ name }'); logActive('${ name }'); }, text: '${ name }'"></a></li>
-          % endfor
-          </ul>
-
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane" id="job-mapreduce-task-page-attempts${ SUFFIX }">
-
-          <table class="table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Assigned Container Id')}</th>
-              <th>${_('Progress')}</th>
-              <th>${_('Elapsed Time')}</th>
-              <th>${_('State')}</th>
-              <th>${_('Rack')}</th>
-              <th>${_('Node Http Address')}</th>
-              <th>${_('Type')}</th>
-              <th>${_('Start Time')}</th>
-              <th>${_('Id')}</th>
-              <th>${_('Finish Time')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['attempts']()['task_list']">
-              <tr class="pointer" data-bind="click: function() { $root.job().id(id); $root.job().fetchJob(); }">
-                <td data-bind="text: assignedContainerId"></td>
-                <td data-bind="text: progress"></td>
-                <td data-bind="text: elapsedTime.toHHMMSS()"></td>
-                <td data-bind="text: state"></td>
-                <td data-bind="text: rack"></td>
-                <td data-bind="text: nodeHttpAddress"></td>
-                <td data-bind="text: type"></td>
-                <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
-                <td data-bind="text: id"></td>
-                <td data-bind="moment: {data: finishTime, format: 'LLL'}"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="tab-pane" id="job-mapreduce-task-page-counters${ SUFFIX }">
-          <div data-bind="template: { name: 'render-task-counters${ SUFFIX }', data: properties['counters'] }"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-<script type="text/html" id="job-yarnv2-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: applicationType"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() == 'RUNNING', 'progress-success': apiStatus() == 'SUCCEEDED', 'progress-danger': apiStatus() == 'FAILED'}, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+          </div>
+          <div class="control-group ">
+            <label class="control-label">Concurrency</label>
+            <div class="controls">
+              <input id="id_concurrency" class="disable-autofocus" name="concurrency" type="number" data-bind="value: $root.job().syncCoorConcurrency">
             </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-          <li class="nav-header">${ _('State') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <!-- ko with: properties -->
-            <li class="nav-header">${ _('Start time') }</li>
-            <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Finish time') }</li>
-            <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Elapsed time') }</li>
-            <li><span data-bind="text: elapsedTime().toHHMMSS()"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="jb-logs-link" href="#job-yarnv2-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#job-yarnv2-page-attempts${ SUFFIX }" data-bind="click: function(){ fetchProfile('attempts'); $('a[href=\'#job-yarnv2-page-attempts${ SUFFIX }\']').tab('show'); }">${ _('Attempts') }</a></li>
-      </ul>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-yarnv2-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs scrollable" data-bind="foreach: logsList">
-            <li data-bind="css: { 'active': $data == $parent.logActive() }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $parent.fetchLogs($data); $parent.logActive($data); }, text: $data"></a></li>
-          </ul>
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane" id="job-yarnv2-page-attempts${ SUFFIX }">
-          <table class="table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Assigned Container Id')}</th>
-              <th>${_('Node Id')}</th>
-              <th>${_('Application Attempt Id')}</th>
-              <th>${_('Start Time')}</th>
-              <th>${_('Finish Time')}</th>
-              <th>${_('Node Http Address')}</th>
-              <th>${_('Blacklisted Nodes')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['attempts']()['task_list']">
-              <tr class="pointer" data-bind="click: function() { $root.job().id(appAttemptId); $root.job().fetchJob(); }">
-                <td data-bind="text: containerId"></td>
-                <td data-bind="text: nodeId"></td>
-                <td data-bind="text: id"></td>
-                <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
-                <td data-bind="moment: {data: finishedTime, format: 'LLL'}"></td>
-                <td data-bind="text: nodeHttpAddress"></td>
-                <td data-bind="text: blacklistedNodes"></td>
-              </tr>
-            </tbody>
-          </table>
+          </div>
         </div>
-
-      </div>
-
-    </div>
-  </div>
-</script>
-
-<script type="text/html" id="job-yarnv2-attempt-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <!-- ko with: properties -->
-          <li class="nav-header">${ _('Attempt Id') }</li>
-          <li class="break-word"><span data-bind="text: appAttemptId"></span></li>
-          <li class="nav-header">${ _('State') }</li>
-          <li><span data-bind="text: state"></span></li>
-          <!-- ko if: !$root.isMini() -->
-          <li class="nav-header">${ _('Start time') }</li>
-          <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
-          <li class="nav-header">${ _('Node Http Address') }</li>
-          <li><span data-bind="text: nodeHttpAddress"></span></li>
-          <li class="nav-header">${ _('Elapsed time') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
-    </div>
-  </div>
-</script>
-
-<script type="text/html" id="job-mapreduce-task-attempt-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() == 'RUNNING', 'progress-success': apiStatus() == 'SUCCEEDED', 'progress-danger': apiStatus() == 'FAILED'}, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-          <!-- ko with: properties -->
-            <li class="nav-header">${ _('State') }</li>
-            <li><span data-bind="text: state"></span></li>
-            <li class="nav-header">${ _('Assigned Container ID') }</li>
-            <li><span data-bind="text: assignedContainerId"></span></li>
-            <li class="nav-header">${ _('Rack') }</li>
-            <li><span data-bind="text: rack"></span></li>
-            <li class="nav-header">${ _('Node HTTP address') }</li>
-            <li><span data-bind="text: nodeHttpAddress"></span></li>
-            <li class="nav-header">${ _('Start time') }</li>
-            <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Finish time') }</li>
-            <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
-            <li class="nav-header">${ _('Elapsed time') }</li>
-            <li><span data-bind="text: elapsedTime().toHHMMSS()"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="jb-logs-link" href="#job-mapreduce-task-attempt-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#job-mapreduce-task-attempt-page-counters${ SUFFIX }" data-bind="click: function(){ fetchProfile('counters'); $('a[href=\'#job-mapreduce-task-attempt-page-counters${ SUFFIX }\']').tab('show'); }">${ _('Counters') }</a></li>
-      </ul>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-mapreduce-task-attempt-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs">
-          % for name in ['stdout', 'stderr', 'syslog']:
-            <li class="${ name == 'stdout' and 'active' or '' }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $(e.currentTarget).parent().siblings().removeClass('active'); $(e.currentTarget).parent().addClass('active'); fetchLogs('${ name }'); logActive('${ name }'); }, text: '${ name }'"></a></li>
-          % endfor
-          </ul>
-          <pre data-bind="html: logs, logScroller: logs"></pre>
+        <div class="modal-body">
+            <p class="confirmation_body"></p>
         </div>
-
-        <div class="tab-pane" id="job-mapreduce-task-attempt-page-counters${ SUFFIX }">
-          <div data-bind="template: { name: 'render-attempt-counters${ SUFFIX }', data: properties['counters'] }"></div>
+        <div class="modal-footer update">
+          <a href="#" class="btn" data-dismiss="modal">Cancel</a>
+          <a id="syncCoorBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ job().control('sync_coordinator'); }">${_('Update')}</a>
         </div>
       </div>
 
-    </div>
+      <div id="syncWorkflowModal" class="modal hide"></div>
+    <!-- /ko -->
   </div>
-</script>
-
-<script type="text/html" id="job-spark-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="text: submitted"></span></li>
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="job-spark-logs-link" href="#job-spark-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#job-spark-page-executors${ SUFFIX }" data-bind="click: function(){ fetchProfile('executors'); $('a[href=\'#job-spark-page-executors${ SUFFIX }\']').tab('show'); }">${ _('Executors') }</a></li>
-        <li><a href="#job-spark-page-properties${ SUFFIX }" data-toggle="tab">${ _('Properties') }</a></li>
-
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-spark-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs">
-          % for name in ['stdout', 'stderr']:
-            <li class="${ name == 'stdout' and 'active' or '' }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $(e.currentTarget).parent().siblings().removeClass('active'); $(e.currentTarget).parent().addClass('active'); fetchLogs('${ name }'); logActive('${ name }'); }, text: '${ name }'"></a></li>
-          % endfor
-          </ul>
-
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-        <div class="tab-pane" id="job-spark-page-executors${ SUFFIX }">
-          <form class="form-inline">
-            <input data-bind="textFilter: textFilter, clearable: {value: textFilter}, valueUpdate: 'afterkeydown'" type="text" class="input-xlarge search-query" placeholder="${_('Filter by name')}">
-          </form>
-
-          <table class="table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Executor Id')}</th>
-              <th>${_('Address')}</th>
-              <th>${_('RDD Blocks')}</th>
-              <th>${_('Storage Memory')}</th>
-              <th>${_('Disk Used')}</th>
-              <th>${_('Active Tasks')}</th>
-              <th>${_('Failed Tasks')}</th>
-              <th>${_('Complete Tasks')}</th>
-              <th>${_('Task Time')}</th>
-              <th>${_('Input')}</th>
-              <th>${_('Shuffle Read')}</th>
-              <th>${_('Shuffle Write')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['executors']()['executor_list']">
-              <tr data-bind="click: function() { $root.job().id(id); $root.job().fetchJob(); }" class="status-border pointer">
-                <td data-bind="text: executor_id"></td>
-                <td data-bind="text: address"></td>
-                <td data-bind="text: rdd_blocks"></td>
-                <td data-bind="text: storage_memory"></td>
-                <td data-bind="text: disk_used"></td>
-                <td data-bind="text: active_tasks"></td>
-                <td data-bind="text: failed_tasks"></td>
-                <td data-bind="text: complete_tasks"></td>
-                <td data-bind="text: task_time"></td>
-                <td data-bind="text: input"></td>
-                <td data-bind="text: shuffle_read"></td>
-                <td data-bind="text: shuffle_write"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-        <div class="tab-pane" id="job-spark-page-properties${ SUFFIX }">
-          <table class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Name')}</th>
-              <th>${_('Value')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['metadata']">
-              <tr>
-                <td data-bind="text: name"></td>
-                <td><!-- ko template: { name: 'link-or-text', data: { name: name, value: value } } --><!-- /ko --></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="job-spark-executor-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <!-- ko with: properties -->
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: executor_id"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <!-- ko if: !$root.isMini() -->
-          <!-- ko with: properties -->
-            <li class="nav-header">${ _('Address') }</li>
-            <li><span data-bind="text: address"></span></li>
-            <li class="nav-header">${ _('RDD Blocks') }</li>
-            <li><span data-bind="text: rdd_blocks"></span></li>
-            <li class="nav-header">${ _('Storage Memory') }</li>
-            <li><span data-bind="text: storage_memory"></span></li>
-            <li class="nav-header">${ _('Disk Used') }</li>
-            <li><span data-bind="text: disk_used"></span></li>
-            <li class="nav-header">${ _('Active Tasks') }</li>
-            <li><span data-bind="text: active_tasks"></span></li>
-            <li class="nav-header">${ _('Failed Tasks') }</li>
-            <li><span data-bind="text: failed_tasks"></span></li>
-            <li class="nav-header">${ _('Complet Tasks') }</li>
-            <li><span data-bind="text: complete_tasks"></span></li>
-            <li class="nav-header">${ _('Input') }</li>
-            <li><span data-bind="text: input"></span></li>
-            <li class="nav-header">${ _('Shuffle Read') }</li>
-            <li><span data-bind="text: shuffle_read"></span></li>
-            <li class="nav-header">${ _('Shuffle Write') }</li>
-            <li><span data-bind="text: shuffle_write"></span></li>
-          <!-- /ko -->
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a class="jb-logs-link" href="#job-spark-executor-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-      </ul>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="job-spark-executor-page-logs${ SUFFIX }">
-          <ul class="nav nav-tabs">
-          % for name in ['stdout', 'stderr']:
-            <li class="${ name == 'stdout' and 'active' or '' }"><a href="javascript:void(0)" data-bind="click: function(data, e) { $(e.currentTarget).parent().siblings().removeClass('active'); $(e.currentTarget).parent().addClass('active'); fetchLogs('${ name }'); logActive('${ name }'); }, text: '${ name }'"></a></li>
-          % endfor
-          </ul>
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="dataeng-job-page${ SUFFIX }">
-  <button class="btn" title="${ _('Troubleshoot') }" data-bind="click: troubleshoot">
-    <i class="fa fa-tachometer"></i> ${ _('Troubleshoot') }
-  </button>
-
-  <!-- ko if: type() == 'dataeng-job-HIVE' -->
-    <div data-bind="template: { name: 'dataeng-job-hive-page${ SUFFIX }', data: $root.job() }"></div>
-  <!-- /ko -->
-</script>
-
-
-<script type="text/html" id="dataware-clusters-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: properties['properties']['cdhVersion']"></span></li>
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}">
-              <div class="bar" data-bind="style: {'width': '100%'}"></div>
-            </div>
-          </li>
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <div class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></div>
-    </div>
-  </div>
-
-  <br>
-
-  <button class="btn" title="${ _('Troubleshoot') }" data-bind="click: troubleshoot">
-    <i class="fa fa-tachometer"></i> ${ _('Troubleshoot') }
-  </button>
-</script>
-
-
-<script type="text/html" id="dataware2-clusters-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('Workers Online') }</li>
-          <li>
-            <span data-bind="text: properties['properties']['workerReplicasOnline']"></span>
-            /
-            <span data-bind="text: properties['properties']['workerReplicas']"></span>
-            <!-- ko if: properties['properties']['workerAutoResize'] -->
-              - ${ _('CPU') } <span data-bind="text: properties['properties']['workercurrentCPUUtilizationPercentage']"></span>%
-            <!-- /ko -->
-            <!-- ko if: status() == 'SCALING_UP' || status() == 'SCALING_DOWN' -->
-              <i class="fa fa-spinner fa-spin fa-fw"></i>
-            <!-- /ko -->
-          </li>
-          <li>
-            ##<div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}">
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': status() == 'SCALING_UP' || status() == 'SCALING_DOWN', 'progress-success': status() == 'ONLINE', 'progress-danger': apiStatus() === 'FAILED'}">
-              <div class="bar" data-bind="style: {'width': Math.min(properties['properties']['workerReplicas'](), properties['properties']['workerReplicasOnline']()) / Math.max(properties['properties']['workerReplicasOnline'](), properties['properties']['workerReplicas']()) * 100 + '%'}"></div>
-            </div>
-          </li>
-          <li class="nav-header">${ _('Auto resize') }</li>
-          <li>
-            <i data-bind="visible: !properties['properties']['workerAutoResize']()" class="fa fa-square-o fa-fw"></i>
-            <span data-bind="visible: properties['properties']['workerAutoResize']">
-              <i class="fa fa-check-square-o fa-fw"></i>
-              <span data-bind="text: properties['properties']['workerAutoResizeMin']"></span> -
-              <span data-bind="text: properties['properties']['workerAutoResizeMax']"></span>
-              ${ _('CPU:') } <span data-bind="text: properties['properties']['workerAutoResizeCpu']"></span>%
-            </span>
-          </li>
-          <li class="nav-header">${ _('Auto pause') }</li>
-          <li><i class="fa fa-square-o fa-fw"></i></li>
-          <li class="nav-header">${ _('Impalad') }</li>
-          <li>
-            <a href="#" data-bind="attr: { 'href': properties['properties']['coordinatorEndpoint']['publicHost']() + ':25000' }">
-              <span data-bind="text: properties['properties']['coordinatorEndpoint']['publicHost']"></span>
-              <i class="fa fa-external-link fa-fw"></i>
-            </a>
-          </li>
-          <li class="nav-header">${ _('Id') }</li>
-          <li><span class="break-word" data-bind="text: id"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }" style="position: relative;">
-      <div style="position: absolute; top: 0; right: 0">
-        <button class="btn" title="Create cluster" data-bind="enable: isRunning(), visible: $root.interface() == 'dataware2-clusters', templatePopover : { placement: 'bottom', contentTemplate: 'configure-cluster-content', minWidth: '320px', trigger: 'click' }, click: updateClusterShow" style="">
-            ${ _('Configure') }
-          <i class="fa fa-chevron-down"></i>
-        </button>
-
-        <a class="btn" title="${ _('Pause') }">
-          <i class="fa fa-pause"></i>
-        </a>
-
-        <a class="btn" title="${ _('Refresh') }" data-bind="click: function() { fetchJob(); }">
-          <i class="fa fa-refresh"></i>
-        </a>
-      </div>
-
-      <div class="acl-panel-content">
-        <ul class="nav nav-tabs">
-          <li class="active"><a href="#servicesLoad" data-toggle="tab">${ _("Load") }</a></li>
-          <li><a href="#servicesPrivileges" data-toggle="tab">${ _("Privileges") }</a></li>
-          <li><a href="#servicesTroubleshooting" data-toggle="tab">${ _("Troubleshooting") }</a></li>
-        </ul>
-
-        <div class="tab-content">
-          <div class="tab-pane active" id="servicesLoad">
-            <div class="wxm-poc" style="clear: both;">
-              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
-                ${ _("Metrics are not setup") }
-              </div>
-            </div>
-          </div>
-          <div class="tab-pane" id="servicesPrivileges">
-            <div class="acl-block-title">
-              <i class="fa fa-cube muted"></i> <a class="pointer"><span>admin</span></a>
-            </div>
-            <div>
-              <div class="acl-block acl-block-airy">
-                <span class="muted" title="3 months ago">CLUSTER</span>
-                <span>
-                  <a class="muted" style="margin-left: 4px" title="Open in Sentry" href="/security/hive"><i class="fa fa-external-link"></i></a>
-                </span>
-                <br>
-                server=<span>server1</span>
-                <span>
-                  <i class="fa fa-long-arrow-right"></i> db=<a class="pointer" title="Browse db privileges">gke_gcp-eng-dsdw_us-west2-b_impala-demo</a>
-                </span>
-                <i class="fa fa-long-arrow-right"></i> action=ALL
-              </div>
-            </div>
-            <div class="acl-block-title">
-              <i class="fa fa-cube muted"></i> <a class="pointer"><span>eng</span></a>
-            </div>
-            <div>
-              <div class="acl-block acl-block-airy">
-                <span class="muted" title="3 months ago">CLUSTER</span>
-                <span>
-                  <a class="muted" style="margin-left: 4px" title="Open in Sentry" href="/security/hive"><i class="fa fa-external-link"></i></a>
-                </span>
-                <br>
-                server=server1
-                <span>
-                  <i class="fa fa-long-arrow-right"></i> db=<a class="pointer" title="Browse db privileges">gke_gcp-eng-dsdw_us-west2-b_impala-demo</a>
-                </span>
-                <i class="fa fa-long-arrow-right"></i> action=<span>ACCESS</span>
-              </div>
-            </div>
-            <div class="acl-block acl-actions">
-              <span class="pointer" title="Show 50 more..." style="display: none;"><i class="fa fa-ellipsis-h"></i></span>
-              <span class="pointer" title="Add privilege"><i class="fa fa-plus"></i></span>
-              <span class="pointer" title="Undo" style="display: none;"> &nbsp; <i class="fa fa-undo"></i></span>
-              <span class="pointer" title="Save" style="display: none;"> &nbsp; <i class="fa fa-save"></i></span>
-            </div>
-          </div>
-          <div class="tab-pane" id="servicesTroubleshooting">
-            <div class="wxm-poc" style="clear: both;">
-              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
-                <h4>Outliers</h4>
-                <img src="${ static('desktop/art/wxm_fake/outliers.svg') }" style="height: 440px"/>
-              </div>
-              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
-                <h4>Statement Types</h4>
-                <img src="${ static('desktop/art/wxm_fake/statement_types.svg') }" style="height: 440px"/>
-              </div>
-              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
-                <h4>Duration</h4>
-                <img src="${ static('desktop/art/wxm_fake/duration.svg') }" style="height: 440px"/>
-              </div>
-              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
-                <h4>Memory Utilization</h4>
-                <img src="${ static('desktop/art/wxm_fake/memory.svg') }" style="height: 440px"/>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="dataeng-job-hive-page${ SUFFIX }">
-  ${ _('Id') } <span data-bind="text: id"></span>
-  ${ _('Name') } <span data-bind="text: name"></span>
-  ${ _('Type') } <span data-bind="text: type"></span>
-  ${ _('Status') } <span data-bind="text: status"></span>
-  ${ _('User') } <span data-bind="text: user"></span>
-  ${ _('Progress') } <span data-bind="text: progress"></span>
-  ${ _('Duration') } <span data-bind="text: duration().toHHMMSS()"></span>
-  ${ _('Submitted') } <span data-bind="text: submitted"></span>
-
-  <br>
-
-  <span data-bind="text: ko.mapping.toJSON(properties['properties'])"></span>
-  ##<div data-bind="template: { name: 'render-properties${ SUFFIX }', data: properties['properties'] }"></div>
-</script>
-
-
-<script type="text/html" id="queries-page${ SUFFIX }">
-  <div class="row-fluid" data-jobType="queries">
-    <!-- ko if: id() -->
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Id') }</li>
-          <li>
-            % if hasattr(IMPALA_COORDINATOR_URL, 'get') and not IMPALA_COORDINATOR_URL.get():
-            <a data-bind="attr: { href: doc_url_modified }" target="_blank" title="${ _('Open in Impalad') }">
-              <span data-bind="text: id"></span>
-            </a>
-            % else:
-            <span data-bind="text: id"></span>
-            % endif
-            <!-- ko if: $root.isMini() -->
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%; height: 4px" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }, attr: {'title': progress() + '%'}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-            <!-- /ko -->
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span>
-          </li>
-          <!-- /ko -->
-          <!-- ko if: !$root.isMini() -->
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li>
-            <span data-bind="text: progress"></span>%
-          </li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <!-- /ko -->
-          <!-- ko if: !$root.isMini() -->
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <!-- ko if: properties.plan && properties.plan().status && properties.plan().status.length > 2 -->
-          <li class="nav-header">${ _('Status Text') }</li>
-          <li><span data-bind="text: properties.plan().status"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Open Duration') }</li>
-          <li><span data-bind="text: duration() && duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <!-- ko if: $root.job().mainType() == 'queries-impala' -->
-        <li>
-          <a href="#queries-page-plan${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-plan${ SUFFIX }\']').tab('show'); }, event: {'shown': function () { if (!properties.plan || !properties.plan()) { fetchProfile('plan'); } } }">
-            ${ _('Plan') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-stmt${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-stmt${ SUFFIX }\']').tab('show'); }">
-            ${ _('Query') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-plan-text${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-plan-text${ SUFFIX }\']').tab('show'); }">
-            ${ _('Text Plan') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-summary${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-summary${ SUFFIX }\']').tab('show'); }">
-            ${ _('Summary') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-profile${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-profile${ SUFFIX }\']').tab('show'); }, event: {'shown': function () { if (!properties.profile || !properties.profile().profile) { fetchProfile('profile'); } } }">
-            ${ _('Profile') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-memory${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-memory${ SUFFIX }\']').tab('show'); }, event: {'shown': function () { if (!properties.memory || !properties.memory().mem_usage) { fetchProfile('memory'); } } }">
-            ${ _('Memory') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-backends${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-backends${ SUFFIX }\']').tab('show'); }, event: {'shown': function () { if (!properties.backends || !properties.backends().backend_states) { fetchProfile('backends'); } } }">
-            ${ _('Backends') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-finstances${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-finstances${ SUFFIX }\']').tab('show'); }, event: {'shown': function () { if (!properties.finstances || !properties.finstances().backend_instances) { fetchProfile('finstances'); } } }">
-            ${ _('Instances') }</a>
-        </li>
-        <!-- /ko -->
-        <!-- ko if: $root.job().mainType() == 'queries-hive' -->
-        <li class="active">
-          <a href="#queries-page-hive-plan-text${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-hive-plan-text${ SUFFIX }\']').tab('show'); }">
-            ${ _('Plan') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-hive-stmt${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-hive-stmt${ SUFFIX }\']').tab('show'); }">
-            ${ _('Query') }</a>
-        </li>
-        <li>
-          <a href="#queries-page-hive-perf${ SUFFIX }" data-bind="click: function(){ $('a[href=\'#queries-page-hive-perf${ SUFFIX }\']').tab('show'); }">
-            ${ _('Perf') }</a>
-        </li>
-        <!-- /ko -->
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <!-- ko if: $root.job().mainType() == 'queries-impala' -->
-        <div class="tab-pane" id="queries-page-plan${ SUFFIX }" data-profile="plan">
-          <div data-bind="visible:properties.plan && properties.plan().plan_json && properties.plan().plan_json.plan_nodes.length">
-            <div class="query-plan" id="queries-page-plan-graph${ SUFFIX }" data-bind="impalaDagre: { value: properties.plan && properties.plan(), height:$root.isMini() ? 535 : 600 }">
-              <svg style="width:100%;height:100%;position:relative;" id="queries-page-plan-svg${ SUFFIX }">
-                <g></g>
-              </svg>
-            </div>
-          </div>
-          <pre data-bind="visible:!properties.plan || !properties.plan().plan_json || !properties.plan().plan_json.plan_nodes.length" >${ _('The selected tab has no data') }</pre>
-        </div>
-        <div class="tab-pane" id="queries-page-stmt${ SUFFIX }" data-profile="plan">
-          <!-- ko if: properties.plan && properties.plan().stmt -->
-            <pre data-bind="highlight: { value: properties.plan().stmt.trim(), enableOverflow: true, formatted: true, dialect: 'impala' }"></pre>
-          <!-- /ko -->
-          <!-- ko ifnot: properties.plan && properties.plan().stmt -->
-            <pre>
-              ${ _('The selected tab has no data') }
-            </pre>
-          <!-- /ko -->
-        </div>
-        <div class="tab-pane" id="queries-page-plan-text${ SUFFIX }" data-profile="plan">
-          <pre data-bind="text: (properties.plan && properties.plan().plan) || _('The selected tab has no data')"></pre>
-        </div>
-        <div class="tab-pane" id="queries-page-summary${ SUFFIX }" data-profile="plan">
-          <pre data-bind="text: (properties.plan && properties.plan().summary) || _('The selected tab has no data')"></pre>
-        </div>
-        <div class="tab-pane" id="queries-page-profile${ SUFFIX }" data-profile="profile">
-          <button class="btn" type="button" data-clipboard-target="#query-impala-profile" style="float: right;" data-bind="
-              visible: properties.profile && properties.profile().profile,
-              clipboard: { onSuccess: function() { $.jHueNotify.info('${ _("Profile copied to clipboard!") }'); } }">
-            <i class="fa fa-fw fa-clipboard"></i> ${ _('Clipboard') }
-          </button>
-          <button class="btn" type="button" style="float: right;" data-bind="
-              click: function(){ submitQueryProfileDownloadForm('download-profile'); },
-              visible: properties.profile && properties.profile().profile">
-            <i class="fa fa-fw fa-download"></i> ${ _('Download') }
-          </button>
-          <div id="downloadProgressModal"></div>
-          <pre id="query-impala-profile" style="float: left; margin-top: 8px" data-bind="
-              text: (properties.profile && properties.profile().profile) || _('The selected tab has no data')"></pre>
-        </div>
-        <div class="tab-pane" id="queries-page-memory${ SUFFIX }" data-profile="mem_usage">
-          <pre data-bind="text: (properties.memory && properties.memory().mem_usage) || _('The selected tab has no data')"></pre>
-        </div>
-        <div class="tab-pane" id="queries-page-backends${ SUFFIX }" data-profile="backends">
-          <!-- ko if: properties.backends && properties.backends().backend_states -->
-          <div id="queries-page-memory-backends-template${ SUFFIX }" style="overflow-x: scroll;">
-            <table class="table table-condensed">
-              <thead>
-              <tr>
-                <!-- ko foreach: Object.keys(properties.backends().backend_states[0]).sort() -->
-                <th data-bind="text: $data"></th>
-                <!-- /ko -->
-              </tr>
-              </thead>
-              <tbody data-bind="foreach: { data: $data.properties.backends().backend_states, minHeight: 20, container: '#queries-page-memory-backends-template${ SUFFIX }'}">
-                <tr>
-                  <!-- ko foreach: Object.keys($data).sort() -->
-                  <td data-bind="text: $parent[$data]"></td>
-                  <!-- /ko -->
-                </tr>
-              </tbody>
-            </table>
-          </div>
-          <!-- /ko -->
-          <!-- ko if: !properties.backends || !properties.backends().backend_states -->
-          <pre data-bind="text: _('The selected tab has no data')"></pre>
-          <!-- /ko -->
-        </div>
-        <div class="tab-pane" id="queries-page-finstances${ SUFFIX }" data-profile="finstances">
-          <!-- ko if: properties.finstances && properties.finstances().backend_instances -->
-          <div id="queries-page-memory-finstances-template${ SUFFIX }" style="overflow-x: scroll;">
-            <table class="table table-condensed">
-              <thead>
-              <tr>
-                <!-- ko foreach: [_('host')].concat(Object.keys($data.properties.finstances().backend_instances[0].instance_stats[0])).sort() -->
-                <th data-bind="text: $data"></th>
-                <!-- /ko -->
-              </tr>
-              </thead>
-              <tbody data-bind="foreach: { data: $data.properties.finstances().backend_instances.reduce( function(arr, instance) { instance.instance_stats.forEach(function(stats) { stats.host = instance.host; }); return arr.concat(instance.instance_stats); }, []), minHeight: 20, container: '#queries-page-memory-finstances-template${ SUFFIX }'}">
-                <tr>
-                  <!-- ko foreach: Object.keys($data).sort() -->
-                  <td data-bind="text: $parent[$data]"></td>
-                  <!-- /ko -->
-                </tr>
-              </tbody>
-            </table>
-          </div>
-          <!-- /ko -->
-          <!-- ko if: !properties.finstances || !properties.finstances().backend_instances -->
-          <pre data-bind="text: _('The selected tab has no data')"></pre>
-          <!-- /ko -->
-        </div>
-        <!-- /ko -->
-
-        <!-- ko if: $root.job().mainType() == 'queries-hive' -->
-        <div class="tab-pane active" id="queries-page-hive-plan-text${ SUFFIX }" data-profile="plan">
-          <!-- ko if: properties.plan && properties.plan().plan -->
-            <pre data-bind="text: properties.plan().plan || _('The selected tab has no data')"></pre>
-          <!-- /ko -->
-        </div>
-        <div class="tab-pane" id="queries-page-hive-stmt${ SUFFIX }" data-profile="stmt">
-          <pre data-bind="text: (properties.plan && properties.plan().stmt) || _('The selected tab has no data')"></pre>
-        </div>
-        <div class="tab-pane" id="queries-page-hive-perf${ SUFFIX }" data-profile="perf">
-          <hive-query-plan></hive-query-plan>
-        </div>
-        <!-- /ko -->
-      </div>
-    </div>
-    <!-- /ko -->
-  </div>
-</script>
-
-
-<script type="text/html" id="livy-session-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
-              <span data-bind="text: name"></span>
-            </a>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{ 'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        <li>
-          <a href="#livy-session-page-statements${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#livy-session-page-statements${ SUFFIX }\']').tab('show'); }">
-            ${ _('Properties') }</a>
-        </li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="livy-session-page-statements${ SUFFIX }">
-          <table id="actionsTable" class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Id')}</th>
-              <th>${_('State')}</th>
-              <th>${_('Output')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['statements']">
-              <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
-                <td>
-                  <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
-                    <i class="fa fa-tasks"></i>
-                  </a>
-                </td>
-                <td data-bind="text: state"></td>
-                <td data-bind="text: output"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="celery-beat-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
-              <span data-bind="text: name"></span>
-            </a>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li>
-          <a href="#celery-beat-page-statements${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#celery-beat-page-statements${ SUFFIX }\']').tab('show'); }">
-            ${ _('Properties') }
-          </a>
-        </li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="celery-beat-page-statements${ SUFFIX }">
-          <table id="actionsTable" class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Id')}</th>
-              <th>${_('State')}</th>
-              <th>${_('Output')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['statements']">
-              <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
-                <td>
-                  <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
-                    <i class="fa fa-tasks"></i>
-                  </a>
-                </td>
-                <td data-bind="text: state"></td>
-                <td data-bind="text: output"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="schedule-hive-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
-              <span data-bind="text: name"></span>
-            </a>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <li class="nav-header">${ _('Duration') }</li>
-          <li><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active">
-          <a href="#schedule-hive-page-properties${ SUFFIX }" data-toggle="tab">
-            ${ _('Properties') }
-          </a>
-        </li>
-        <li>
-          <a href="#schedule-hive-page-queries${ SUFFIX }" data-bind="click: function(){ fetchProfile('tasks'); $('a[href=\'#schedule-hive-queries${ SUFFIX }\']').tab('show'); }" data-toggle="tab">
-            ${ _('Queries') }
-          </a>
-        </li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="schedule-hive-page-properties${ SUFFIX }">
-          <pre data-bind="highlight: { value: properties['query'], formatted: true, dialect: 'hive' }"></pre>
-        </div>
-
-        <div class="tab-pane" id="schedule-hive-page-queries${ SUFFIX }">
-          <!-- ko with: coordinatorActions() -->
-          <form class="form-inline">
-            <div data-bind="template: { name: 'job-actions${ SUFFIX }' }" class="pull-right"></div>
-          </form>
-
-          <table id="schedulesHiveTable" class="datatables table table-condensed status-border-container">
-            <thead>
-            <tr>
-              <th width="1%"><div class="select-all hue-checkbox fa" data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div></th>
-              <th>${_('Status')}</th>
-              <th>${_('Title')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: apps">
-              <tr class="status-border pointer" data-bind="
-                css: {
-                  'completed': properties.status() == 'SUCCEEDED',
-                  'running': ['RUNNING', 'FAILED', 'KILLED'].indexOf(properties.status()) != -1,
-                  'failed': properties.status() == 'FAILED' || properties.status() == 'KILLED'
-                },
-                click: function(data, event) {
-                  $root.job().onTableRowClick(event, properties.externalId());
-                }">
-                <td data-bind="click: function() {}, clickBubble: false">
-                  <div class="hue-checkbox fa" data-bind="click: function() {}, clickBubble: false, multiCheck: '#schedulesHiveTable', value: $data, hueChecked: $parent.selectedJobs"></div>
-                </td>
-                <td><span class="label job-status-label" data-bind="text: properties.status"></span></td>
-                <td data-bind="text: properties.title"></td>
-              </tr>
-            </tbody>
-          </table>
-          <!-- /ko -->
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="job-actions${ SUFFIX }">
-  <div class="btn-group">
-    <!-- ko if: $root.job() && $root.job().type() === 'schedule' -->
-    <button class="btn" title="${ _('Sync Coordinator') }" data-bind="click: function() { $root.job().showSyncCoorModal() }, enable: killEnabled">
-      <i class="fa fa-refresh"></i> <!-- ko ifnot: $root.isMini -->${ _('Coordinator') }<!-- /ko -->
-    </button>
-
-    <button class="btn" title="${ _('Sync Workflow') }" data-bind="click: function(){ control('sync_workflow'); }, enable: killEnabled">
-      <i class="fa fa-refresh"></i> <!-- ko ifnot: $root.isMini -->${ _('Workflow') }<!-- /ko -->
-    </button>
-    <!-- /ko -->
-  </div>
-
-  <div class="btn-group">
-    <!-- ko if: hasResume -->
-    <button class="btn" title="${ _('Resume selected') }" data-bind="click: function() { control('resume'); }, enable: resumeEnabled">
-      <i class="fa fa-play"></i> <!-- ko ifnot: $root.isMini -->${ _('Resume') }<!-- /ko -->
-    </button>
-    <!-- /ko -->
-
-    <!-- ko if: hasPause -->
-    <button class="btn" title="${ _('Suspend selected') }" data-bind="click: function() { control('suspend'); }, enable: pauseEnabled">
-      <i class="fa fa-pause"></i> <!-- ko ifnot: $root.isMini -->${ _('Suspend') }<!-- /ko -->
-    </button>
-    <!-- /ko -->
-
-    <!-- ko if: hasRerun -->
-    <button class="btn" title="${ _('Rerun selected') }" data-bind="click: function() { control('rerun'); }, enable: rerunEnabled">
-      <i class="fa fa-repeat"></i> <!-- ko ifnot: $root.isMini -->${ _('Rerun') }<!-- /ko -->
-    </button>
-    <!-- /ko -->
-
-    % if not DISABLE_KILLING_JOBS.get():
-    <!-- ko if: hasKill -->
-    <button class="btn btn-danger disable-feedback" title="${_('Stop selected')}" data-bind="click: function() { $('#killModal${ SUFFIX }').modal('show'); }, enable: killEnabled">
-      <i class="fa fa-times"></i> <!-- ko ifnot: $root.isMini -->${_('Kill')}<!-- /ko -->
-    </button>
-    <!-- /ko -->
-    % endif
-
-    <!-- ko if: hasIgnore -->
-    <button class="btn btn-danger disable-feedback" title="${_('Ignore selected')}" data-bind="click: function() { control('ignore'); }, enable: ignoreEnabled">
-      ## TODO confirmation
-      <i class="fa fa-eraser"></i> <!-- ko ifnot: $root.isMini -->${_('Ignore')}<!-- /ko -->
-    </button>
-    <!-- /ko -->
-  </div>
-</script>
-
-
-<script type="text/html" id="workflow-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Id') }</li>
-          <li class="break-word" data-bind="visible: ! $root.isMini()"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <div>
-              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }" ></a>
-              <a data-bind="documentContextPopover: { uuid: doc_url().split('=')[1], orientation: 'bottom', offset: { top: 5 } }" href="javascript: void(0);" title="${ _('Preview document') }">
-                <i class="fa fa-info"></i>
-              </a>
-            </div>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Status') }</li>
-          <li><span data-bind="text: status, visible: ! $root.isMini()"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() === 'RUNNING', 'progress-success': apiStatus() === 'SUCCEEDED' }, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <li data-bind="visible: ! $root.isMini()" class="nav-header">${ _('Duration') }</li>
-          <li data-bind="visible: ! $root.isMini()"><span data-bind="text: duration().toHHMMSS()"></span></li>
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Submitted') }</li>
-          <li data-bind="visible: ! $root.isMini()"><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
-          <!-- ko if: properties['parameters'].length > 0 -->
-          <li class="nav-header">${ _('Variables') }</li>
-          <li>
-            <ul class="unstyled" data-bind="foreach: properties['parameters']">
-              <li class="margin-top-5">
-              <span data-bind="text: name, attr: { title: value }" class="muted"></span><br>
-                &nbsp;
-              <!-- ko if: link -->
-              <a data-bind="hueLink: link, text: value, attr: { title: value }" href="javascript: void(0);">
-              </a>
-              <!-- /ko -->
-              <!-- ko ifnot: link -->
-                <span data-bind="text: value, attr: { title: value }"></span>
-              <!-- /ko -->
-              </li>
-            </ul>
-          </li>
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css: { 'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-
-      <ul class="nav nav-pills margin-top-20">
-        %if not is_mini:
-        <li class="active"><a href="#workflow-page-graph${ SUFFIX }" data-toggle="tab">${ _('Graph') }</a></li>
-        %endif
-        <li><a href="#workflow-page-metadata${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#workflow-page-metadata${ SUFFIX }\']').tab('show'); }">${ _('Properties') }</a></li>
-        <li><a class="jb-logs-link" href="#workflow-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li class="${ 'active' if is_mini else ''}"><a href="#workflow-page-tasks${ SUFFIX }" data-toggle="tab">${ _('Tasks') }</a></li>
-        <li><a href="#workflow-page-xml${ SUFFIX }" data-bind="click: function(){ fetchProfile('xml'); $('a[href=\'#workflow-page-xml${ SUFFIX }\']').tab('show'); }">${ _('XML') }</a></li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        %if not is_mini:
-        <div class="tab-pane active dashboard-container" id="workflow-page-graph${ SUFFIX }"></div>
-        %endif
-
-        <div class="tab-pane" id="workflow-page-logs${ SUFFIX }">
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane ${ 'active' if is_mini else ''}" id="workflow-page-tasks${ SUFFIX }">
-          <table id="actionsTable" class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Log')}</th>
-              <th>${_('Status')}</th>
-              <th>${_('Error message')}</th>
-              <th>${_('Error code')}</th>
-              <th>${_('External id')}</th>
-              <th>${_('Id')}</th>
-              <th>${_('Start time')}</th>
-              <th>${_('End time')}</th>
-	      <th>${_('Data')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['actions']">
-              <tr>
-                <td>
-                  <a data-bind="hueLink: '/jobbrowser/jobs/' + ko.unwrap(externalId), clickBubble: false">
-                    <i class="fa fa-tasks"></i>
-                  </a>
-                </td>
-                <td data-bind="text: status"></td>
-                <td data-bind="text: errorMessage"></td>
-                <td data-bind="text: errorCode"></td>
-                <td data-bind="text: externalId, click: function() { $root.job().id(ko.unwrap(externalId)); $root.job().fetchJob();}, style: { color: '#0B7FAD' }" class="pointer"></td>
-                <td data-bind="text: id, click: function(data, event) { $root.job().onTableRowClick(event, id); }, style: { color: '#0B7FAD' }" class="pointer"></td>
-                <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
-                <td data-bind="moment: {data: endTime, format: 'LLL'}"></td>
-		<td data-bind="text: data"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="tab-pane" id="workflow-page-metadata${ SUFFIX }">
-          <div data-bind="template: { name: 'render-properties${ SUFFIX }', data: properties['properties'] }"></div>
-        </div>
-
-        <div class="tab-pane" id="workflow-page-xml${ SUFFIX }">
-          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="workflow-action-page${ SUFFIX }">
-
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Job') }</li>
-          <li>
-            <a data-bind="hueLink: '/jobbrowser/jobs/' + properties['externalId']()" href="javascript: void(0);">
-              <span data-bind="text: properties['externalId']"></span>
-            </a>
-          </li>
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-        </ul>
-      </div>
-    </div>
-
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a href="#workflow-action-page-metadata${ SUFFIX }" data-toggle="tab">${ _('Properties') }</a></li>
-        <li><a href="#workflow-action-page-tasks${ SUFFIX }" data-toggle="tab">${ _('Child jobs') }</a></li>
-        <li><a href="#workflow-action-page-xml${ SUFFIX }" data-toggle="tab">${ _('XML') }</a></li>
-      </ul>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="workflow-action-page-metadata${ SUFFIX }">
-          <table class="datatables table table-condensed">
-            <thead>
-            <tr>
-              <th>${_('Name')}</th>
-              <th>${_('Value')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['properties']">
-              <tr>
-                <td data-bind="text: name"></td>
-                <td data-bind="text: value"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="tab-pane" id="workflow-action-page-tasks${ SUFFIX }">
-          <!-- ko if: properties['externalChildIDs'].length > 0 -->
-          <table class="table table-condensed datatables">
-            <thead>
-              <tr>
-                <th>${ _('Ids') }</th>
-              </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['externalChildIDs']">
-              <tr>
-                <td>
-                  <a data-bind="hueLink: '/jobbrowser/jobs/' + $data, text: $data" href="javascript: void(0);">
-                  </a>
-                </td>
-              </tr>
-            </tbody>
-          </table>
-          <!-- /ko -->
-
-          <!-- ko if: properties['externalChildIDs'].length == 0 -->
-            ${ _('No external jobs') }
-          <!-- /ko -->
-        </div>
-
-        <div class="tab-pane" id="workflow-action-page-xml${ SUFFIX }">
-          <div data-bind="readOnlyAce: properties['conf'], type: 'xml'"></div>
-        </div>
-
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="schedule-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Id') }</li>
-          <li class="break-word" data-bind="visible: ! $root.isMini()"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <div>
-              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }" ></a>
-              <a data-bind="documentContextPopover: { uuid: doc_url().split('=')[1], orientation: 'bottom', offset: { top: 5 } }" href="javascript: void(0);" title="${ _('Preview document') }">
-                <i class="fa fa-info"></i>
-              </a>
-            </div>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Type') }</li>
-          <li data-bind="visible: ! $root.isMini()"><span data-bind="text: type"></span></li>
-          <li class="nav-header" data-bind="visible: ! $root.isMini()">${ _('Status') }</li>
-          <li data-bind="visible: ! $root.isMini()"><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100, 'progress-danger': apiStatus() === 'FAILED'}, attr: {title: status}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          <!-- ko if: !$root.isMini() -->
-            <li class="nav-header">${ _('Submitted') }</li>
-            <li><span data-bind="text: submitted"></span></li>
-            <li class="nav-header">${ _('Next Run') }</li>
-            <li><span data-bind="text: properties['nextTime']"></span></li>
-            <li class="nav-header">${ _('Total Actions') }</li>
-            <li><span data-bind="text: properties['total_actions']"></span></li>
-            <li class="nav-header">${ _('End time') }</li>
-            <li><span data-bind="text: properties['endTime']"></span></li>
-          <!-- /ko -->
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a href="#schedule-page-calendar${ SUFFIX }" data-toggle="tab">${ _('Tasks') }</a></li>
-        <li><a class="jb-logs-link" href="#schedule-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#schedule-page-metadata${ SUFFIX }" data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#schedule-page-metadata${ SUFFIX }\']').tab('show'); }">${ _('Properties') }</a></li>
-        <li><a href="#schedule-page-xml${ SUFFIX }" data-bind="click: function(){ fetchProfile('xml'); $('a[href=\'#schedule-page-xml${ SUFFIX }\']').tab('show'); }">${ _('XML') }</a></li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="schedule-page-calendar${ SUFFIX }">
-          <!-- ko with: coordinatorActions() -->
-          <form class="form-inline">
-            ##<input data-bind="value: textFilter" type="text" class="input-xlarge search-query" placeholder="${_('Filter by name')}">
-            <!-- ko with: $root.job() -->
-            <!-- ko if: type() === 'schedule' -->
-            <span data-bind="foreach: statesValuesFilter">
-              <label class="checkbox">
-                <div class="pull-left margin-left-5 status-border status-content" data-bind="css: value, hueCheckbox: checked"></div>
-                <div class="inline-block" data-bind="text: name, toggle: checked"></div>
-              </label>
-            </span>
-            <!-- /ko -->
-            <!-- /ko -->
-            <div data-bind="template: { name: 'job-actions${ SUFFIX }' }" class="pull-right"></div>
-          </form>
-
-          <table id="schedulesTable" class="datatables table table-condensed status-border-container">
-            <thead>
-            <tr>
-              <th width="1%"><div class="select-all hue-checkbox fa" data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div></th>
-              <th>${_('Status')}</th>
-              <th>${_('Title')}</th>
-              <th>${_('type')}</th>
-              <th>${_('errorMessage')}</th>
-              <th>${_('missingDependencies')}</th>
-              <th>${_('number')}</th>
-              <th>${_('errorCode')}</th>
-              <th>${_('externalId')}</th>
-              <th>${_('id')}</th>
-              <th>${_('lastModifiedTime')}</th>
-            </tr>
-            </thead>
-            <!-- ko if: !$root.job().forceUpdatingJob() -->
-            <tbody data-bind="foreach: apps">
-              <tr class="status-border pointer" data-bind="
-                css: {
-                  'completed': properties.status() == 'SUCCEEDED',
-                  'running': ['RUNNING', 'FAILED', 'KILLED'].indexOf(properties.status()) != -1,
-                  'failed': properties.status() == 'FAILED' || properties.status() == 'KILLED'
-                },
-                click: function(data, event) {
-                  $root.job().onTableRowClick(event, properties.externalId());
-                }">
-                <td data-bind="click: function() {}, clickBubble: false">
-                  <div class="hue-checkbox fa" data-bind="click: function() {}, clickBubble: false, multiCheck: '#schedulesTable', value: $data, hueChecked: $parent.selectedJobs"></div>
-                </td>
-                <td><span class="label job-status-label" data-bind="text: properties.status"></span></td>
-                <td data-bind="text: properties.title"></td>
-                <td data-bind="text: properties.type"></td>
-                <td data-bind="text: properties.errorMessage"></td>
-                <td data-bind="text: properties.missingDependencies"></td>
-                <td data-bind="text: properties.number"></td>
-                <td data-bind="text: properties.errorCode"></td>
-                <td data-bind="text: properties.externalId"></td>
-                <td data-bind="text: properties.id"></td>
-                <td data-bind="text: properties.lastModifiedTime"></td>
-              </tr>
-            </tbody>
-            <!-- /ko -->
-            <!-- ko if: $root.job().forceUpdatingJob() -->
-            <tbody>
-              <tr>
-                <td colspan="11"><!-- ko hueSpinner: { spin: true, inline: true, size: 'large' } --><!-- /ko --></td>
-              </tr>
-            </tbody>
-            <!-- /ko -->
-          </table>
-          <!-- /ko -->
-        </div>
-
-        <div class="tab-pane" id="schedule-page-logs${ SUFFIX }">
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane" id="schedule-page-metadata${ SUFFIX }">
-          <div data-bind="template: { name: 'render-properties${ SUFFIX }', data: properties['properties'] }"></div>
-        </div>
-
-        <div class="tab-pane" id="schedule-page-xml${ SUFFIX }">
-          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-
-<script type="text/html" id="bundle-page${ SUFFIX }">
-  <div class="row-fluid">
-    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
-      <div class="sidebar-nav">
-        <ul class="nav nav-list">
-          <li class="nav-header">${ _('Id') }</li>
-          <li class="break-word"><span data-bind="text: id"></span></li>
-          <!-- ko if: doc_url -->
-          <li class="nav-header">${ _('Document') }</li>
-          <li>
-            <div>
-              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }" ></a>
-              <a data-bind="documentContextPopover: { uuid: doc_url().split('=')[1], orientation: 'bottom', offset: { top: 5 } }" href="javascript: void(0);" title="${ _('Preview document') }">
-                <i class="fa fa-info"></i>
-              </a>
-            </div>
-          </li>
-          <!-- /ko -->
-          <!-- ko ifnot: doc_url -->
-          <li class="nav-header">${ _('Name') }</li>
-          <li><span data-bind="text: name"></span></li>
-          <!-- /ko -->
-          <li class="nav-header">${ _('Type') }</li>
-          <li><span data-bind="text: type"></span></li>
-          <li class="nav-header">${ _('Status') }</li>
-          <li><span data-bind="text: status"></span></li>
-          <li class="nav-header">${ _('User') }</li>
-          <li><span data-bind="text: user"></span></li>
-          <li class="nav-header">${ _('Progress') }</li>
-          <li><span data-bind="text: progress"></span>%</li>
-          <li>
-            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="css: {'progress-danger': apiStatus() === 'FAILED', 'progress-warning': apiStatus() !== 'FAILED' && progress() < 100, 'progress-success': apiStatus() !== 'FAILED' && progress() === 100}">
-              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
-            </div>
-          </li>
-          ##<li class="nav-header">${ _('Duration') }</li>
-          ##<li><span data-bind="text: duration"></span></li>
-          <li class="nav-header">${ _('Submitted') }</li>
-          <li><span data-bind="text: submitted"></span></li>
-          ##<li class="nav-header">${ _('Next Run') }</li>
-          ##<li><span data-bind="text: properties['nextTime']"></span></li>
-          ##<li class="nav-header">${ _('Total Actions') }</li>
-          ##<li><span data-bind="text: properties['total_actions']"></span></li>
-          ##<li class="nav-header">${ _('End time') }</li>
-          ##<li><span data-bind="text: properties['endTime']"></span></li>
-        </ul>
-      </div>
-    </div>
-    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
-      <ul class="nav nav-pills margin-top-20">
-        <li class="active"><a href="#bundle-page-coordinators${ SUFFIX }" data-toggle="tab">${ _('Tasks') }</a></li>
-        <li><a class="jb-logs-link" href="#bundle-page-logs${ SUFFIX }" data-toggle="tab">${ _('Logs') }</a></li>
-        <li><a href="#bundle-page-metadata${ SUFFIX }"  data-bind="click: function(){ fetchProfile('properties'); $('a[href=\'#bundle-page-metadata${ SUFFIX }\']').tab('show'); }">${ _('Properties') }</a></li>
-        <li><a href="#bundle-page-xml${ SUFFIX }" data-bind="click: function(){ fetchProfile('xml'); $('a[href=\'#bundle-page-xml${ SUFFIX }\']').tab('show'); }">${ _('XML') }</a></li>
-        <li class="pull-right" data-bind="template: { name: 'job-actions${ SUFFIX }' }"></li>
-      </ul>
-
-      <div class="clearfix"></div>
-
-      <div class="tab-content">
-        <div class="tab-pane active" id="bundle-page-coordinators${ SUFFIX }">
-          <table id="coordsTable" class="datatables table table-condensed status-border-container">
-            <thead>
-            <tr>
-              <th>${_('Status')}</th>
-              <th>${_('Name')}</th>
-              <th>${_('Type')}</th>
-              <th>${_('nextMaterializedTime')}</th>
-              <th>${_('lastAction')}</th>
-              <th>${_('frequency')}</th>
-              <th>${_('timeUnit')}</th>
-              <th>${_('externalId')}</th>
-              <th>${_('id')}</th>
-              <th>${_('pauseTime')}</th>
-            </tr>
-            </thead>
-            <tbody data-bind="foreach: properties['actions']">
-              <tr class="status-border pointer" data-bind="css: {'completed': ko.unwrap(status) == 'SUCCEEDED', 'running': ['SUCCEEDED', 'FAILED', 'KILLED'].indexOf(ko.unwrap(status)) != -1, 'failed': ko.unwrap(status) == 'FAILED' || ko.unwrap(status) == 'KILLED'}, click: function() { if (ko.unwrap(id)) { $root.job().id(ko.unwrap(id)); $root.job().fetchJob();} }">
-                <td><span class="label job-status-label" data-bind="text: status"></span></td>
-                <td data-bind="text: name"></td>
-                <td data-bind="text: type"></td>
-                <td data-bind="text: nextMaterializedTime"></td>
-                <td data-bind="text: lastAction"></td>
-                <td data-bind="text: frequency"></td>
-                <td data-bind="text: timeUnit"></td>
-                <td data-bind="text: externalId"></td>
-                <td data-bind="text: id"></td>
-                <td data-bind="text: pauseTime"></td>
-              </tr>
-            </tbody>
-          </table>
-        </div>
-
-        <div class="tab-pane" id="bundle-page-logs${ SUFFIX }">
-          <pre data-bind="html: logs, logScroller: logs"></pre>
-        </div>
-
-        <div class="tab-pane" id="bundle-page-metadata${ SUFFIX }">
-          <div data-bind="template: { name: 'render-properties${ SUFFIX }', data: properties['properties'] }"></div>
-        </div>
-
-        <div class="tab-pane" id="bundle-page-xml${ SUFFIX }">
-          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-</script>
-
-<script type="text/html" id="render-properties${ SUFFIX }">
-  <!-- ko hueSpinner: { spin: !$data.properties, center: true, size: 'xlarge' } --><!-- /ko -->
-  <!-- ko if: $data.properties -->
-  <!-- ko if: !$root.isMini() -->
-  <form class="form-search">
-    <input type="text" data-bind="clearable: $parent.propertiesFilter, valueUpdate: 'afterkeydown'" class="input-xlarge search-query" placeholder="${_('Text Filter')}">
-  </form>
-  <br>
-  <!-- /ko -->
-  <table id="jobbrowserJobPropertiesTable" class="table table-condensed">
-    <thead>
-    <tr>
-      <th>${ _('Name') }</th>
-      <th>${ _('Value') }</th>
-    </tr>
-    </thead>
-    <tbody data-bind="foreach: Object.keys($data.properties)">
-      <tr>
-        <td data-bind="text: $data"></td>
-        <td>
-        <!-- ko template: { name: 'link-or-text${ SUFFIX }', data: { name: $data, value: $parent.properties[$data] } } --><!-- /ko -->
-        </td>
-      </tr>
-    </tbody>
-  </table>
-  <!-- /ko -->
-</script>
-
-
-<script type="text/html" id="render-page-counters${ SUFFIX }">
-  <!-- ko hueSpinner: { spin: !$data, center: true, size: 'xlarge' } --><!-- /ko -->
-
-  <!-- ko if: $data -->
-    <!-- ko ifnot: $data.counterGroup -->
-      <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
-    <!-- /ko -->
-    <!-- ko if: $data.counterGroup -->
-    <!-- ko foreach: $data.counterGroup -->
-      <h3 data-bind="text: counterGroupName"></h3>
-      <table class="table table-condensed">
-        <thead>
-        <tr>
-          <th>${ _('Name') }</th>
-          <th width="15%">${ _('Maps total') }</th>
-          <th width="15%">${ _('Reduces total') }</th>
-          <th width="15%">${ _('Total') }</th>
-        </tr>
-        </thead>
-        <tbody data-bind="foreach: counter">
-          <tr>
-            <td data-bind="text: name"></td>
-            <td data-bind="text: mapCounterValue"></td>
-            <td data-bind="text: reduceCounterValue"></td>
-            <td data-bind="text: totalCounterValue"></td>
-          </tr>
-        </tbody>
-      </table>
-    <!-- /ko -->
-    <!-- /ko -->
-  <!-- /ko -->
-</script>
-
-<script type="text/html" id="render-task-counters${ SUFFIX }">
-  <!-- ko hueSpinner: { spin: !$data.id, center: true, size: 'xlarge' } --><!-- /ko -->
-
-  <!-- ko if: $data.id -->
-    <!-- ko ifnot: $data.taskCounterGroup -->
-      <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
-    <!-- /ko -->
-    <!-- ko if: $data.taskCounterGroup -->
-    <!-- ko foreach: $data.taskCounterGroup -->
-      <h3 data-bind="text: counterGroupName"></h3>
-      <table class="table table-condensed">
-        <thead>
-        <tr>
-          <th>${ _('Name') }</th>
-          <th width="30%">${ _('Value') }</th>
-        </tr>
-        </thead>
-        <tbody data-bind="foreach: counter">
-          <tr>
-            <td data-bind="text: name"></td>
-            <td data-bind="text: value"></td>
-          </tr>
-        </tbody>
-      </table>
-    <!-- /ko -->
-    <!-- /ko -->
-  <!-- /ko -->
-</script>
-
-<script type="text/html" id="render-attempt-counters${ SUFFIX }">
-  <!-- ko hueSpinner: { spin: !$data.id, center: true, size: 'xlarge' } --><!-- /ko -->
-
-  <!-- ko if: $data.id -->
-    <!-- ko ifnot: $data.taskAttemptCounterGroup -->
-      <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
-    <!-- /ko -->
-    <!-- ko if: $data.taskAttemptCounterGroup -->
-    <!-- ko foreach: $data.taskAttemptCounterGroup -->
-      <h3 data-bind="text: counterGroupName"></h3>
-      <table class="table table-condensed">
-        <thead>
-        <tr>
-          <th>${ _('Name') }</th>
-          <th width="30%">${ _('Value') }</th>
-        </tr>
-        </thead>
-        <tbody data-bind="foreach: counter">
-          <tr>
-            <td data-bind="text: name"></td>
-            <td data-bind="text: value"></td>
-          </tr>
-        </tbody>
-      </table>
-    <!-- /ko -->
-    <!-- /ko -->
-  <!-- /ko -->
-</script>
-
-<script type="text/html" id="render-metadata${ SUFFIX }">
-  <!-- ko hueSpinner: { spin: !$data.property, center: true, size: 'xlarge' } --><!-- /ko -->
-  <!-- ko if: $data.property -->
-  <form class="form-search">
-    <input type="text" data-bind="clearable: $parent.metadataFilter, valueUpdate: 'afterkeydown'" class="input-xlarge search-query" placeholder="${_('Text Filter')}">
-  </form>
-  %if not is_mini:
-  <div id="job-mapreduce-page-metadata-template${ SUFFIX }" style="overflow-y: hidden; height: calc(100vh - 350px);">
-  % else:
-  <div id="job-mapreduce-page-metadata-template${ SUFFIX }" style="overflow-y: hidden; height: 400px;">
-  %endif
-    <table id="jobbrowserJobMetadataTable" class="table table-condensed">
-      <thead>
-      <tr>
-        <th>${ _('Name') }</th>
-        <th width="50%">${ _('Value') }</th>
-      </tr>
-      </thead>
-      <tbody data-bind="foreachVisible: { data: property, minHeight: 20, container: '#job-mapreduce-page-metadata-template${ SUFFIX }'}">
-        <tr>
-          <td data-bind="text: name"></td>
-          <td>
-            <!-- ko template: { name: 'link-or-text${ SUFFIX }', data: { name: name, value: value } } --><!-- /ko -->
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-  <!-- /ko -->
-</script>
-
-
-<script type="text/html" id="link-or-text${ SUFFIX }">
-  <!-- ko if: typeof $data.value === 'string' -->
-    <!-- ko if: $data.name.indexOf('logs') > -1 || $data.name.indexOf('trackingUrl') > -1 -->
-      <a href="javascript:void(0);" data-bind="text: $data.value, attr: { href: $data.value }" target="_blank"></a>
-    <!-- /ko -->
-    <!-- ko if: ($data.name.indexOf('dir') > -1 || $data.name.indexOf('path') > -1 || $data.name.indexOf('output') > -1 || $data.name.indexOf('input') > -1) && ($data.value.startsWith('/') || $data.value.startsWith('hdfs://') || $data.value.startsWith('s3a://')) -->
-      <a href="javascript:void(0);" data-bind="hueLink: '/filebrowser/view=' + $root.getHDFSPath($data.value), text: $data.value"></a>
-      <a href="javascript: void(0);" data-bind="storageContextPopover: { path: $root.getHDFSPath($data.value), orientation: 'left', offset: { top: 5 } }"><i class="fa fa-info"></i></a>
-    <!-- /ko -->
-    <!-- ko ifnot: $data.name.indexOf('logs') > -1 || $data.name.indexOf('trackingUrl') > -1 || (($data.name.indexOf('dir') > -1 || $data.name.indexOf('path') > -1 || $data.name.indexOf('output') > -1 || $data.name.indexOf('input') > -1) && ($data.value.startsWith('/') || $data.value.startsWith('hdfs://') || $data.value.startsWith('s3a://'))) -->
-      <span data-bind="text: $data.value"></span>
-    <!-- /ko -->
-  <!-- /ko -->
-  <!-- ko ifnot: typeof $data.value === 'string' -->
-    <span data-bind="text: $data.value"></span>
-  <!-- /ko -->
-</script>
-
-
-<script type="text/javascript">
-
-  (function () {
-
-    var Job = function (vm, job) {
-      var self = this;
-
-      self.paginationPage = ko.observable(1);
-      self.paginationOffset = ko.observable(1); // Starting index
-      self.paginationResultPage = ko.observable(50);
-      self.totalApps = ko.observable(job.properties && job.properties.total_actions || 0);
-      self.hasPagination = ko.computed(function() {
-        return self.totalApps() && ['workflows', 'schedules', 'bundles'].indexOf(vm.interface()) !== -1;
-      });
-      self.pagination = ko.pureComputed(function() {
-        return {
-          'page': self.paginationPage(),
-          'offset': self.paginationOffset(),
-          'limit': self.paginationResultPage()
-        };
-      });
-
-      self.pagination.subscribe(function() {
-        if (vm.interface() === 'schedules') {
-          self.updateJob(false, true);
-        }
-      });
-
-      self.showPreviousPage = ko.computed(function() {
-        return self.paginationOffset() > 1;
-      });
-      self.showNextPage = ko.computed(function() {
-        return self.totalApps() != null && (self.paginationOffset() + self.paginationResultPage()) < self.totalApps();
-      });
-      self.previousPage = function() {
-        self.paginationOffset(self.paginationOffset() - self.paginationResultPage());
-      };
-      self.nextPage = function() {
-        self.paginationOffset(self.paginationOffset() + self.paginationResultPage());
-      };
-
-      self.id = ko.observableDefault(job.id);
-      %if not is_mini:
-      self.id.subscribe(function () {
-        huePubSub.publish('graph.stop.refresh.view');
-      });
-      %endif
-      self.doc_url = ko.observableDefault(job.doc_url);
-      self.doc_url_modified = ko.computed(function () {
-        var url = self.doc_url();
-        if (window.KNOX_BASE_URL.length && window.URL && url) { // KNOX
-          try {
-            var parsedDocUrl = new URL(url);
-            var parsedKnoxUrl = new URL(window.KNOX_BASE_URL);
-            parsedDocUrl.hostname = parsedKnoxUrl.hostname;
-            parsedDocUrl.protocol = parsedKnoxUrl.protocol;
-            parsedDocUrl.port = parsedKnoxUrl.port;
-            var service = url.indexOf('livy') >= 0 ? '/livy' : '/impalaui';
-            parsedDocUrl.pathname = parsedKnoxUrl.pathname + service + parsedDocUrl.pathname;
-            return parsedDocUrl.toString();
-          } catch (e) {
-            return url;
-          }
-        } else if (window.KNOX_BASE_PATH.length && window.URL) { // DWX
-          var parsedDocUrl = new URL(url);
-          var service = url.indexOf('livy') >= 0 ? '/livy' : '/impalaui';
-          parsedDocUrl.pathname = parsedKnoxUrl.pathname + service + window.KNOX_BASE_PATH;
-        } else {
-          return url;
-        }
-      });
-      self.name = ko.observableDefault(job.name || job.id);
-      self.type = ko.observableDefault(job.type);
-      self.applicationType = ko.observableDefault(job.applicationType || '');
-
-      self.status = ko.observableDefault(job.status);
-      self.apiStatus = ko.observableDefault(job.apiStatus);
-      self.progress = ko.observableDefault(job.progress);
-      self.isRunning = ko.computed(function() {
-        return ['RUNNING', 'PAUSED'].indexOf(self.apiStatus()) != -1 || job.isRunning;
-      });
-
-      self.isRunning.subscribe(function () {
-        // The JB page for jobs is split in two tables, "Running" and "Completed", this esentially unchecks any job
-        // that moves from one table to the other.
-        ko.utils.arrayRemoveItem(vm.jobs.selectedJobs(), self)
-      });
-
-      self.user = ko.observableDefault(job.user);
-      self.queue = ko.observableDefault(job.queue);
-      self.cluster = ko.observableDefault(job.cluster);
-      self.duration = ko.observableDefault(job.duration);
-      self.submitted = ko.observableDefault(job.submitted);
-      self.canWrite = ko.observableDefault(job.canWrite == true);
-
-      self.logActive = ko.observable('default');
-      self.logsByName = ko.observable({});
-      self.logsListDefaults = ko.observable(['default', 'stdout', 'stderr', 'syslog']);
-      self.logsList = ko.observable(self.logsListDefaults());
-      self.logs = ko.pureComputed(function() {
-        return self.logsByName()[self.logActive()];
-      });
-
-      self.properties = ko.mapping.fromJS(job.properties && Object.keys(job.properties).reduce(function(p, key) { p[key] = ''; return p;}, {}) || {'properties': ''});
-      Object.keys(job.properties || []).reduce(function(p, key) { p[key](job.properties[key]); return p;}, self.properties);
-      self.mainType = ko.observable(vm.interface());
-      self.lastEvent = ko.observable(job.lastEvent || '');
-
-      self.syncCoorEndTimeDateUI = ko.observable(null);
-      self.syncCoorEndTimeTimeUI = ko.observable(null);
-      self.syncCoorPauseTimeDateUI = ko.observable(null);
-      self.syncCoorPauseTimeTimeUI = ko.observable(null);
-      self.syncCoorConcurrency = ko.observable(null);
-
-      self.showSyncCoorModal = function () {
-        self.syncCoorEndTimeDateUI(self.properties['endTimeDateUI']());
-        self.syncCoorEndTimeTimeUI(self.properties['endTimeTimeUI']());
-        self.syncCoorPauseTimeDateUI(self.properties['pauseTimeDateUI']());
-        self.syncCoorPauseTimeTimeUI(self.properties['pauseTimeTimeUI']());
-        self.syncCoorConcurrency(self.properties['concurrency']());
-
-        $('#syncCoordinatorModal${ SUFFIX }').modal('show');
-      };
-
-      self.coordinatorActions = ko.pureComputed(function() {
-        if (self.mainType().indexOf('schedule') != -1 && self.properties['tasks']) {
-          var apps = self.properties['tasks']().map(function (instance) {
-            var job = new CoordinatorAction(vm, ko.mapping.toJS(instance), self);
-            job.properties = ko.mapping.fromJS(instance);
-            return job;
-          });
-          var instances = new Jobs(vm);
-          instances.apps(apps);
-          instances.isCoordinator(true);
-          return instances;
-        }
-      });
-
-      self.textFilter = ko.observable('').extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 1000 } });
-      self.statesValuesFilter = ko.observableArray([
-        ko.mapping.fromJS({'value': 'completed', 'name': '${_("Succeeded")}', 'checked': false, 'klass': 'green'}),
-        ko.mapping.fromJS({'value': 'running', 'name': '${_("Running")}', 'checked': false, 'klass': 'orange'}),
-        ko.mapping.fromJS({'value': 'failed', 'name': '${_("Failed")}', 'checked': false, 'klass': 'red'}),
-      ]);
-      self.statesFilter = ko.computed(function () {
-        var checkedStates = ko.utils.arrayFilter(self.statesValuesFilter(), function (state) {
-          return state.checked();
-        });
-        return ko.utils.arrayMap(checkedStates, function(state){
-          return state.value()
-        });
-      });
-      self.typesValuesFilter = ko.observableArray([
-        ko.mapping.fromJS({'value': 'map', 'name': '${_("Map")}', 'checked': false, 'klass': 'green'}),
-        ko.mapping.fromJS({'value': 'reduce', 'name': '${_("Reduce")}', 'checked': false, 'klass': 'orange'}),
-      ]);
-      self.typesFilter = ko.computed(function () {
-        var checkedTypes = ko.utils.arrayFilter(self.typesValuesFilter(), function (type) {
-          return type.checked();
-        });
-        return ko.utils.arrayMap(checkedTypes, function(type){
-          return type.value()
-        });
-      });
-      self.filters = ko.pureComputed(function() {
-        return [
-          {'text': self.textFilter()},
-          {'states': ko.mapping.toJS(self.statesFilter())},
-          {'types': ko.mapping.toJS(self.typesFilter())},
-        ];
-      });
-      self.forceUpdatingJob = ko.observable(false);
-      self.filters.subscribe(function () {
-        if (self.type() === 'schedule') {
-          self.updateJob(false, true);
-        } else {
-          self.fetchProfile('tasks');
-        }
-      });
-      self.metadataFilter = ko.observable('');
-      self.metadataFilter.subscribe(function(newValue) {
-        $("#jobbrowserJobMetadataTable tbody tr").removeClass("hide");
-        $("#jobbrowserJobMetadataTable tbody tr").each(function () {
-          if ($(this).text().toLowerCase().indexOf(newValue.toLowerCase()) == -1) {
-            $(this).addClass("hide");
-          }
-        });
-      });
-      self.propertiesFilter = ko.observable('');
-      self.propertiesFilter.subscribe(function(newValue) {
-        $("#jobbrowserJobPropertiesTable tbody tr").removeClass("hide");
-        $("#jobbrowserJobPropertiesTable tbody tr").each(function () {
-          if ($(this).text().toLowerCase().indexOf(newValue.toLowerCase()) == -1) {
-            $(this).addClass("hide");
-          }
-        });
-      });
-
-      self.rerunModalContent = ko.observable('');
-
-      self.hasKill = ko.pureComputed(function() {
-        return self.type() && (
-          [
-            'MAPREDUCE',
-            'SPARK',
-            'workflow',
-            'schedule',
-            'bundle',
-            'QUERY',
-            'TEZ',
-            'YarnV2',
-            'DDL',
-            'schedule-hive',
-            'celery-beat',
-            'history'
-          ].indexOf(self.type()) != -1 ||
-          self.type().indexOf('Data Warehouse') != -1 ||
-          self.type().indexOf('Altus') != -1
-        );
-      });
-      self.killEnabled = ko.pureComputed(function() {
-        // Impala can kill queries that are finished, but not yet terminated
-        return self.hasKill() && self.canWrite() && self.isRunning();
-      });
-
-      self.hasResume = ko.pureComputed(function() {
-        return ['workflow', 'schedule', 'bundle', 'schedule-hive', 'celery-beat', 'history'].indexOf(self.type()) != -1;
-      });
-      self.resumeEnabled = ko.pureComputed(function() {
-        return self.hasResume() && self.canWrite() && self.apiStatus() == 'PAUSED';
-      });
-
-      self.hasRerun = ko.pureComputed(function() {
-        return ['workflow', 'schedule-task'].indexOf(self.type()) != -1;
-      });
-      self.rerunEnabled = ko.pureComputed(function() {
-        return self.hasRerun() && self.canWrite() && ! self.isRunning();
-      });
-
-      self.hasPause = ko.pureComputed(function() {
-        return ['workflow', 'schedule', 'bundle', 'celery-beat', 'schedule-hive', 'history'].indexOf(self.type()) != -1;
-      });
-      self.pauseEnabled = ko.pureComputed(function() {
-        return self.hasPause() && self.canWrite() && self.apiStatus() == 'RUNNING';
-      });
-
-      self.hasIgnore = ko.pureComputed(function() {
-        return ['schedule-task'].indexOf(self.type()) != -1;
-      });
-      self.ignoreEnabled = ko.pureComputed(function() {
-        return self.hasIgnore() && self.canWrite() && ! self.isRunning();
-      });
-
-      self.loadingJob = ko.observable(false);
-      var lastFetchJobRequest = null;
-      var lastUpdateJobRequest = null;
-      var lastFetchLogsRequest = null;
-      var lastFetchProfileRequest = null;
-      var lastFetchStatusRequest = null;
-
-      self._fetchJob = function (callback) {
-        if (vm.interface() == 'engines') {
-          huePubSub.publish('context.selector.set.cluster', 'AltusV2');
-          return;
-        }
-
-        return $.post("/jobbrowser/api/job/" + vm.interface(), {
-          cluster: ko.mapping.toJSON(vm.compute),
-          app_id: ko.mapping.toJSON(self.id),
-          interface: ko.mapping.toJSON(vm.interface),
-          pagination: ko.mapping.toJSON(self.pagination),
-          filters: ko.mapping.toJSON(self.filters)
-        }, function (data) {
-          if (data.status == 0) {
-            if (data.app) {
-              huePubSub.publish('jobbrowser.data', [data.app]);
-            }
-            if (callback) {
-              callback(data);
-            }
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        });
-      };
-
-      self._rewriteKnoxUrls = function (data) {
-        if (data && data.app && data.app.type === 'SPARK' && data.app.properties && data.app.properties.metadata) {
-          data.app.properties.metadata.forEach(function (item) {
-            if (item.name === 'trackingUrl') {
-              /*
-                Rewrite tracking url
-                Sample trackingUrl: http://<yarn>:8088/proxy/application_1652826179847_0003/
-                Knox URL: https://<knox-base>/yarnuiv2/redirect#/yarn-app/application_1652826179847_0003/attempts
-              */
-              var matches = item.value.match('(application_[0-9_]+)');
-              if (matches && matches.length > 1) {
-                var applicationId = matches[1];
-                item.value = window.KNOX_BASE_URL + '/yarnuiv2/redirect#/yarn-app/' + applicationId + '/attempts';
-              }
-            }
-            return;
-          });
-        }
-      }
-
-      self.onTableRowClick = function (event, id) {
-        var openInNewTab = event && (event.which === 2 || event.metaKey || event.ctrlKey);
-        var idToOpen = id || self.id();
-        if (idToOpen && idToOpen !== '-') {
-          if (openInNewTab) {
-            var urlParts = window.location.toString().split('#');
-            var newUrl = urlParts[0] + '#!id=' + idToOpen;
-            window.open(newUrl, '_blank');
-          } else {
-            self.id(idToOpen);
-            self.fetchJob();
-          }
-        }
-      }
-
-      self.fetchJob = function () {
-        // TODO: Remove cancelActiveRequest from apiHelper when in webpack
-        vm.apiHelper.cancelActiveRequest(lastFetchJobRequest);
-        vm.apiHelper.cancelActiveRequest(lastUpdateJobRequest);
-
-        self.loadingJob(true);
-
-        var interface = vm.interface();
-        if (/application_/.test(self.id()) || /job_/.test(self.id()) || /attempt_/.test(self.id())) {
-          interface = 'jobs';
-        }
-        if (/oozie-\w+-W/.test(self.id())) {
-          interface = 'workflows';
-        }
-        else if (/oozie-\w+-C/.test(self.id())) {
-          interface = 'schedules';
-        }
-        else if (/oozie-\w+-B/.test(self.id())) {
-          interface = 'bundles';
-        }
-        else if (/celery-beat-\w+/.test(self.id())) {
-          interface = 'celery-beat';
-        }
-        else if (/schedule-hive-\w+/.test(self.id())) {
-          interface = 'schedule-hive';
-        }
-        else if (/altus:dataeng/.test(self.id()) && /:job:/.test(self.id())) {
-          interface = 'dataeng-jobs';
-        }
-        else if (/altus:dataeng/.test(self.id()) && /:cluster:/.test(self.id())) {
-          interface = 'dataeng-clusters';
-        }
-        else if (/altus:dataware:k8/.test(self.id()) && /:cluster:/.test(self.id())) {
-          interface = 'dataware2-clusters';
-        }
-        else if (/altus:dataware/.test(self.id()) && /:cluster:/.test(self.id())) {
-          interface = 'dataware-clusters';
-        }
-        else if (/[a-z0-9]{16}:[a-z0-9]{16}/.test(self.id())) {
-          interface = 'queries-impala';
-        }
-        else if (/hive_[a-z0-9]*_[a-z0-9]*/.test(self.id())) {
-          interface = 'queries-hive';
-        }
-        else if (/livy-[0-9]+/.test(self.id())) {
-          interface = 'livy-sessions';
-        }
-
-        interface = interface.indexOf('dataeng') || interface.indexOf('dataware') ? interface : vm.isValidInterface(interface); // TODO: support multi cluster selection in isValidInterface
-        vm.interface(interface);
-
-        lastFetchJobRequest = self._fetchJob(function (data) {
-          if (data.status == 0) {
-            if (window.KNOX_BASE_URL && window.KNOX_BASE_URL.length) {
-              self._rewriteKnoxUrls(data);
-            }
-
-            vm.interface(interface);
-            vm.job(new Job(vm, data.app));
-            if (window.location.hash !== '#!id=' + vm.job().id()) {
-              hueUtils.changeURL('#!id=' + vm.job().id());
-            }
-            var crumbs = [];
-            if (/^appattempt_/.test(vm.job().id())) {
-              crumbs.push({'id': vm.job().properties['app_id'], 'name': vm.job().properties['app_id'], 'type': 'app'});
-            }
-            if (/^attempt_/.test(vm.job().id())) {
-              crumbs.push({'id': vm.job().properties['app_id'], 'name': vm.job().properties['app_id'], 'type': 'app'});
-              crumbs.push({'id': vm.job().properties['task_id'], 'name': vm.job().properties['task_id'], 'type': 'task'});
-            }
-            if (/^task_/.test(vm.job().id())) {
-              crumbs.push({'id': vm.job().properties['app_id'], 'name': vm.job().properties['app_id'], 'type': 'app'});
-            }
-            if (/_executor_/.test(vm.job().id())) {
-              crumbs.push({'id': vm.job().properties['app_id'], 'name': vm.job().properties['app_id'], 'type': 'app'});
-            }
-            var oozieWorkflow = vm.job().name().match(/oozie:launcher:T=.+?:W=.+?:A=.+?:ID=(.+?-oozie-\w+-W)$/i);
-            if (oozieWorkflow) {
-              crumbs.push({'id': oozieWorkflow[1], 'name': oozieWorkflow[1], 'type': 'workflow'});
-            }
-
-            if (/-oozie-\w+-W@/.test(vm.job().id())) {
-              crumbs.push({'id': vm.job().properties['workflow_id'], 'name': vm.job().properties['workflow_id'], 'type': 'workflow'});
-            }
-            else if (/-oozie-\w+-W/.test(vm.job().id())) {
-              if (vm.job().properties['bundle_id']()) {
-                crumbs.push({'id': vm.job().properties['bundle_id'](), 'name': vm.job().properties['bundle_id'](), 'type': 'bundle'});
-              }
-              if (vm.job().properties['coordinator_id']()) {
-                crumbs.push({'id': vm.job().properties['coordinator_id'](), 'name': vm.job().properties['coordinator_id'](), 'type': 'schedule'});
-              }
-            }
-            else if (/-oozie-\w+-C/.test(vm.job().id())) {
-              if (vm.job().properties['bundle_id']()) {
-                crumbs.push({'id': vm.job().properties['bundle_id'](), 'name': vm.job().properties['bundle_id'](), 'type': 'bundle'});
-              }
-            }
-
-            if (vm.job().type() == 'SPARK_EXECUTOR') {
-              crumbs.push({'id': vm.job().id(), 'name': vm.job().properties['executor_id'](), 'type': vm.job().type()});
-            }
-            else {
-              crumbs.push({'id': vm.job().id(), 'name': vm.job().name(), 'type': vm.job().type()});
-            }
-
-            vm.resetBreadcrumbs(crumbs);
-            // Show is still bound to old job, setTimeout allows knockout model change event done at begining of this method to sends it's notification
-            setTimeout(function () {
-              if (vm.job().mainType() === 'queries-impala' && !$("#queries-page-plan${ SUFFIX }").parent().children().hasClass("active")) {
-                $("a[href=\'#queries-page-plan${ SUFFIX }\']").tab("show");
-              }
-            }, 0);
-            %if not is_mini:
-            if (vm.job().type() === 'workflow' && !vm.job().workflowGraphLoaded) {
-              vm.job().updateWorkflowGraph();
-            }
-            %else:
-            if (vm.job().type() === 'workflow') {
-              vm.job().fetchProfile('properties');
-              $('a[href="#workflow-page-metadata${ SUFFIX }"]').tab('show');
-            }
-            $('#rerun-modal${ SUFFIX }').on('shown', function (e) {
-                // Replaces dark modal backdrop from the end of the body tag to the closer scope
-                // in order to activate z-index effect.
-                var rerunModalData = $(this).data('modal');
-                rerunModalData.$backdrop.appendTo("#jobbrowserMiniComponents");
-            });
-            $('#killModal${ SUFFIX }').on('shown', function (e) {
-                 var killModalData = $(this).data('modal');
-                 killModalData.$backdrop.appendTo("#jobbrowserMiniComponents");
-            });
-            %endif
-
-            vm.job().fetchLogs();
-
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        }).always(function () {
-          self.loadingJob(false);
-        });
-      };
-
-      self.updateJob = function (updateLogs, forceUpdate) {
-        huePubSub.publish('graph.refresh.view');
-        var deferred = $.Deferred();
-        if (vm.job() == self && (self.apiStatus() == 'RUNNING' || forceUpdate)) {
-          vm.apiHelper.cancelActiveRequest(lastUpdateJobRequest);
-          if (forceUpdate) {
-            self.forceUpdatingJob(true);
-          }
-          lastUpdateJobRequest = self._fetchJob(function (data) {
-            var requests = [];
-            if (['schedule', 'workflow'].indexOf(vm.job().type()) >= 0) {
-              window.hueUtils.deleteAllEmptyStringKeys(data.app); // It's preferable for our backend to return empty strings for various values in order to initialize them, but they shouldn't overwrite any values that are currently set.
-              var selectedIDs = []
-              if (vm.job().coordinatorActions()) {
-                selectedIDs = vm.job().coordinatorActions().selectedJobs().map(
-                  function(coordinatorAction) {
-                      return coordinatorAction.id();
-                  }
-                );
-              }
-              vm.job = ko.mapping.fromJS(data.app, {}, vm.job);
-              if (selectedIDs.length > 0) {
-                vm.job().coordinatorActions().selectedJobs(
-                  vm.job().coordinatorActions().apps().filter(function(coordinatorAction){
-                      return selectedIDs.indexOf(coordinatorAction.id()) != -1
-                  })
-                )
-              }
-              if (vm.job().type() == 'schedule') {
-                self.totalApps(data.app.properties.total_actions);
-              }
-            } else {
-              requests.push(vm.job().fetchStatus());
-            }
-            if(updateLogs !== false) {
-              requests.push(vm.job().fetchLogs(vm.job().logActive()));
-            }
-            var profile = $("div[data-jobType] .tab-content .active").data("profile");
-            if (profile) {
-              requests.push(vm.job().fetchProfile(profile));
-            }
-            $.when.apply(this, requests).done(function (){
-              deferred.resolve();
-            });
-          }).always(function () {
-            self.forceUpdatingJob(false);
-          });
-        }
-        return deferred;
-      };
-
-      self.fetchLogs = function (name) {
-        name = name || 'default';
-        vm.apiHelper.cancelActiveRequest(lastFetchLogsRequest);
-        lastFetchLogsRequest = $.post("/jobbrowser/api/job/logs?is_embeddable=${ str(is_embeddable).lower() }", {
-          cluster: ko.mapping.toJSON(vm.compute),
-          app_id: ko.mapping.toJSON(self.id),
-          interface: ko.mapping.toJSON(vm.interface),
-          type: ko.mapping.toJSON(self.type),
-          name: ko.mapping.toJSON(name)
-        }, function (data) {
-          if (data.status == 0) {
-            var result = self.logsByName();
-            result[name] = data.logs.logs;
-            self.logsByName(result);
-            if (data.logs.logsList && data.logs.logsList.length) {
-              var logsListDefaults = self.logsListDefaults();
-              self.logsList(logsListDefaults.concat(data.logs.logsList.filter(function(log) { return logsListDefaults.indexOf(log) < 0; })));
-            }
-            if ($('.jb-panel pre:visible').length > 0){
-              $('.jb-panel pre:visible').css('overflow-y', 'auto').height(Math.max(200, $(window).height() - $('.jb-panel pre:visible').offset().top - $('.page-content').scrollTop() - 75));
-            }
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        });
-        return lastFetchLogsRequest;
-      };
-
-      self.submitQueryProfileDownloadForm = function(name) {
-        var $downloadForm = $(
-          '<form method="POST" class="download-form" style="display: inline" action="' +
-            window.HUE_BASE_URL +
-            '/jobbrowser/api/job/profile"></form>'
-        );
-
-        $('<input type="hidden" name="csrfmiddlewaretoken" />')
-          .val(window.CSRF_TOKEN)
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="cluster" />')
-          .val(ko.mapping.toJSON(vm.cluster))
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="app_id" />')
-          .val(ko.mapping.toJSON(self.id))
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="interface" />')
-          .val(ko.mapping.toJSON(vm.interface))
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="app_type" />')
-          .val(ko.mapping.toJSON(self.type))
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="app_property" />')
-          .val(ko.mapping.toJSON(name))
-          .appendTo($downloadForm);
-        $('<input type="hidden" name="app_filters" />')
-          .val(ko.mapping.toJSON(self.filters))
-          .appendTo($downloadForm);
-
-        $('#downloadProgressModal').append($downloadForm);
-
-        huePubSub.publish('ignore.next.unload');
-        $downloadForm.submit();
-      };
-
-      self.fetchProfile = function (name, callback) {
-        vm.apiHelper.cancelActiveRequest(lastFetchProfileRequest);
-        lastFetchProfileRequest = $.post("/jobbrowser/api/job/profile", {
-          cluster: ko.mapping.toJSON(vm.compute),
-          app_id: ko.mapping.toJSON(self.id),
-          interface: ko.mapping.toJSON(vm.interface),
-          app_type: ko.mapping.toJSON(self.type),
-          app_property: ko.mapping.toJSON(name),
-          app_filters: ko.mapping.toJSON(self.filters),
-        }, function (data) {
-          if (data.status == 0) {
-            self.properties[name](data[name]);
-            if (callback) {
-              callback(data);
-            }
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        });
-        return lastFetchProfileRequest;
-      };
-
-      self.fetchStatus = function () {
-        vm.apiHelper.cancelActiveRequest(lastFetchStatusRequest);
-        lastFetchStatusRequest = $.post("/jobbrowser/api/job", {
-          cluster: ko.mapping.toJSON(vm.compute),
-          app_id: ko.mapping.toJSON(self.id),
-          interface: ko.mapping.toJSON(self.mainType)
-        }, function (data) {
-          if (data.status == 0) {
-            self.status(data.app.status);
-            self.apiStatus(data.app.apiStatus);
-            self.progress(data.app.progress);
-            self.canWrite(data.app.canWrite);
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        });
-        return lastFetchStatusRequest;
-      };
-
-      self.control = function (action) {
-        if (action == 'rerun') {
-          $.get('/oozie/rerun_oozie_job/' + self.id() + '/?format=json' + '${ "&is_mini=true" if is_mini else "" | n }', function(response) {
-            $('#rerun-modal${ SUFFIX }').modal('show');
-            self.rerunModalContent(response);
-          });
-        } else if (action == 'sync_coordinator') {
-          var $syncCoordinatorModal = $('#syncCoordinatorModal');
-          var endTimeDateUI = $syncCoordinatorModal.find("#endTimeDateUI").val();
-          var endTimeTimeUI = $syncCoordinatorModal.find("#endTimeTimeUI").val();
-          var endTime = '';
-          if (endTimeDateUI != '' && endTimeTimeUI != '') {
-            endTime = endTimeDateUI + 'T' + endTimeTimeUI;
-          }
-          var pauseTimeDateUI = $syncCoordinatorModal.find("#pauseTimeDateUI").val();
-          var pauseTimeTimeUI = $syncCoordinatorModal.find("#pauseTimeTimeUI").val();
-          var pauseTime = '';
-          if (pauseTimeDateUI != '' && pauseTimeTimeUI != '') {
-            pauseTime = pauseTimeDateUI + 'T' + pauseTimeTimeUI;
-          }
-
-          var clear_pause_time = $syncCoordinatorModal.find("#id_clearPauseTime")[0].checked;
-          var concurrency =  $syncCoordinatorModal.find("#id_concurrency").val();
-
-          $.post('/oozie/manage_oozie_jobs/' + self.id() + '/change', {
-            "end_time": endTime,
-            "pause_time": pauseTime,
-            "clear_pause_time": clear_pause_time,
-            "concurrency": concurrency
-          }, function (data) {
-            if (data.status == 0) {
-              $.jHueNotify.info("${ _('Successfully updated Coordinator Job Properties') }");
-            } else {
-              huePubSub.publish('hue.global.error', {message: data.message});
-            }
-          }).fail(function (xhr, textStatus, errorThrown) {
-            huePubSub.publish('hue.global.error', {message: xhr.responseText});
-          });
-
-        } else if (action == 'sync_workflow') {
-          csrfmiddlewaretoken = "${request and request.COOKIES.get('csrftoken', '')}";
-
-          $.get('/oozie/sync_coord_workflow/' + self.id(), {
-            format: 'json'
-          }, function (data) {
-            $(document).trigger("showSubmitPopup", data);
-          }).fail(function (xhr, textStatus, errorThrown) {
-            huePubSub.publish('hue.global.error', {message: xhr.responseText});
-          });
-        } else {
-          vm.jobs._control([self.id()], action, function(data) {
-            huePubSub.publish('hue.global.info', { message: data.message });
-            self.fetchStatus();
-          });
-        }
-      };
-
-      self.updateClusterWorkers = ko.observable(1);
-      self.updateClusterAutoResize = ko.observable(false);
-      self.updateClusterAutoResizeMin = ko.observable(1);
-      self.updateClusterAutoResizeMax = ko.observable(3);
-      self.updateClusterAutoResizeCpu = ko.observable(80);
-      self.updateClusterAutoPause = ko.observable();
-
-      self.updateClusterShow = function() {
-        self.updateClusterWorkers(self.properties['properties']['workerReplicas']());
-        self.updateClusterAutoResize(self.properties['properties']['workerAutoResize']());
-        if (self.properties['properties']['workerAutoResize']()) {
-          self.updateClusterAutoResizeMin(self.properties['properties']['workerAutoResizeMin']());
-          self.updateClusterAutoResizeMax(self.properties['properties']['workerAutoResizeMax']());
-          self.updateClusterAutoResizeCpu(self.properties['properties']['workerAutoResizeCpu']());
-        }
-      };
-
-      self.clusterConfigModified = ko.pureComputed(function () {
-        return (self.updateClusterWorkers() > 0 && self.updateClusterWorkers() !== self.properties['properties']['workerReplicas']()) ||
-            (self.updateClusterAutoResize() !== self.properties['properties']['workerAutoResize']());
-      });
-
-      ## TODO Move to control
-      self.updateCluster = function() {
-        $.post("/metadata/api/analytic_db/update_cluster/", {
-          "is_k8": vm.interface().indexOf('dataware2-clusters') != -1,
-          "cluster_name": self.id(),
-          "workers_group_size": self.updateClusterWorkers(),
-          "auto_resize_changed": self.updateClusterAutoResize() !== self.properties['properties']['workerAutoResize'](),
-          "auto_resize_enabled": self.updateClusterAutoResize(),
-          "auto_resize_min": self.updateClusterAutoResizeMin(),
-          "auto_resize_max": self.updateClusterAutoResizeMax(),
-          "auto_resize_cpu": self.updateClusterAutoResizeCpu()
-        }, function(data) {
-          console.log(ko.mapping.toJSON(data));
-          ## huePubSub.publish('hue.global.info', { message: ko.mapping.toJSON(data) });
-          self.updateJob();
-        });
-      }
-
-      self.troubleshoot = function (action) {
-        $.post('/metadata/api/workload_analytics/get_operation_execution_details', {
-          operation_id: ko.mapping.toJSON(self.id())
-        }, function(data) {
-          console.log(ko.mapping.toJSON(data));
-        });
-      }
-
-
-      self.workflowGraphLoaded = false;
-
-      self.lastArrowsPosition = {
-        top: 0,
-        left: 0
-      }
-      self.redrawOnResize = function(){
-        huePubSub.publish('graph.draw.arrows');
-      }
-      self.initialArrowsDrawingCount = 0;
-      self.initialArrowsDrawing = function() {
-        if (self.initialArrowsDrawingCount < 20) {
-          self.initialArrowsDrawingCount++;
-          huePubSub.publish('graph.draw.arrows');
-          window.setTimeout(self.initialArrowsDrawing, 100);
-        }
-        else if (self.initialArrowsDrawingCount < 30){
-          self.initialArrowsDrawingCount++;
-          huePubSub.publish('graph.draw.arrows');
-          window.setTimeout(self.initialArrowsDrawing, 500);
-        }
-        else {
-          self.initialArrowsDrawingCount = 0;
-        }
-      }
-
-      self.updateWorkflowGraph = function() {
-        huePubSub.publish('graph.stop.refresh.view');
-
-        $('canvas').remove();
-
-        if (vm.job().type() === 'workflow') {
-          $('#workflow-page-graph${ SUFFIX }').html('<div class="hue-spinner"><i class="fa fa-spinner fa-spin hue-spinner-center hue-spinner-xlarge"></i></div>');
-          $.ajax({
-            url: "/oozie/list_oozie_workflow/" + vm.job().id(),
-            data: {
-              'graph': true,
-              'element': 'workflow-page-graph${ SUFFIX }',
-              'is_jb2': true
-            },
-            beforeSend: function (xhr) {
-              xhr.setRequestHeader("X-Requested-With", "Hue");
-            },
-            dataType: "html",
-            success: function (response) {
-              self.workflowGraphLoaded = true;
-
-              huePubSub.publish('hue4.process.headers', {
-                response: response,
-                callback: function (r) {
-                  $('#workflow-page-graph${ SUFFIX }').html(r);
-                  self.initialArrowsDrawing();
-                  $(window).on('resize',self.redrawOnResize);
-                }
-              });
-            }
-          });
-        }
-      };
-    };
-
-    var CoordinatorAction = function (vm, job, coordinator) {
-      var self = this;
-      Job.apply(self, [vm, job]);
-      self.coordinator = coordinator;
-
-      self.canWrite = ko.computed(function () {
-        return self.coordinator.canWrite();
-      });
-
-      self.resumeEnabled = function () {
-        return false;
-      };
-    };
-
-    var Jobs = function (vm) {
-      var self = this;
-
-      self.apps = ko.observableArray().extend({ rateLimit: 50 });
-      self.runningApps = ko.pureComputed(function() {
-        return $.grep(self.apps(), function(app) {
-          return app.isRunning();
-        });
-      });
-      self.finishedApps = ko.pureComputed(function() {
-        return $.grep(self.apps(), function(app) {
-          return !app.isRunning();
-        });
-      });
-      self.totalApps = ko.observable(null);
-      self.isCoordinator = ko.observable(false);
-
-      self.loadingJobs = ko.observable(false);
-      self.selectedJobs = ko.observableArray();
-
-      self.hasKill = ko.pureComputed(function() {
-        return [
-          'jobs',
-          'workflows',
-          'schedules',
-          'bundles',
-          'queries-impala',
-          'dataeng-jobs',
-          'dataeng-clusters',
-          'dataware-clusters',
-          'dataware2-clusters',
-          'celery-beat',
-          'schedule-hive',
-          'history'
-        ].indexOf(vm.interface()) != -1 && !self.isCoordinator();
-      });
-      self.killEnabled = ko.pureComputed(function() {
-        return self.hasKill() && self.selectedJobs().length > 0 && $.grep(self.selectedJobs(), function(job) {
-          return job.killEnabled();
-        }).length == self.selectedJobs().length;
-      });
-
-      self.hasResume = ko.pureComputed(function() {
-        return [
-          'workflows',
-          'schedules',
-          'bundles',
-          'dataware2-clusters',
-          'celery-beat',
-          'schedule-hive',
-          'history'
-        ].indexOf(vm.interface()) != -1 && !self.isCoordinator();
-      });
-      self.resumeEnabled = ko.pureComputed(function() {
-        return self.hasResume() && self.selectedJobs().length > 0 && $.grep(self.selectedJobs(), function(job) {
-          return job.resumeEnabled();
-        }).length == self.selectedJobs().length;
-      });
-
-      self.hasRerun = ko.pureComputed(function() {
-        return self.isCoordinator();
-      });
-      self.rerunEnabled = ko.pureComputed(function() {
-          var validSelectionCount =
-                  self.selectedJobs().length === 1 ||
-                  (self.selectedJobs().length > 1 && vm.interface() === 'schedules');
-          return self.hasRerun() && validSelectionCount && $.grep(self.selectedJobs(), function (job) {
-              return job.rerunEnabled();
-          }).length === self.selectedJobs().length;
-      });
-
-      self.hasPause = ko.pureComputed(function() {
-        return [
-          'workflows',
-          'schedules',
-          'bundles',
-          'dataware2-clusters',
-          'celery-beat',
-          'schedule-hive',
-          'history'
-        ].indexOf(vm.interface()) != -1 && !self.isCoordinator();
-      });
-      self.pauseEnabled = ko.pureComputed(function() {
-        return self.hasPause() && self.selectedJobs().length > 0 && $.grep(self.selectedJobs(), function(job) {
-          return job.pauseEnabled();
-        }).length == self.selectedJobs().length;
-      });
-
-      self.hasIgnore = ko.pureComputed(function() {
-        return self.isCoordinator();
-      });
-      self.ignoreEnabled = ko.pureComputed(function() {
-        return self.hasIgnore() && self.selectedJobs().length > 0 && $.grep(self.selectedJobs(), function(job) {
-          return job.ignoreEnabled();
-        }).length == self.selectedJobs().length;
-      });
-
-      self.textFilter = ko.observable('user:${ user.username } ').extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 1000 } });
-      self.statesValuesFilter = ko.observableArray([
-        ko.mapping.fromJS({'value': 'completed', 'name': '${_("Succeeded")}', 'checked': false, 'klass': 'green'}),
-        ko.mapping.fromJS({'value': 'running', 'name': '${_("Running")}', 'checked': false, 'klass': 'orange'}),
-        ko.mapping.fromJS({'value': 'failed', 'name': '${_("Failed")}', 'checked': false, 'klass': 'red'}),
-      ]);
-      self.statesFilter = ko.computed(function () {
-        var checkedStates = ko.utils.arrayFilter(self.statesValuesFilter(), function (state) {
-          return state.checked();
-        });
-        return ko.utils.arrayMap(checkedStates, function(state){
-          return state.value()
-        });
-      });
-      self.timeValueFilter = ko.observable(7).extend({ throttle: 500 });
-      self.timeUnitFilter = ko.observable('days').extend({ throttle: 500 });
-      self.timeUnitFilterUnits = ko.observable([
-        {'value': 'days', 'name': '${_("days")}'},
-        {'value': 'hours', 'name': '${_("hours")}'},
-        {'value': 'minutes', 'name': '${_("minutes")}'},
-      ]);
-
-      self.hasPagination = ko.computed(function() {
-        return ['workflows', 'schedules', 'bundles'].indexOf(vm.interface()) != -1;
-      });
-      self.paginationPage = ko.observable(1);
-      self.paginationOffset = ko.observable(1); // Starting index
-      self.paginationResultPage = ko.observable(100);
-      self.pagination = ko.computed(function() {
-        return {
-            'page': self.paginationPage(),
-            'offset': self.paginationOffset(),
-            'limit': self.paginationResultPage()
-        };
-      });
-
-      self.showPreviousPage = ko.computed(function() {
-        return self.paginationOffset() > 1;
-      });
-      self.showNextPage = ko.computed(function() {
-        return self.totalApps() != null && (self.paginationOffset() + self.paginationResultPage()) < self.totalApps();
-      });
-      self.previousPage = function() {
-        self.paginationOffset(self.paginationOffset() - self.paginationResultPage());
-      };
-      self.nextPage = function() {
-        self.paginationOffset(self.paginationOffset() + self.paginationResultPage());
-      };
-
-      self.searchFilters = ko.pureComputed(function() {
-        return [
-          {'text': self.textFilter()},
-          {'time': {'time_value': self.timeValueFilter(), 'time_unit': self.timeUnitFilter()}},
-          {'states': ko.mapping.toJS(self.statesFilter())},
-        ];
-      });
-      self.searchFilters.subscribe(function() {
-        self.paginationOffset(1);
-      });
-      self.paginationFilters = ko.pureComputed(function() {
-        return [
-          {'pagination': self.pagination()},
-        ];
-      });
-      self.filters = ko.pureComputed(function() {
-        return self.searchFilters().concat(self.paginationFilters());
-      });
-      self.filters.subscribe(function(value) {
-        self.fetchJobs();
-      });
-
-
-      self._fetchJobs = function (callback) {
-        return $.post("/jobbrowser/api/jobs/" + vm.interface(), {
-          cluster: ko.mapping.toJSON(vm.compute),
-          interface: ko.mapping.toJSON(vm.interface),
-          filters: ko.mapping.toJSON(self.filters),
-        }, function (data) {
-          if (data.status == 0) {
-            if (data.apps && data.apps.length) {
-              huePubSub.publish('jobbrowser.data', data.apps);
-            }
-            if (callback) {
-              callback(data);
-            };
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        });
-      };
-
-      var lastFetchJobsRequest = null;
-      var lastUpdateJobsRequest = null;
-      self.showJobCountBanner = ko.pureComputed(function() {
-        return self.apps().length == ${ MAX_JOB_FETCH.get() };
-      });
-
-      self.fetchJobs = function () {
-        if(vm.interface() === 'hive-queries' || vm.interface() === 'impala-queries') {
-          return;
-        }
-
-        vm.apiHelper.cancelActiveRequest(lastUpdateJobsRequest);
-        vm.apiHelper.cancelActiveRequest(lastFetchJobsRequest);
-
-        self.loadingJobs(true);
-        vm.job(null);
-        lastFetchJobsRequest = self._fetchJobs(function(data) {
-          var apps = [];
-          if (data && data.apps) {
-            data.apps.forEach(function (job) {
-              apps.push(new Job(vm, job));
-            });
-          }
-          self.apps(apps);
-          self.totalApps(data.total);
-        }).always(function () {
-          self.loadingJobs(false);
-        });
-      }
-
-      self.updateJobs = function () {
-        if(vm.interface() === 'hive-queries' || vm.interface() === 'impala-queries') {
-          return;
-        }
-
-        vm.apiHelper.cancelActiveRequest(lastUpdateJobsRequest);
-
-        lastFetchJobsRequest = self._fetchJobs(function(data) {
-          if (data && data.apps) {
-            var i = 0, j = 0;
-            var newJobs = [];
-
-            while ((self.apps().length == 0 || i < self.apps().length) && j < data.apps.length) { // Nothing displayed or compare existing
-              if (self.apps().length == 0 || self.apps()[i].id() != data.apps[j].id) {
-                // New Job
-                newJobs.unshift(new Job(vm, data.apps[j]));
-                j++;
-              } else {
-                // Updated jobs
-                if (self.apps()[i].status() != data.apps[j].status) {
-                  self.apps()[i].status(data.apps[j].status);
-                  self.apps()[i].apiStatus(data.apps[j].apiStatus);
-                  self.apps()[i].canWrite(data.apps[j].canWrite);
-                }
-                i++;
-                j++;
-              }
-            }
-
-            if (i < self.apps().length) {
-              self.apps.splice(i, self.apps().length - i);
-            }
-
-            newJobs.forEach(function (job) {
-              self.apps.unshift(job);
-            });
-
-            self.totalApps(data.total);
-          }
-        });
-        return lastFetchJobsRequest;
-      };
-
-      self.createClusterShow = ko.observable(false);
-      self.createClusterName = ko.observable('');
-      self.createClusterWorkers = ko.observable(1);
-      self.createClusterShowWorkers = ko.observable(false);
-      self.createClusterAutoResize = ko.observable(false);
-      self.createClusterAutoPause = ko.observable(false);
-
-      self.createClusterFormReset = function() {
-        self.createClusterName('');
-        self.createClusterWorkers(1);
-        self.createClusterAutoResize(false);
-        self.createClusterAutoPause(false);
-      }
-
-      self.createCluster = function() {
-        if (vm.interface().indexOf('dataeng') != -1) {
-          $.post("/metadata/api/dataeng/create_cluster/", {
-            "cluster_name": "cluster_name",
-            "cdh_version": "CDH515",
-            "public_key": "public_key",
-            "instance_type": "m4.xlarge",
-            "environment_name": "crn:altus:environments:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:environment:analytics/236ebdda-18bd-428a-9d2b-cd6973d42946",
-            "workers_group_size": "3",
-            "namespace_name": "crn:altus:sdx:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:namespace:analytics/7ea35fe5-dbc9-4b17-92b1-97a1ab32e410"
-          }, function(data) {
-            console.log(ko.mapping.toJSON(data));
-            huePubSub.publish('hue.global.info', { message: ko.mapping.toJSON(data) });
-            self.updateJobs();
-            huePubSub.publish('context.catalog.refresh');
-          });
-        } else {
-          $.post("/metadata/api/analytic_db/create_cluster/", {
-            "is_k8": vm.interface().indexOf('dataware2-clusters') != -1,
-            "cluster_name": self.createClusterName(),
-            "cluster_hdfs_host": "hdfs-namenode",
-            "cluster_hdfs_port": 9820,
-            "cdh_version": "CDH515",
-            "public_key": "public_key",
-            "instance_type": "m4.xlarge",
-            "environment_name": "crn:altus:environments:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:environment:jheyming-secure/b4e6d99a-261f-4ada-9b4a-576aa0af8979",
-            "workers_group_size": self.createClusterWorkers(),
-            "namespace_name": "crn:altus:sdx:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:namespace:analytics/7ea35fe5-dbc9-4b17-92b1-97a1ab32e410"
-          }, function(data) {
-            console.log(ko.mapping.toJSON(data));
-            self.createClusterFormReset();
-            ##huePubSub.publish('hue.global.info', { message: ko.mapping.toJSON(data) });
-            self.updateJobs();
-            huePubSub.publish('context.catalog.refresh');
-          });
-        }
-        self.createClusterShow(false);
-      };
-
-      self.control = function (action) {
-        if (action === 'rerun') {
-          $.get('/oozie/rerun_oozie_coord/' + vm.job().id() + '/?format=json' + '${ "&is_mini=true" if is_mini else "" | n }', function(response) {
-            $('#rerun-modal${ SUFFIX }').modal('show');
-            vm.job().rerunModalContent(response);
-            // Force Knockout to handle the update of rerunModalContent before trying to modify its DOM
-            ko.tasks.runEarly();
-
-            var frag = document.createDocumentFragment();
-            vm.job().coordinatorActions().selectedJobs().forEach(function (item) {
-              var option = $('<option>', {
-                value: item.properties.number(),
-                selected: true
-              });
-              option.appendTo($(frag));
-            });
-            $('#id_actions${ SUFFIX }').find('option').remove();
-            $(frag).appendTo('#id_actions${ SUFFIX }');
-          });
-        } else if (action === 'ignore') {
-          $.post('/oozie/manage_oozie_jobs/' + vm.job().id() + '/ignore', {
-            actions: $.map(vm.job().coordinatorActions().selectedJobs(), function(wf) {
-              return wf.properties.number();
-            }).join(' ')
-          }, function(response) {
-            vm.job().apiStatus('RUNNING');
-            vm.job().updateJob();
-          });
-        } else {
-          self._control(
-            $.map(self.selectedJobs(), function(job) {
-              return job.id();
-            }),
-            action,
-            function(data) {
-              huePubSub.publish('hue.global.info', { message: data.message });
-              self.updateJobs();
-            }
-          )
-        }
-      }
-
-      self._control = function (app_ids, action, callback) {
-        $.post("/jobbrowser/api/job/action/" + vm.interface() + "/" + action, {
-          app_ids: ko.mapping.toJSON(app_ids),
-          interface: ko.mapping.toJSON(vm.interface),
-          operation: ko.mapping.toJSON({action: action})
-        }, function (data) {
-          if (data.status === 0) {
-            if (callback) {
-              callback(data);
-            }
-            if (vm.interface().indexOf('clusters') !== -1 && action === 'kill') {
-              huePubSub.publish('context.catalog.refresh');
-              self.selectedJobs([]);
-            }
-          } else {
-            huePubSub.publish('hue.global.error', {message: data.message});
-          }
-        }).always(function () {
-        });
-      };
-    };
-
-    var JobBrowserViewModel = function () {
-      var self = this;
-
-      self.apiHelper = window.apiHelper;
-      self.assistAvailable = ko.observable(true);
-      self.isLeftPanelVisible = ko.observable();
-      window.hueUtils.withLocalStorage('assist.assist_panel_visible', self.isLeftPanelVisible, true);
-      self.isLeftPanelVisible.subscribe(function () {
-        huePubSub.publish('assist.forceRender');
-      });
-
-      self.appConfig = ko.observable();
-      self.clusterType = ko.observable();
-      self.isMini = ko.observable(false);
-
-      self.cluster = ko.observable();
-      self.compute = ko.observable();
-      self.compute.subscribe(function () {
-        if (self.interface()) {
-          self.jobs.fetchJobs();
-        }
-      });
-
-      self.availableInterfaces = ko.pureComputed(function () {
-        var isDialectEnabled = function (dialect) {
-          return self.appConfig()?.editor?.interpreter_names?.indexOf(dialect) >= 0;
-        };
-
-        var historyInterfaceCondition = function () {
-          return '${ ENABLE_HISTORY_V2.get() }' == 'True';
-        };
-        var jobsInterfaceCondition = function () {
-          return !window.getLastKnownConfig().has_computes && self.appConfig() && self.appConfig()['browser'] && self.appConfig()['browser']['interpreter_names'].indexOf('yarn') != -1 && (!self.cluster() || self.cluster()['type'].indexOf('altus') == -1);
-        };
-        var dataEngInterfaceCondition = function () {
-          return self.cluster() && self.cluster()['type'] == 'altus-de';
-        };
-        var enginesInterfaceCondition = function () {
-          return self.cluster() && self.cluster()['type'] == 'altus-engines';
-        };
-        var dataWarehouseInterfaceCondition = function () {
-          return self.cluster() && self.cluster()['type'] == 'altus-dw';
-        };
-        var dataWarehouse2InterfaceCondition = function () {
-          return self.cluster() && self.cluster()['type'] == 'altus-dw2';
-        };
-        var schedulerInterfaceCondition = function () {
-          return '${ user.has_hue_permission(action="access", app="oozie") }' == 'True' && (!self.cluster() || self.cluster()['type'].indexOf('altus') == -1) && self.appConfig() && self.appConfig()['scheduler'];
-        };
-        var schedulerExtraInterfaceCondition = function () {
-          return '${ is_mini }' == 'False' && schedulerInterfaceCondition();
-        };
-        var schedulerBeatInterfaceCondition = function () {
-          return self.appConfig() && self.appConfig()['scheduler'] && self.appConfig()['scheduler']['interpreter_names'].indexOf('celery-beat') != -1;
-        };
-        var livyInterfaceCondition = function () {
-          return '${ is_mini }' == 'False' && self.appConfig() && self.appConfig()['editor'] && (self.appConfig()['editor']['interpreter_names'].indexOf('pyspark') != -1 || self.appConfig()['editor']['interpreter_names'].indexOf('sparksql') != -1);
-        };
-        var queryInterfaceCondition = function () {
-          return !window.getLastKnownConfig().has_computes && '${ ENABLE_QUERY_BROWSER.get() }' == 'True' && self.appConfig() && self.appConfig()['editor'] && self.appConfig()['editor']['interpreter_names'].indexOf('impala') != -1 && (!self.cluster() || self.cluster()['type'].indexOf('altus') == -1);
-        };
-        var queryHiveInterfaceCondition = function () {
-          return !window.getLastKnownConfig().has_computes && '${ ENABLE_HIVE_QUERY_BROWSER.get() }' == 'True';
-        };
-        var scheduleHiveInterfaceCondition = function () {
-          return '${ ENABLE_QUERY_SCHEDULING.get() }' == 'True';
-        };
-        var hiveQueriesInterfaceCondition = function () {
-          return '${ QUERY_STORE.IS_ENABLED.get() }' == 'True' && isDialectEnabled('hive');
-        };
-        var impalaQueriesInterfaceCondition = function () {
-          return '${ QUERY_STORE.IS_ENABLED.get() }' == 'True' && isDialectEnabled('impala');
-        };
-
-        var interfaces = [
-          {'interface': 'jobs', 'label': '${ _ko('Jobs') }', 'condition': jobsInterfaceCondition},
-          {'interface': 'dataeng-jobs', 'label': '${ _ko('Jobs') }', 'condition': dataEngInterfaceCondition},
-          {'interface': 'dataeng-clusters', 'label': '${ _ko('Clusters') }', 'condition': dataEngInterfaceCondition},
-          {'interface': 'dataware-clusters', 'label': '${ _ko('Clusters') }', 'condition': dataWarehouseInterfaceCondition},
-          {'interface': 'dataware2-clusters', 'label': '${ _ko('Warehouses') }', 'condition': dataWarehouse2InterfaceCondition},
-          {'interface': 'engines', 'label': '${ _ko('') }', 'condition': enginesInterfaceCondition},
-          {'interface': 'queries-impala', 'label': '${ _ko('Impala') }', 'condition': queryInterfaceCondition},
-          {'interface': 'queries-hive', 'label': '${ _ko('Hive') }', 'condition': queryHiveInterfaceCondition},
-          {'interface': 'schedule-hive', 'label': '${ _ko('Hive Schedules') }', 'condition': scheduleHiveInterfaceCondition},
-          {'interface': 'celery-beat', 'label': '${ _ko('Scheduled Tasks') }', 'condition': schedulerBeatInterfaceCondition},
-          {'interface': 'history', 'label': '${ _ko('History') }', 'condition': historyInterfaceCondition},
-          {'interface': 'workflows', 'label': '${ _ko('Workflows') }', 'condition': schedulerInterfaceCondition},
-          {'interface': 'schedules', 'label': '${ _ko('Schedules') }', 'condition': schedulerInterfaceCondition},
-          {'interface': 'bundles', 'label': '${ _ko('Bundles') }', 'condition': schedulerExtraInterfaceCondition},
-          {'interface': 'slas', 'label': '${ _ko('SLAs') }', 'condition': schedulerExtraInterfaceCondition},
-          {'interface': 'livy-sessions', 'label': '${ _ko('Livy') }', 'condition': livyInterfaceCondition},
-          {'interface': 'hive-queries', 'label': '${ _ko('Hive Queries') }', 'condition': hiveQueriesInterfaceCondition},
-          {'interface': 'impala-queries', 'label': '${ _ko('Impala Queries') }', 'condition': impalaQueriesInterfaceCondition},
-        ];
-
-        return interfaces.filter(function (i) {
-          return i.condition();
-        });
-      });
-
-      self.availableInterfaces.subscribe(function (newInterfaces) {
-        if (self.interface() && !newInterfaces.some(function (newInterface) {
-          return newInterface.interface === self.interface();
-        })) {
-          self.selectInterface(newInterfaces[0]);
-        }
-      });
-
-      self.slasLoadedOnce = false;
-      self.slasLoading = ko.observable(true);
-      self.loadSlaPage = function(){
-        if (!self.slasLoadedOnce) {
-          $.ajax({
-            url: '/oozie/list_oozie_sla/?is_embeddable=true',
-            beforeSend: function (xhr) {
-              xhr.setRequestHeader('X-Requested-With', 'Hue');
-            },
-            dataType: 'html',
-            success: function (response) {
-              self.slasLoading(false);
-              $('#slas').html(response);
-            }
-          });
-        }
-      };
-
-      self.oozieInfoLoadedOnce = false;
-      self.oozieInfoLoading = ko.observable(true);
-      self.loadOozieInfoPage = function(){
-        if (!self.oozieInfoLoadedOnce) {
-          self.oozieInfoLoadedOnce = true;
-          $.ajax({
-            url: '/oozie/list_oozie_info/?is_embeddable=true',
-            beforeSend: function (xhr) {
-              xhr.setRequestHeader('X-Requested-With', 'Hue');
-            },
-            dataType: 'html',
-            success: function (response) {
-              self.oozieInfoLoading(false);
-              $('#oozieInfo').html(response);
-            }
-          });
-        }
-      };
-
-      self.interface = ko.observable();
-      self.isValidInterface = function(name) {
-        var flatAvailableInterfaces = self.availableInterfaces().map(function (i) {
-          return i.interface;
-        });
-        if (flatAvailableInterfaces.indexOf(name) != -1 || name == 'oozie-info') {
-          return name;
-        } else {
-          return flatAvailableInterfaces[0];
-        }
-      };
-      self.selectInterface = function(interface) {
-        interface = self.isValidInterface(interface);
-        self.interface(interface);
-        self.resetBreadcrumbs();
-
-        % if not is_mini:
-          huePubSub.publish('graph.stop.refresh.view');
-          if (window.location.hash !== '#!' + interface) {
-            hueUtils.changeURL('#!' + interface);
-          }
-        % endif
-        self.jobs.selectedJobs([]);
-        self.job(null);
-
-        if (interface === 'slas'){
-          % if not is_mini:
-            self.loadSlaPage();
-          % endif
-        }
-        else if (interface === 'oozie-info') {
-          % if not is_mini:
-            self.loadOozieInfoPage();
-          % endif
-        }
-        else if(interface !== 'hive-queries' || interface !== 'impala-queries'){
-          self.jobs.fetchJobs();
-        }
-      };
-
-      self.onClusterSelect = function () {
-        var interfaceToSet = self.interface();
-        if (!self.availableInterfaces().some(function (availableInterface) {
-          return availableInterface.interface === interfaceToSet;
-        })) {
-          interfaceToSet = self.availableInterfaces()[0].interface;
-        }
-        self.selectInterface(interfaceToSet);
-      };
-
-      self.jobs = new Jobs(self);
-      self.job = ko.observable();
-
-      var updateJobTimeout = -1;
-      var updateJobsTimeout = -1;
-      var jobUpdateCounter = 0;
-      var exponentialFactor = 1;
-      self.job.subscribe(function(val) {
-        self.monitorJob(val);
-      });
-
-      self.monitorJob = function(job) {
-        window.clearTimeout(updateJobTimeout);
-        window.clearTimeout(updateJobsTimeout);
-        jobUpdateCounter = 0;
-        exponentialFactor = 1;
-        if (self.interface() && self.interface() !== 'slas' && self.interface() !== 'oozie-info' && self.interface !== 'hive-queries' && self.interface !== 'impala-queries'){
-          if (job) {
-            if (job.apiStatus() === 'RUNNING') {
-              var _updateJob = function () {
-                jobUpdateCounter++;
-                var updateLogs = (jobUpdateCounter % exponentialFactor) === 0;
-                if(updateLogs && exponentialFactor < 50) {
-                  exponentialFactor *= 2;
-                }
-                var def = job.updateJob(updateLogs);
-                if (def) {
-                  def.done(function () {
-                    updateJobTimeout = setTimeout(_updateJob, window.JB_SINGLE_CHECK_INTERVAL_IN_MILLIS);
-                  });
-                }
-              };
-              updateJobTimeout = setTimeout(_updateJob, window.JB_SINGLE_CHECK_INTERVAL_IN_MILLIS);
-            }
-          }
-          else {
-            var _updateJobs = function () {
-              self.jobs.updateJobs().done(function () {
-                setTimeout(_updateJobs, window.JB_MULTI_CHECK_INTERVAL_IN_MILLIS);
-              });
-            };
-            updateJobsTimeout = setTimeout(_updateJobs, window.JB_MULTI_CHECK_INTERVAL_IN_MILLIS);
-          }
-        }
-      };
-
-      self.breadcrumbs = ko.observableArray([]);
-      self.resetBreadcrumbs = function(extraCrumbs) {
-        var crumbs = [{'id': '', 'name': self.interface(), 'type': self.interface()}]
-        if (extraCrumbs) {
-          crumbs = crumbs.concat(extraCrumbs);
-        }
-        self.breadcrumbs(crumbs);
-      };
-
-      self.resetBreadcrumbs();
-
-      self.getHDFSPath = function (path) {
-        if (path.startsWith('hdfs://')) {
-          var bits = path.substr(7).split('/');
-          bits.shift();
-          return '/' + bits.join('/');
-        }
-        return path;
-      };
-
-      self.formatProgress = function (progress) {
-        if (typeof progress === 'function') {
-          progress = progress();
-        }
-        if (!isNaN(progress)) {
-          return Math.round(progress*100)/100 + '%';
-        }
-        return progress;
-      };
-
-      self.load = function() {
-        var h = window.location.hash;
-        % if not is_mini:
-        huePubSub.publish('graph.stop.refresh.view');
-        % endif
-
-        h = h.indexOf('#!') === 0 ? h.substr(2) : '';
-        switch (h) {
-          case '':
-            h = 'jobs';
-          case 'slas':
-          case 'oozie-info':
-          case 'jobs':
-          case 'queries-impala':
-          case 'queries-hive':
-          case 'celery-beat':
-          case 'schedule-hive':
-          case 'history':
-          case 'workflows':
-          case 'schedules':
-          case 'bundles':
-          case 'dataeng-clusters':
-          case 'dataware-clusters':
-          case 'dataware2-clusters':
-          case 'engines':
-          case 'dataeng-jobs':
-          case 'livy-sessions':
-          case 'hive-queries':
-          case 'impala-queries':
-            self.selectInterface(h);
-            break;
-          default:
-            if (h.indexOf('id=') === 0 && !self.isMini()){
-              new Job(self, {id: h.substr(3)}).fetchJob();
-            }
-            else {
-              self.selectInterface('reset');
-            }
-        }
-      };
-    };
-
-    $(document).ready(function () {
-      var jobBrowserViewModel = new JobBrowserViewModel();
-      var openJob = function(id) {
-        if (jobBrowserViewModel.job() == null) {
-          jobBrowserViewModel.job(new Job(jobBrowserViewModel, {}));
-        }
-        jobBrowserViewModel.job().id(id);
-        jobBrowserViewModel.job().fetchJob();
-      }
-      % if not is_mini:
-        ko.applyBindings(jobBrowserViewModel, $('#jobbrowserComponents')[0]);
-
-        huePubSub.subscribe('oozie.action.logs.click', function (widget) {
-          $.get(widget.logsURL(), {
-              format: 'link'
-            },
-            function(data) {
-              var id = data.job || data.attemptid;
-              if (id) {
-                openJob(id);
-              } else {
-                huePubSub.publish('hue.global.error', {message: '${ _("No log available") }'});
-              }
-            }
-          );
-        }, 'jobbrowser');
-
-        huePubSub.subscribe('oozie.action.click', function (widget) {
-          openJob(widget.externalId());
-        }, 'jobbrowser');
-
-        huePubSub.subscribe('browser.job.open.link', function (id) {
-          openJob(id);
-        }, 'jobbrowser');
-      % else:
-        ko.applyBindings(jobBrowserViewModel, $('#jobbrowserMiniComponents')[0]);
-        jobBrowserViewModel.isMini(true);
-      % endif
-
-      huePubSub.subscribe('app.gained.focus', function (app) {
-        if (app === 'jobbrowser') {
-          huePubSub.publish('graph.draw.arrows');
-          loadHash();
-        }
-      }, 'jobbrowser');
-
-      var loadHash = function () {
-        if (window.location.pathname.indexOf('jobbrowser') > -1) {
-          jobBrowserViewModel.load();
-        }
-      };
-
-      window.onhashchange = function () {
-        loadHash();
-      };
-
-      var configUpdated = function (clusterConfig) {
-        jobBrowserViewModel.appConfig(clusterConfig && clusterConfig['app_config']);
-        jobBrowserViewModel.clusterType(clusterConfig && clusterConfig['cluster_type']);
-        loadHash();
-      }
-
-      huePubSub.subscribe('cluster.config.set.config', configUpdated);
-      huePubSub.publish('cluster.config.get.config', configUpdated);
-
-
-      huePubSub.subscribe('submit.rerun.popup.return${ SUFFIX }', function (data) {
-        $.jHueNotify.info('${_("Rerun submitted.")}');
-        $('#rerun-modal${ SUFFIX }').modal('hide');
-
-        jobBrowserViewModel.job().apiStatus('RUNNING');
-        jobBrowserViewModel.monitorJob(jobBrowserViewModel.job());
-      }, '${ "jobbrowser" if not is_mini else "" }');
-
-      % if is_mini:
-        huePubSub.subscribe('mini.jb.navigate', function (options) {
-          if (options.compute) {
-            jobBrowserViewModel.compute(options.compute);
-          }
-          $('#jobsPanel .nav-pills li').removeClass('active');
-          interface = jobBrowserViewModel.isValidInterface(options.section);
-          $('#jobsPanel .nav-pills li[data-interface="' + interface + '"]').addClass('active');
-          jobBrowserViewModel.selectInterface(interface);
-        });
-        huePubSub.subscribe('mini.jb.open.job', openJob);
-        huePubSub.subscribe('mini.jb.expand', function () {
-          if (jobBrowserViewModel.job()) {
-            huePubSub.publish('open.link', '/jobbrowser/#!id=' + jobBrowserViewModel.job().id());
-          }
-          else {
-            huePubSub.publish('open.link', '/jobbrowser/#!' + jobBrowserViewModel.interface());
-          }
-        });
-      % endif
-
-      $(document).on('shown', '.jb-logs-link', function (e) {
-        var dest = $(e.target).attr('href');
-        if (dest.indexOf('logs') > -1 && $(dest).find('pre:visible').length > 0){
-          $(dest).find('pre').css('overflow-y', 'auto').height(Math.max(200, $(window).height() - $(dest).find('pre').offset().top - $('.page-content').scrollTop() - 75));
-        }
-      });
-      $(document).off("showSubmitPopup");
-      $(document).on("showSubmitPopup", function(event, data) {
-        $('#syncWorkflowModal').empty();
-        $('#syncWorkflowModal').html(data);
-        $('#syncWorkflowModal').modal('show');
-        $('#syncWorkflowModal').on('hidden', function () {
-          huePubSub.publish('hide.datepicker');
-        });
-
-        $('#syncWorkflowModal').find(".submit-form").on('submit',function(e){
-          e.preventDefault();
-          $.ajax({
-            type: "POST",
-            cache: false,
-            url: $(this).attr('action'),
-            data: $(this).serialize(),
-            success: function(data) {
-              $('#syncWorkflowModal').modal('hide');
-              if (data && data.status === 0) {
-                $.jHueNotify.info(data.message);
-              } else {
-                $.jHueNotify.error(data.message);
-              }
-            },
-            error: function (data) {
-              $('#syncWorkflowModal').modal('hide');
-              $.jHueNotify.error(data.message);
-            }
-          });
-        });
-
-      });
-    });
-  })();
-</script>
 </span>
 
 % if not is_embeddable:

+ 357 - 0
apps/jobbrowser/src/jobbrowser/templates/mini_job_browser.mako

@@ -0,0 +1,357 @@
+## Licensed to Cloudera, Inc. under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  Cloudera, Inc. 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.
+<%
+import sys
+
+from desktop.conf import CUSTOM, IS_K8S_ONLY
+from desktop.views import commonheader, commonfooter
+from desktop.webpack_utils import get_hue_bundles
+from jobbrowser.conf import MAX_JOB_FETCH, ENABLE_QUERY_BROWSER
+from webpack_loader.templatetags.webpack_loader import render_bundle
+
+if sys.version_info[0] > 2:
+  from django.utils.translation import gettext as _
+else:
+  from django.utils.translation import ugettext as _
+%>
+
+% if not is_embeddable:
+${ commonheader("Job Browser", "jobbrowser", user, request) | n,unicode }
+% endif
+
+<span class="notebook">
+  % if not is_embeddable:
+  <link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
+  <link rel="stylesheet" href="${ static('notebook/css/notebook-layout.css') }">
+  <style type="text/css">
+  % if CUSTOM.BANNER_TOP_HTML.get():
+    .show-assist {
+      top: 110px!important;
+    }
+    .main-content {
+      top: 112px!important;
+    }
+  % endif
+  </style>
+  % endif
+  
+  <link rel="stylesheet" href="${ static('jobbrowser/css/jobbrowser-embeddable.css') }">
+  <link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-datepicker.min.css') }">
+  <link rel="stylesheet" href="${ static('desktop/ext/css/bootstrap-timepicker.min.css') }">
+  <link rel="stylesheet" href="${ static('desktop/css/bootstrap-spinedit.css') }">
+  <link rel="stylesheet" href="${ static('desktop/css/bootstrap-slider.css') }">
+  
+  % for bundle in get_hue_bundles('miniJobBrowser'):
+    ${ render_bundle(bundle) | n,unicode }
+  % endfor
+  
+  <script src="${ static('desktop/ext/js/bootstrap-datepicker.min.js') }" type="text/javascript" charset="utf-8"></script>
+  <script src="${ static('desktop/ext/js/bootstrap-timepicker.min.js') }" type="text/javascript" charset="utf-8"></script>
+  <script src="${ static('desktop/js/bootstrap-spinedit.js') }" type="text/javascript" charset="utf-8"></script>
+  <script src="${ static('desktop/js/bootstrap-slider.js') }" type="text/javascript" charset="utf-8"></script>
+  <script src="${ static('oozie/js/dashboard-utils.js') }" type="text/javascript" charset="utf-8"></script>
+  
+  % if ENABLE_QUERY_BROWSER.get():
+  <script src="${ static('desktop/ext/js/dagre-d3-min.js') }"></script>
+  <script src="${ static('jobbrowser/js/impala_dagre.js') }"></script>
+  % endif
+  
+  <div id="jobbrowserMiniComponents" class="jobbrowser-components jobbrowser-mini jb-panel">
+  
+  % if not is_embeddable:
+    <a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">
+      <i class="fa fa-chevron-right"></i>
+    </a>
+  % endif
+  
+    <div class="mini-jb-context-selector">
+      <!-- ko component: {
+        name: 'hue-context-selector',
+        params: {
+          connector: { id: 'impala' },
+          compute: compute,
+          ##namespace: namespace,
+          ##availableDatabases: availableDatabases,
+          ##database: database,
+          hideDatabases: true
+        }
+      } --><!-- /ko -->
+  
+      % if not IS_K8S_ONLY.get():
+      <!-- ko component: {
+        name: 'hue-context-selector',
+        params: {
+          connector: { id: 'jobs' },
+          cluster: cluster,
+          onClusterSelect: onClusterSelect,
+          hideLabels: true
+        }
+      } --><!-- /ko -->
+      % endif
+    </div>
+    <ul class="nav nav-pills">
+      <!-- ko foreach: availableInterfaces -->
+        <li data-bind="css: {'active': $parent.interface() === interface}, visible: condition()">
+          <a class="pointer" data-bind="click: function(){ $parent.selectInterface(interface); }, text: label"></a>
+        </li>
+      <!-- /ko -->
+    </ul>
+
+    <div class="main-content">
+      <div class="vertical-full container-fluid" data-bind="style: { 'padding-left' : $root.isLeftPanelVisible() ? '0' : '20px' }">
+        <div class="vertical-full">
+          <div class="vertical-full row-fluid panel-container">
+            % if not is_embeddable:
+            <div class="assist-container left-panel" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable()">
+              <a title="${_('Toggle Assist')}" class="pointer hide-assist" data-bind="click: function() { $root.isLeftPanelVisible(false) }">
+                <i class="fa fa-chevron-left"></i>
+              </a>
+              <div class="assist" data-bind="component: {
+                  name: 'assist-panel',
+                  params: {
+                    user: '${user.username}',
+                    sql: {
+                      navigationSettings: {
+                        openItem: false,
+                        showStats: true
+                      }
+                    },
+                    visibleAssistPanels: ['sql']
+                  }
+                }"></div>
+            </div>
+            <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
+            % endif
+  
+            <div class="content-panel">
+              <div class="content-panel-inner">
+                <!-- ko if: $root.job() -->
+                <div data-bind="template: { name: 'jb-breadcrumbs' }"></div>
+                <!-- /ko -->
+  
+                <!-- ko if: interface() !== 'slas' && interface() !== 'oozie-info' && interface() !== 'hive-queries' && interface() !== 'impala-queries'-->
+                <!-- ko if: !$root.job() -->
+                <form class="form-inline">
+                  <!-- ko if: interface() != 'dataware2-clusters' && interface() != 'engines' -->
+                  <input type="text" class="input-large" data-bind="clearable: jobs.textFilter, valueUpdate: 'afterkeydown'" placeholder="${_('Filter by id, name, user...')}" />
+                    <!-- ko if: jobs.statesValuesFilter -->
+                    <span data-bind="foreach: jobs.statesValuesFilter">
+                      <label class="checkbox">
+                        <div class="pull-left margin-left-5 status-border status-content" data-bind="css: value, hueCheckbox: checked"></div>
+                        <div class="inline-block" data-bind="text: name, toggle: checked"></div>
+                      </label>
+                    </span>
+                    <!-- /ko -->
+                  <!-- /ko -->
+                </form>
+  
+                <div data-bind="visible: jobs.showJobCountBanner" class="pull-center alert alert-warning">
+                  ${ _("Showing oldest %s jobs. Use days filter to get the recent ones.") % MAX_JOB_FETCH.get() }
+                </div>
+  
+                <div class="card card-small">
+                  <!-- ko hueSpinner: { spin: jobs.loadingJobs(), center: true, size: 'xlarge' } --><!-- /ko -->
+                  <!-- ko ifnot: jobs.loadingJobs() -->
+                    <ul class="unstyled status-border-container" id="jobsTable" data-bind="foreach: jobs.apps">
+                      <li class="status-border pointer" data-bind="
+                        css: {
+                          'completed': apiStatus() == 'SUCCEEDED',
+                          'info': apiStatus() === 'PAUSED',
+                          'running': apiStatus() === 'RUNNING',
+                          'failed': apiStatus() == 'FAILED'
+                        },
+                        click: function (data, event) {
+                          onTableRowClick(event, id());
+                        }">
+                        <span class="muted pull-left" data-bind="momentFromNow: {data: submitted, interval: 10000, titleFormat: 'LLL'}"></span><span class="muted">&nbsp;-&nbsp;</span><span class="muted" data-bind="text: status"></span>
+                        <span class="muted pull-right" data-bind="text: duration() ? duration().toHHMMSS() : ''"></span>
+                        <div class="clearfix"></div>
+                        <strong class="pull-left" data-bind="text: type"></strong>
+                        <div class="inline-block pull-right"><i class="fa fa-user muted"></i> <span data-bind="text: user"></span></div>
+                        <div class="clearfix"></div>
+                        <div class="pull-left" data-bind="ellipsis: {data: name(), length: 40 }"></div>
+                        <div class="pull-right muted" data-bind="ellipsis: {data: id(), length: 32 }"></div>
+                        <div class="clearfix"></div>
+                      </li>
+                      <div class="status-bar status-background" data-bind="css: {'running': isRunning()}, style: {'width': progress() + '%'}"></div>
+                    </ul>
+                  <!-- /ko -->
+                </div>
+                <!-- /ko -->
+  
+                <!-- ko if: $root.job() -->
+                <!-- ko with: $root.job() -->
+                  <!-- ko if: mainType() == 'history' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-history-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'jobs' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-job-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'queries-impala' || mainType() == 'queries-hive' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-queries-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'celery-beat' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-celery-beat-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'schedule-hive' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-schedule-hive-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'workflows' -->
+                    <!-- ko if: type() == 'workflow' -->
+                      <div class="jb-panel" data-bind="template: { name: 'jb-workflow-page' }"></div>
+                    <!-- /ko -->
+  
+                    <!-- ko if: type() == 'workflow-action' -->
+                      <div class="jb-panel" data-bind="template: { name: 'jb-workflow-action-page' }"></div>
+                    <!-- /ko -->
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'schedules' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-schedule-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'bundles' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-bundle-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType().startsWith('dataeng-job') -->
+                    <div data-bind="template: { name: 'jb-dataeng-job-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'dataeng-clusters' || mainType() == 'dataware-clusters' -->
+                    <div data-bind="template: { name: 'jb-dataware-clusters-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'dataware2-clusters' -->
+                    <div data-bind="template: { name: 'jb-dataware2-clusters-page' }"></div>
+                  <!-- /ko -->
+  
+                  <!-- ko if: mainType() == 'livy-sessions' -->
+                    <div class="jb-panel" data-bind="template: { name: 'jb-livy-session-page' }"></div>
+                  <!-- /ko -->
+                <!-- /ko -->
+                <!-- /ko -->
+  
+                <!-- ko if: $root.job() && !$root.job().forceUpdatingJob() && $root.job().hasPagination() && interface() === 'schedules' -->
+                <div data-bind="template: { name: 'jb-pagination', data: $root.job() }, visible: !jobs.loadingJobs()"></div>
+                <!-- /ko -->
+                <div data-bind="template: { name: 'jb-pagination', data: $root.jobs }, visible: !$root.job() && !jobs.loadingJobs()"></div>
+                <!-- /ko -->
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  
+    <!-- ko if: $root.job() -->
+      <div id="rerun-modal-mini" class="modal hide" data-bind="htmlUnsecure: $root.job().rerunModalContent"></div>
+    <!-- /ko -->
+  
+    <!-- ko if: ($root.job() && $root.job().hasKill()) || (!$root.job() && $root.jobs.hasKill()) -->
+      <div id="killModal-mini" class="modal hide">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
+          <h2 class="modal-title">${_('Confirm Kill')}</h2>
+        </div>
+        <div class="modal-body">
+          <p>${_('Are you sure you want to kill the selected job(s)?')}</p>
+        </div>
+        <div class="modal-footer">
+          <a class="btn" data-dismiss="modal">${_('No')}</a>
+          <a id="killJobBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ if (job()) { job().control('kill'); } else { jobs.control('kill'); } }">${_('Yes')}</a>
+        </div>
+      </div>
+    <!-- /ko -->
+  
+    <!-- ko if: $root.job() && $root.job().type() === 'schedule' -->
+      <div id="syncCoordinatorModal-mini" class="modal hide">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="${ _('Close') }"><span aria-hidden="true">&times;</span></button>
+          <h2 class="modal-title confirmation_header">${ _('Update Coordinator Job Properties') }</h2>
+        </div>
+        <div id="update-coord" class="span10">
+          <div class="control-group">
+            <label class="control-label">${ _('End Time') }</label>
+            <div class="controls">
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-calendar"></i>
+                </span>
+                <input id="endTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeDateUI, datepicker: {}" />
+              </div>
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-clock-o"></i>
+                </span>
+                <input id="endTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorEndTimeTimeUI, timepicker: {}" />
+              </div>
+              <span class="help-inline"></span>
+            </div>
+          </div>
+          <div class="control-group">
+            <label class="control-label">${ _('Pause Time') }</label>
+            <div class="controls">
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-calendar"></i>
+                </span>
+                <input id="pauseTimeDateUI" type="text" class="input-small disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeDateUI, datepicker: {}" />
+              </div>
+              <div class="input-prepend input-group">
+                <span class="add-on input-group-addon">
+                  <i class="fa fa-clock-o"></i>
+                </span>
+                <input id="pauseTimeTimeUI" type="text" class="input-mini disable-autofocus" data-bind="value: $root.job().syncCoorPauseTimeTimeUI, timepicker: {}" />
+              </div>
+              <span class="help-inline"></span>
+            </div>
+          </div>
+          <div class="control-group ">
+            <label class="control-label">Clear Pause Time</label>
+            <div class="controls">
+              <input id="id_clearPauseTime" class="disable-autofocus" name="clearPauseTime" type="checkbox">
+            </div>
+          </div>
+          <div class="control-group ">
+            <label class="control-label">Concurrency</label>
+            <div class="controls">
+              <input id="id_concurrency" class="disable-autofocus" name="concurrency" type="number" data-bind="value: $root.job().syncCoorConcurrency">
+            </div>
+          </div>
+        </div>
+        <div class="modal-body">
+            <p class="confirmation_body"></p>
+        </div>
+        <div class="modal-footer update">
+          <a href="#" class="btn" data-dismiss="modal">Cancel</a>
+          <a id="syncCoorBtn" class="btn btn-danger disable-feedback" data-dismiss="modal" data-bind="click: function(){ job().control('sync_coordinator'); }">${_('Update')}</a>
+        </div>
+      </div>
+  
+      <div id="syncWorkflowModal-mini" class="modal hide"></div>
+    <!-- /ko -->
+  </div>
+</span>
+
+% if not is_embeddable:
+${ commonfooter(request, messages) | n,unicode }
+% endif

+ 8 - 1
apps/jobbrowser/src/jobbrowser/views.py

@@ -114,9 +114,16 @@ def get_job(request, job_id):
 
 
 def apps(request):
+  if request.GET.get('is_mini', False):
+    return render('mini_job_browser.mako', request, {
+      'is_embeddable': request.GET.get('is_embeddable', False),
+      'is_mini': True,
+      'hiveserver2_impersonation_enabled': hiveserver2_impersonation_enabled()
+    })
+
   return render('job_browser.mako', request, {
     'is_embeddable': request.GET.get('is_embeddable', False),
-    'is_mini': request.GET.get('is_mini', False),
+    'is_mini': False,
     'hiveserver2_impersonation_enabled': hiveserver2_impersonation_enabled()
   })
 

+ 97 - 1
desktop/core/src/desktop/js/apps/jobBrowser/app.js

@@ -14,15 +14,111 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import $ from 'jquery';
+import * as ko from 'knockout';
+
 import huePubSub from 'utils/huePubSub';
+
 import './components/hiveQueryPlan/webcomp';
 import './components/queriesList/webcomp';
 import './components/impalaQueries/webcomp';
+import './eventListeners';
+import JobBrowserViewModel from './knockout/JobBrowserViewModel';
+import Job from './knockout/Job';
+import I18n from 'utils/i18n';
 
 huePubSub.subscribe('app.dom.loaded', app => {
   if (app !== 'jobbrowser') {
     return;
   }
 
-  // TODO: Move js from job_browser.mako here.
+  $(document).ready(() => {
+    const jobBrowserViewModel = new JobBrowserViewModel(false);
+    const openJob = id => {
+      if (jobBrowserViewModel.job() == null) {
+        jobBrowserViewModel.job(new Job(jobBrowserViewModel, {}));
+      }
+      jobBrowserViewModel.job().id(id);
+      jobBrowserViewModel.job().fetchJob();
+    };
+
+    ko.applyBindings(jobBrowserViewModel, $('#jobbrowserComponents')[0]);
+
+    const loadHash = () => {
+      if (window.location.pathname.indexOf('jobbrowser') > -1) {
+        jobBrowserViewModel.load();
+      }
+    };
+
+    window.onhashchange = () => loadHash();
+
+    huePubSub.subscribe(
+      'oozie.action.logs.click',
+      widget => {
+        $.get(
+          widget.logsURL(),
+          {
+            format: 'link'
+          },
+          data => {
+            const id = data.job || data.attemptid;
+            if (id) {
+              openJob(id);
+            } else {
+              huePubSub.publish('hue.global.error', { message: I18n('No log available') });
+            }
+          }
+        );
+      },
+      'jobbrowser'
+    );
+
+    huePubSub.subscribe(
+      'oozie.action.click',
+      widget => {
+        openJob(widget.externalId());
+      },
+      'jobbrowser'
+    );
+
+    huePubSub.subscribe(
+      'browser.job.open.link',
+      id => {
+        openJob(id);
+      },
+      'jobbrowser'
+    );
+
+    huePubSub.subscribe(
+      'app.gained.focus',
+      app => {
+        if (app === 'jobbrowser') {
+          huePubSub.publish('graph.draw.arrows');
+          loadHash();
+        }
+      },
+      'jobbrowser'
+    );
+
+    const configUpdated = clusterConfig => {
+      jobBrowserViewModel.appConfig(clusterConfig && clusterConfig['app_config']);
+      jobBrowserViewModel.clusterType(clusterConfig && clusterConfig['cluster_type']);
+      loadHash();
+    };
+
+    huePubSub.subscribe('cluster.config.set.config', configUpdated);
+    huePubSub.publish('cluster.config.get.config', configUpdated);
+
+    huePubSub.subscribe(
+      'submit.rerun.popup.return',
+      data => {
+        huePubSub.publish('hue.global.info', { message: I18n('Rerun submitted.') });
+        $('#rerun-modal').modal('hide');
+
+        jobBrowserViewModel.job().apiStatus('RUNNING');
+        jobBrowserViewModel.monitorJob(jobBrowserViewModel.job());
+      },
+      'jobbrowser'
+    );
+  });
 });

+ 70 - 0
desktop/core/src/desktop/js/apps/jobBrowser/eventListeners.js

@@ -0,0 +1,70 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+import huePubSub from '../../utils/huePubSub';
+
+$(document).off('shown', '.jb-logs-link');
+$(document).on('shown', '.jb-logs-link', e => {
+  const dest = $(e.target).attr('href');
+  if (dest.indexOf('logs') > -1 && $(dest).find('pre:visible').length > 0) {
+    $(dest)
+      .find('pre')
+      .css('overflow-y', 'auto')
+      .height(
+        Math.max(
+          200,
+          $(window).height() -
+            $(dest).find('pre').offset().top -
+            $('.page-content').scrollTop() -
+            75
+        )
+      );
+  }
+});
+
+$(document).off('showSubmitPopup');
+$(document).on('showSubmitPopup', (event, data) => {
+  const syncWorkflowModal = $('#syncWorkflowModal');
+  syncWorkflowModal.empty();
+  syncWorkflowModal.html(data);
+  syncWorkflowModal.modal('show');
+  syncWorkflowModal.on('hidden', () => {
+    huePubSub.publish('hide.datepicker');
+  });
+
+  syncWorkflowModal.find('.submit-form').on('submit', function (e) {
+    e.preventDefault();
+    $.ajax({
+      type: 'POST',
+      cache: false,
+      url: $(this).attr('action'),
+      data: $(this).serialize(),
+      success: function (data) {
+        $('#syncWorkflowModal').modal('hide');
+        if (data && data.status === 0) {
+          huePubSub.publish('hue.global.info', data);
+        } else {
+          huePubSub.publish('hue.global.error', data);
+        }
+      },
+      error: function (data) {
+        $('#syncWorkflowModal').modal('hide');
+        huePubSub.publish('hue.global.error', data);
+      }
+    });
+  });
+});

+ 951 - 0
desktop/core/src/desktop/js/apps/jobBrowser/knockout/Job.js

@@ -0,0 +1,951 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+import * as ko from 'knockout';
+import komapping from 'knockout.mapping';
+
+import apiHelper from 'api/apiHelper';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import deleteAllEmptyStringKeys from 'utils/string/deleteAllEmptyStringKeys';
+import changeURL from 'utils/url/changeURL';
+import Jobs from './Jobs';
+
+export default class Job {
+  constructor(vm, job) {
+    this.vm = vm;
+    this.suffix = this.vm.isMini() ? '-mini' : '';
+
+    this.paginationPage = ko.observable(1);
+    this.paginationOffset = ko.observable(1); // Starting index
+    this.paginationResultPage = ko.observable(50);
+    this.totalApps = ko.observable((job.properties && job.properties.total_actions) || 0);
+    this.hasPagination = ko.computed(
+      () =>
+        this.totalApps() &&
+        ['workflows', 'schedules', 'bundles'].indexOf(this.vm.interface()) !== -1
+    );
+    this.pagination = ko.pureComputed(() => ({
+      page: this.paginationPage(),
+      offset: this.paginationOffset(),
+      limit: this.paginationResultPage()
+    }));
+
+    this.pagination.subscribe(() => {
+      if (this.vm.interface() === 'schedules') {
+        this.updateJob(false, true);
+      }
+    });
+
+    this.showPreviousPage = ko.computed(() => this.paginationOffset() > 1);
+    this.showNextPage = ko.computed(
+      () =>
+        this.totalApps() != null &&
+        this.paginationOffset() + this.paginationResultPage() < this.totalApps()
+    );
+
+    this.id = ko.observable(job.id || null);
+    if (!this.vm.isMini()) {
+      this.id.subscribe(() => {
+        huePubSub.publish('graph.stop.refresh.view');
+      });
+    }
+    this.doc_url = ko.observable(job.doc_url || null);
+    this.doc_url_modified = ko.computed(() => {
+      const url = this.doc_url();
+      if (window.KNOX_BASE_URL.length && window.URL && url) {
+        // KNOX
+        try {
+          const parsedDocUrl = new URL(url);
+          const parsedKnoxUrl = new URL(window.KNOX_BASE_URL);
+          parsedDocUrl.hostname = parsedKnoxUrl.hostname;
+          parsedDocUrl.protocol = parsedKnoxUrl.protocol;
+          parsedDocUrl.port = parsedKnoxUrl.port;
+          const service = url.indexOf('livy') >= 0 ? '/livy' : '/impalaui';
+          parsedDocUrl.pathname = parsedKnoxUrl.pathname + service + parsedDocUrl.pathname;
+          return parsedDocUrl.toString();
+        } catch (e) {
+          return url;
+        }
+      } else if (window.KNOX_BASE_PATH.length && window.URL) {
+        // DWX
+        const parsedKnoxUrl = new URL(window.KNOX_BASE_URL);
+        const parsedDocUrl = new URL(url);
+        const service = url.indexOf('livy') >= 0 ? '/livy' : '/impalaui';
+        parsedDocUrl.pathname = parsedKnoxUrl.pathname + service + window.KNOX_BASE_PATH;
+      } else {
+        return url;
+      }
+    });
+    this.name = ko.observable(job.name || job.id || null);
+    this.type = ko.observable(job.type || null);
+    this.applicationType = ko.observable(job.applicationType || '');
+
+    this.status = ko.observable(job.status || null);
+    this.apiStatus = ko.observable(job.apiStatus || null);
+    this.progress = ko.observable(job.progress || null);
+    this.isRunning = ko.computed(
+      () => ['RUNNING', 'PAUSED'].indexOf(this.apiStatus()) !== -1 || job.isRunning
+    );
+
+    this.isRunning.subscribe(() => {
+      // The JB page for jobs is split in two tables, "Running" and "Completed", this esentially unchecks any job
+      // that moves from one table to the other.
+      ko.utils.arrayRemoveItem(this.vm.jobs.selectedJobs(), this);
+    });
+
+    this.user = ko.observable(job.user || null);
+    this.queue = ko.observable(job.queue || null);
+    this.cluster = ko.observable(job.cluster || null);
+    this.duration = ko.observable(job.duration || null);
+    this.submitted = ko.observable(job.submitted || null);
+    this.canWrite = ko.observable(!!job.canWrite || null);
+
+    this.logActive = ko.observable('default');
+    this.logsByName = ko.observable({});
+    this.logsListDefaults = ko.observable(['default', 'stdout', 'stderr', 'syslog']);
+    this.logsList = ko.observable(this.logsListDefaults());
+    this.logs = ko.pureComputed(() => this.logsByName()[this.logActive()]);
+
+    this.properties = komapping.fromJS(
+      (job.properties &&
+        Object.keys(job.properties).reduce((p, key) => {
+          p[key] = '';
+          return p;
+        }, {})) || { properties: '' }
+    );
+    Object.keys(job.properties || []).reduce((p, key) => {
+      p[key](job.properties[key]);
+      return p;
+    }, this.properties);
+    this.mainType = ko.observable(this.vm.interface());
+    this.lastEvent = ko.observable(job.lastEvent || '');
+
+    this.syncCoorEndTimeDateUI = ko.observable(null);
+    this.syncCoorEndTimeTimeUI = ko.observable(null);
+    this.syncCoorPauseTimeDateUI = ko.observable(null);
+    this.syncCoorPauseTimeTimeUI = ko.observable(null);
+    this.syncCoorConcurrency = ko.observable(null);
+
+    this.coordinatorActions = ko.pureComputed(() => {
+      if (this.mainType().indexOf('schedule') !== -1 && this.properties['tasks']) {
+        const apps = this.properties['tasks']().map(instance => {
+          const job = new CoordinatorAction(this.vm, komapping.toJS(instance), this);
+          job.properties = komapping.fromJS(instance);
+          return job;
+        });
+        const instances = new Jobs(this.vm);
+        instances.apps(apps);
+        instances.isCoordinator(true);
+        return instances;
+      }
+    });
+
+    this.textFilter = ko
+      .observable('')
+      .extend({ rateLimit: { method: 'notifyWhenChangesStop', timeout: 1000 } });
+    this.statesValuesFilter = ko.observableArray([
+      komapping.fromJS({
+        value: 'completed',
+        name: I18n('Succeeded'),
+        checked: false,
+        klass: 'green'
+      }),
+      komapping.fromJS({
+        value: 'running',
+        name: I18n('Running'),
+        checked: false,
+        klass: 'orange'
+      }),
+      komapping.fromJS({ value: 'failed', name: I18n('Failed'), checked: false, klass: 'red' })
+    ]);
+    this.statesFilter = ko.computed(() => {
+      const checkedStates = ko.utils.arrayFilter(this.statesValuesFilter(), state => {
+        return state.checked();
+      });
+      return ko.utils.arrayMap(checkedStates, state => {
+        return state.value();
+      });
+    });
+    this.typesValuesFilter = ko.observableArray([
+      komapping.fromJS({ value: 'map', name: I18n('Map'), checked: false, klass: 'green' }),
+      komapping.fromJS({ value: 'reduce', name: I18n('Reduce'), checked: false, klass: 'orange' })
+    ]);
+    this.typesFilter = ko.computed(() => {
+      const checkedTypes = ko.utils.arrayFilter(this.typesValuesFilter(), type => {
+        return type.checked();
+      });
+      return ko.utils.arrayMap(checkedTypes, type => {
+        return type.value();
+      });
+    });
+    this.filters = ko.pureComputed(() => [
+      { text: this.textFilter() },
+      { states: komapping.toJS(this.statesFilter()) },
+      { types: komapping.toJS(this.typesFilter()) }
+    ]);
+    this.forceUpdatingJob = ko.observable(false);
+    this.filters.subscribe(() => {
+      if (this.type() === 'schedule') {
+        this.updateJob(false, true);
+      } else {
+        this.fetchProfile('tasks');
+      }
+    });
+    this.metadataFilter = ko.observable('');
+    this.metadataFilter.subscribe(newValue => {
+      const tableRow = $('#jobbrowserJobMetadataTable tbody tr');
+      tableRow.removeClass('hide');
+      tableRow.each(function () {
+        if ($(this).text().toLowerCase().indexOf(newValue.toLowerCase()) === -1) {
+          $(this).addClass('hide');
+        }
+      });
+    });
+    this.propertiesFilter = ko.observable('');
+    this.propertiesFilter.subscribe(newValue => {
+      const tableRow = $('#jobbrowserJobPropertiesTable tbody tr');
+      tableRow.removeClass('hide');
+      tableRow.each(function () {
+        if ($(this).text().toLowerCase().indexOf(newValue.toLowerCase()) === -1) {
+          $(this).addClass('hide');
+        }
+      });
+    });
+
+    this.rerunModalContent = ko.observable('');
+
+    this.hasKill = ko.pureComputed(
+      () =>
+        this.type() &&
+        ([
+          'MAPREDUCE',
+          'SPARK',
+          'workflow',
+          'schedule',
+          'bundle',
+          'QUERY',
+          'TEZ',
+          'YarnV2',
+          'DDL',
+          'schedule-hive',
+          'celery-beat',
+          'history'
+        ].indexOf(this.type()) !== -1 ||
+          this.type().indexOf('Data Warehouse') !== -1 ||
+          this.type().indexOf('Altus') !== -1)
+    );
+    this.killEnabled = ko.pureComputed(() => {
+      // Impala can kill queries that are finished, but not yet terminated
+      return this.hasKill() && this.canWrite() && this.isRunning();
+    });
+
+    this.hasResume = ko.pureComputed(
+      () =>
+        ['workflow', 'schedule', 'bundle', 'schedule-hive', 'celery-beat', 'history'].indexOf(
+          this.type()
+        ) !== -1
+    );
+    this.resumeEnabled = ko.pureComputed(
+      () => this.hasResume() && this.canWrite() && this.apiStatus() === 'PAUSED'
+    );
+
+    this.hasRerun = ko.pureComputed(
+      () => ['workflow', 'schedule-task'].indexOf(this.type()) !== -1
+    );
+
+    this.rerunEnabled = ko.pureComputed(
+      () => this.hasRerun() && this.canWrite() && !this.isRunning()
+    );
+
+    this.hasPause = ko.pureComputed(
+      () =>
+        ['workflow', 'schedule', 'bundle', 'celery-beat', 'schedule-hive', 'history'].indexOf(
+          this.type()
+        ) !== -1
+    );
+
+    this.pauseEnabled = ko.pureComputed(
+      () => this.hasPause() && this.canWrite() && this.apiStatus() === 'RUNNING'
+    );
+
+    this.hasIgnore = ko.pureComputed(() => ['schedule-task'].indexOf(this.type()) !== -1);
+
+    this.ignoreEnabled = ko.pureComputed(
+      () => this.hasIgnore() && this.canWrite() && !this.isRunning()
+    );
+
+    this.loadingJob = ko.observable(false);
+    this.lastFetchJobRequest = null;
+    this.lastUpdateJobRequest = null;
+    this.lastFetchLogsRequest = null;
+    this.lastFetchProfileRequest = null;
+    this.lastFetchStatusRequest = null;
+
+    this.updateClusterWorkers = ko.observable(1);
+    this.updateClusterAutoResize = ko.observable(false);
+    this.updateClusterAutoResizeMin = ko.observable(1);
+    this.updateClusterAutoResizeMax = ko.observable(3);
+    this.updateClusterAutoResizeCpu = ko.observable(80);
+    this.updateClusterAutoPause = ko.observable();
+
+    this.clusterConfigModified = ko.pureComputed(
+      () =>
+        (this.updateClusterWorkers() > 0 &&
+          this.updateClusterWorkers() !== this.properties['properties']['workerReplicas']()) ||
+        this.updateClusterAutoResize() !== this.properties['properties']['workerAutoResize']()
+    );
+
+    this.workflowGraphLoaded = false;
+
+    this.lastArrowsPosition = {
+      top: 0,
+      left: 0
+    };
+    this.initialArrowsDrawingCount = 0;
+  }
+
+  previousPage() {
+    this.paginationOffset(this.paginationOffset() - this.paginationResultPage());
+  }
+
+  nextPage() {
+    this.paginationOffset(this.paginationOffset() + this.paginationResultPage());
+  }
+
+  showSyncCoorModal() {
+    this.syncCoorEndTimeDateUI(this.properties['endTimeDateUI']());
+    this.syncCoorEndTimeTimeUI(this.properties['endTimeTimeUI']());
+    this.syncCoorPauseTimeDateUI(this.properties['pauseTimeDateUI']());
+    this.syncCoorPauseTimeTimeUI(this.properties['pauseTimeTimeUI']());
+    this.syncCoorConcurrency(this.properties['concurrency']());
+
+    $(`#syncCoordinatorModal${this.suffix}`).modal('show');
+  }
+
+  _fetchJob(callback) {
+    if (this.vm.interface() === 'engines') {
+      huePubSub.publish('context.selector.set.cluster', 'AltusV2');
+      return;
+    }
+
+    return $.post(
+      '/jobbrowser/api/job/' + this.vm.interface(),
+      {
+        cluster: komapping.toJSON(this.vm.compute),
+        app_id: komapping.toJSON(this.id),
+        interface: komapping.toJSON(this.vm.interface),
+        pagination: komapping.toJSON(this.pagination),
+        filters: komapping.toJSON(this.filters)
+      },
+      data => {
+        if (data.status === 0) {
+          if (data.app) {
+            huePubSub.publish('jobbrowser.data', [data.app]);
+          }
+          if (callback) {
+            callback(data);
+          }
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    );
+  }
+
+  _rewriteKnoxUrls(data) {
+    if (data?.app?.type === 'SPARK' && data?.app?.properties?.metadata) {
+      data.app.properties.metadata.forEach(item => {
+        if (item.name === 'trackingUrl') {
+          /*
+            Rewrite tracking url
+            Sample trackingUrl: http://<yarn>:8088/proxy/application_1652826179847_0003/
+            Knox URL: https://<knox-base>/yarnuiv2/redirect#/yarn-app/application_1652826179847_0003/attempts
+          */
+          const matches = item.value.match('(application_[0-9_]+)');
+          if (matches && matches.length > 1) {
+            const applicationId = matches[1];
+            item.value =
+              window.KNOX_BASE_URL + '/yarnuiv2/redirect#/yarn-app/' + applicationId + '/attempts';
+          }
+        }
+      });
+    }
+  }
+
+  onTableRowClick(event, id) {
+    const openInNewTab = event && (event.which === 2 || event.metaKey || event.ctrlKey);
+    const idToOpen = id || this.id();
+    if (idToOpen && idToOpen !== '-') {
+      if (openInNewTab) {
+        const urlParts = window.location.toString().split('#');
+        const newUrl = urlParts[0] + '#!id=' + idToOpen;
+        window.open(newUrl, '_blank');
+      } else {
+        this.id(idToOpen);
+        this.fetchJob();
+      }
+    }
+  }
+
+  fetchJob() {
+    // TODO: Remove cancelActiveRequest from apiHelper when in webpack
+    apiHelper.cancelActiveRequest(this.lastFetchJobRequest);
+    apiHelper.cancelActiveRequest(this.lastUpdateJobRequest);
+
+    this.loadingJob(true);
+
+    let jobInterface = this.vm.interface();
+    if (/application_/.test(this.id()) || /job_/.test(this.id()) || /attempt_/.test(this.id())) {
+      jobInterface = 'jobs';
+    }
+    if (/oozie-\w+-W/.test(this.id())) {
+      jobInterface = 'workflows';
+    } else if (/oozie-\w+-C/.test(this.id())) {
+      jobInterface = 'schedules';
+    } else if (/oozie-\w+-B/.test(this.id())) {
+      jobInterface = 'bundles';
+    } else if (/celery-beat-\w+/.test(this.id())) {
+      jobInterface = 'celery-beat';
+    } else if (/schedule-hive-\w+/.test(this.id())) {
+      jobInterface = 'schedule-hive';
+    } else if (/altus:dataeng/.test(this.id()) && /:job:/.test(this.id())) {
+      jobInterface = 'dataeng-jobs';
+    } else if (/altus:dataeng/.test(this.id()) && /:cluster:/.test(this.id())) {
+      jobInterface = 'dataeng-clusters';
+    } else if (/altus:dataware:k8/.test(this.id()) && /:cluster:/.test(this.id())) {
+      jobInterface = 'dataware2-clusters';
+    } else if (/altus:dataware/.test(this.id()) && /:cluster:/.test(this.id())) {
+      jobInterface = 'dataware-clusters';
+    } else if (/[a-z0-9]{16}:[a-z0-9]{16}/.test(this.id())) {
+      jobInterface = 'queries-impala';
+    } else if (/hive_[a-z0-9]*_[a-z0-9]*/.test(this.id())) {
+      jobInterface = 'queries-hive';
+    } else if (/livy-[0-9]+/.test(this.id())) {
+      jobInterface = 'livy-sessions';
+    }
+
+    jobInterface =
+      jobInterface.indexOf('dataeng') || jobInterface.indexOf('dataware')
+        ? jobInterface
+        : this.vm.isValidInterface(jobInterface); // TODO: support multi cluster selection in isValidInterface
+    this.vm.interface(jobInterface);
+
+    this.lastFetchJobRequest = this._fetchJob(data => {
+      if (data.status === 0) {
+        if (window.KNOX_BASE_URL && window.KNOX_BASE_URL.length) {
+          this._rewriteKnoxUrls(data);
+        }
+
+        this.vm.interface(jobInterface);
+        this.vm.job(new Job(this.vm, data.app));
+        if (window.location.hash !== '#!id=' + this.vm.job().id()) {
+          changeURL('#!id=' + this.vm.job().id());
+        }
+        const crumbs = [];
+        if (/^appattempt_/.test(this.vm.job().id())) {
+          crumbs.push({
+            id: this.vm.job().properties['app_id'],
+            name: this.vm.job().properties['app_id'],
+            type: 'app'
+          });
+        }
+        if (/^attempt_/.test(this.vm.job().id())) {
+          crumbs.push({
+            id: this.vm.job().properties['app_id'],
+            name: this.vm.job().properties['app_id'],
+            type: 'app'
+          });
+          crumbs.push({
+            id: this.vm.job().properties['task_id'],
+            name: this.vm.job().properties['task_id'],
+            type: 'task'
+          });
+        }
+        if (/^task_/.test(this.vm.job().id())) {
+          crumbs.push({
+            id: this.vm.job().properties['app_id'],
+            name: this.vm.job().properties['app_id'],
+            type: 'app'
+          });
+        }
+        if (/_executor_/.test(this.vm.job().id())) {
+          crumbs.push({
+            id: this.vm.job().properties['app_id'],
+            name: this.vm.job().properties['app_id'],
+            type: 'app'
+          });
+        }
+        const oozieWorkflow = this.vm
+          .job()
+          .name()
+          .match(/oozie:launcher:T=.+?:W=.+?:A=.+?:ID=(.+?-oozie-\w+-W)$/i);
+        if (oozieWorkflow) {
+          crumbs.push({ id: oozieWorkflow[1], name: oozieWorkflow[1], type: 'workflow' });
+        }
+
+        if (/-oozie-\w+-W@/.test(this.vm.job().id())) {
+          crumbs.push({
+            id: this.vm.job().properties['workflow_id'],
+            name: this.vm.job().properties['workflow_id'],
+            type: 'workflow'
+          });
+        } else if (/-oozie-\w+-W/.test(this.vm.job().id())) {
+          if (this.vm.job().properties['bundle_id']()) {
+            crumbs.push({
+              id: this.vm.job().properties['bundle_id'](),
+              name: this.vm.job().properties['bundle_id'](),
+              type: 'bundle'
+            });
+          }
+          if (this.vm.job().properties['coordinator_id']()) {
+            crumbs.push({
+              id: this.vm.job().properties['coordinator_id'](),
+              name: this.vm.job().properties['coordinator_id'](),
+              type: 'schedule'
+            });
+          }
+        } else if (/-oozie-\w+-C/.test(this.vm.job().id())) {
+          if (this.vm.job().properties['bundle_id']()) {
+            crumbs.push({
+              id: this.vm.job().properties['bundle_id'](),
+              name: this.vm.job().properties['bundle_id'](),
+              type: 'bundle'
+            });
+          }
+        }
+
+        if (this.vm.job().type() === 'SPARK_EXECUTOR') {
+          crumbs.push({
+            id: this.vm.job().id(),
+            name: this.vm.job().properties['executor_id'](),
+            type: this.vm.job().type()
+          });
+        } else {
+          crumbs.push({
+            id: this.vm.job().id(),
+            name: this.vm.job().name(),
+            type: this.vm.job().type()
+          });
+        }
+
+        this.vm.resetBreadcrumbs(crumbs);
+        // Show is still bound to old job, setTimeout allows knockout model change event done at begining of this method to sends it's notification
+        setTimeout(() => {
+          if (
+            this.vm.job().mainType() === 'queries-impala' &&
+            !$(`#queries-page-plan${this.suffix}`).parent().children().hasClass('active')
+          ) {
+            $(`a[href="#queries-page-plan${this.suffix}"]`).tab('show');
+          }
+        }, 0);
+        if (
+          !this.vm.isMini() &&
+          this.vm.job().type() === 'workflow' &&
+          !this.vm.job().workflowGraphLoaded
+        ) {
+          this.vm.job().updateWorkflowGraph();
+        }
+        if (this.vm.isMini()) {
+          if (this.vm.job().type() === 'workflow') {
+            this.vm.job().fetchProfile('properties');
+            $(`a[href="#workflow-page-metadata${this.suffix}"]`).tab('show');
+          }
+          $(`#rerun-modal${this.suffix}`).on('shown', () => {
+            // Replaces dark modal backdrop from the end of the body tag to the closer scope
+            // in order to activate z-index effect.
+            const rerunModalData = $(this).data('modal');
+            rerunModalData.$backdrop.appendTo('#jobbrowserMiniComponents');
+          });
+          $(`#killModal${this.suffix}`).on('shown', () => {
+            const killModalData = $(this).data('modal');
+            killModalData.$backdrop.appendTo('#jobbrowserMiniComponents');
+          });
+        }
+
+        this.vm.job().fetchLogs();
+      } else {
+        huePubSub.publish('hue.global.error', { message: data.message });
+      }
+    }).always(() => {
+      this.loadingJob(false);
+    });
+  }
+
+  updateJob(updateLogs, forceUpdate) {
+    huePubSub.publish('graph.refresh.view');
+    const deferred = $.Deferred();
+    if (this.vm.job() === this && (this.apiStatus() === 'RUNNING' || forceUpdate)) {
+      apiHelper.cancelActiveRequest(this.lastUpdateJobRequest);
+      if (forceUpdate) {
+        this.forceUpdatingJob(true);
+      }
+      this.lastUpdateJobRequest = this._fetchJob(data => {
+        const requests = [];
+        if (['schedule', 'workflow'].indexOf(this.vm.job().type()) >= 0) {
+          deleteAllEmptyStringKeys(data.app); // It's preferable for our backend to return empty strings for various values in order to initialize them, but they shouldn't overwrite any values that are currently set.
+          let selectedIDs = [];
+          if (this.vm.job().coordinatorActions()) {
+            selectedIDs = this.vm
+              .job()
+              .coordinatorActions()
+              .selectedJobs()
+              .map(coordinatorAction => {
+                return coordinatorAction.id();
+              });
+          }
+          this.vm.job = komapping.fromJS(data.app, {}, this.vm.job);
+          if (selectedIDs.length > 0) {
+            this.vm
+              .job()
+              .coordinatorActions()
+              .selectedJobs(
+                this.vm
+                  .job()
+                  .coordinatorActions()
+                  .apps()
+                  .filter(coordinatorAction => selectedIDs.indexOf(coordinatorAction.id()) !== -1)
+              );
+          }
+          if (this.vm.job().type() === 'schedule') {
+            this.totalApps(data.app.properties.total_actions);
+          }
+        } else {
+          requests.push(this.vm.job().fetchStatus());
+        }
+        if (updateLogs !== false) {
+          requests.push(this.vm.job().fetchLogs(this.vm.job().logActive()));
+        }
+        const profile = $('div[data-jobType] .tab-content .active').data('profile');
+        if (profile) {
+          requests.push(this.vm.job().fetchProfile(profile));
+        }
+        $.when.apply(this, requests).done(() => {
+          deferred.resolve();
+        });
+      }).always(() => {
+        this.forceUpdatingJob(false);
+      });
+    }
+    return deferred;
+  }
+
+  fetchLogs(name) {
+    name = name || 'default';
+    apiHelper.cancelActiveRequest(this.lastFetchLogsRequest);
+    this.lastFetchLogsRequest = $.post(
+      '/jobbrowser/api/job/logs?is_embeddable=false',
+      {
+        cluster: komapping.toJSON(this.vm.compute),
+        app_id: komapping.toJSON(this.id),
+        interface: komapping.toJSON(this.vm.interface),
+        type: komapping.toJSON(this.type),
+        name: komapping.toJSON(name)
+      },
+      data => {
+        if (data.status === 0) {
+          const result = this.logsByName();
+          result[name] = data.logs.logs;
+          this.logsByName(result);
+          if (data.logs.logsList && data.logs.logsList.length) {
+            const logsListDefaults = this.logsListDefaults();
+            this.logsList(
+              logsListDefaults.concat(
+                data.logs.logsList.filter(log => {
+                  return logsListDefaults.indexOf(log) < 0;
+                })
+              )
+            );
+          }
+          const visibleJbPanel = $('.jb-panel pre:visible');
+          if (visibleJbPanel.length > 0) {
+            visibleJbPanel
+              .css('overflow-y', 'auto')
+              .height(
+                Math.max(
+                  200,
+                  $(window).height() -
+                    visibleJbPanel.offset().top -
+                    $('.page-content').scrollTop() -
+                    75
+                )
+              );
+          }
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    );
+    return this.lastFetchLogsRequest;
+  }
+
+  submitQueryProfileDownloadForm(name) {
+    const $downloadForm = $(
+      '<form method="POST" class="download-form" style="display: inline" action="' +
+        window.HUE_BASE_URL +
+        '/jobbrowser/api/job/profile"></form>'
+    );
+
+    $('<input type="hidden" name="csrfmiddlewaretoken" />')
+      .val(window.CSRF_TOKEN)
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="cluster" />')
+      .val(komapping.toJSON(this.vm.cluster))
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="app_id" />')
+      .val(komapping.toJSON(this.id))
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="interface" />')
+      .val(komapping.toJSON(this.vm.interface))
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="app_type" />')
+      .val(komapping.toJSON(this.type))
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="app_property" />')
+      .val(komapping.toJSON(name))
+      .appendTo($downloadForm);
+    $('<input type="hidden" name="app_filters" />')
+      .val(komapping.toJSON(this.filters))
+      .appendTo($downloadForm);
+
+    $('#downloadProgressModal').append($downloadForm);
+
+    huePubSub.publish('ignore.next.unload');
+    $downloadForm.submit();
+  }
+
+  fetchProfile(name, callback) {
+    apiHelper.cancelActiveRequest(this.lastFetchProfileRequest);
+    this.lastFetchProfileRequest = $.post(
+      '/jobbrowser/api/job/profile',
+      {
+        cluster: komapping.toJSON(this.vm.compute),
+        app_id: komapping.toJSON(this.id),
+        interface: komapping.toJSON(this.vm.interface),
+        app_type: komapping.toJSON(this.type),
+        app_property: komapping.toJSON(name),
+        app_filters: komapping.toJSON(this.filters)
+      },
+      data => {
+        if (data.status === 0) {
+          this.properties[name](data[name]);
+          if (callback) {
+            callback(data);
+          }
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    );
+    return this.lastFetchProfileRequest;
+  }
+
+  fetchStatus() {
+    apiHelper.cancelActiveRequest(this.lastFetchStatusRequest);
+    this.lastFetchStatusRequest = $.post(
+      '/jobbrowser/api/job',
+      {
+        cluster: komapping.toJSON(this.vm.compute),
+        app_id: komapping.toJSON(this.id),
+        interface: komapping.toJSON(this.mainType)
+      },
+      data => {
+        if (data.status === 0) {
+          this.status(data.app.status);
+          this.apiStatus(data.app.apiStatus);
+          this.progress(data.app.progress);
+          this.canWrite(data.app.canWrite);
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    );
+    return this.lastFetchStatusRequest;
+  }
+
+  control(action) {
+    if (action === 'rerun') {
+      $.get(
+        `/oozie/rerun_oozie_job/${this.id()}/?format=json${
+          this.vm.isMini() ? '&is_mini=true' : ''
+        }`,
+        response => {
+          $(`#rerun-modal${this.suffix}`).modal('show');
+          this.rerunModalContent(response);
+        }
+      );
+    } else if (action === 'sync_coordinator') {
+      const $syncCoordinatorModal = $('#syncCoordinatorModal');
+      const endTimeDateUI = $syncCoordinatorModal.find('#endTimeDateUI').val();
+      const endTimeTimeUI = $syncCoordinatorModal.find('#endTimeTimeUI').val();
+      let endTime = '';
+      if (endTimeDateUI !== '' && endTimeTimeUI !== '') {
+        endTime = endTimeDateUI + 'T' + endTimeTimeUI;
+      }
+      const pauseTimeDateUI = $syncCoordinatorModal.find('#pauseTimeDateUI').val();
+      const pauseTimeTimeUI = $syncCoordinatorModal.find('#pauseTimeTimeUI').val();
+      let pauseTime = '';
+      if (pauseTimeDateUI !== '' && pauseTimeTimeUI !== '') {
+        pauseTime = pauseTimeDateUI + 'T' + pauseTimeTimeUI;
+      }
+
+      const clear_pause_time = $syncCoordinatorModal.find('#id_clearPauseTime')[0].checked;
+      const concurrency = $syncCoordinatorModal.find('#id_concurrency').val();
+
+      $.post(
+        '/oozie/manage_oozie_jobs/' + this.id() + '/change',
+        {
+          end_time: endTime,
+          pause_time: pauseTime,
+          clear_pause_time: clear_pause_time,
+          concurrency: concurrency
+        },
+        data => {
+          if (data.status === 0) {
+            huePubSub.publish('hue.global.info', {
+              message: I18n('Successfully updated Coordinator Job Properties')
+            });
+          } else {
+            huePubSub.publish('hue.global.error', { message: data.message });
+          }
+        }
+      ).fail(xhr => {
+        huePubSub.publish('hue.global.error', { message: xhr.responseText });
+      });
+    } else if (action === 'sync_workflow') {
+      $.get(
+        '/oozie/sync_coord_workflow/' + this.id(),
+        {
+          format: 'json'
+        },
+        data => {
+          $(document).trigger('showSubmitPopup', data);
+        }
+      ).fail(xhr => {
+        huePubSub.publish('hue.global.error', { message: xhr.responseText });
+      });
+    } else {
+      this.vm.jobs._control([this.id()], action, data => {
+        huePubSub.publish('hue.global.info', data);
+        this.fetchStatus();
+      });
+    }
+  }
+
+  updateClusterShow() {
+    this.updateClusterWorkers(this.properties['properties']['workerReplicas']());
+    this.updateClusterAutoResize(this.properties['properties']['workerAutoResize']());
+    if (this.properties['properties']['workerAutoResize']()) {
+      this.updateClusterAutoResizeMin(this.properties['properties']['workerAutoResizeMin']());
+      this.updateClusterAutoResizeMax(this.properties['properties']['workerAutoResizeMax']());
+      this.updateClusterAutoResizeCpu(this.properties['properties']['workerAutoResizeCpu']());
+    }
+  }
+
+  updateCluster() {
+    $.post(
+      '/metadata/api/analytic_db/update_cluster/',
+      {
+        is_k8: this.vm.interface().indexOf('dataware2-clusters') !== -1,
+        cluster_name: this.id(),
+        workers_group_size: this.updateClusterWorkers(),
+        auto_resize_changed:
+          this.updateClusterAutoResize() !== this.properties['properties']['workerAutoResize'](),
+        auto_resize_enabled: this.updateClusterAutoResize(),
+        auto_resize_min: this.updateClusterAutoResizeMin(),
+        auto_resize_max: this.updateClusterAutoResizeMax(),
+        auto_resize_cpu: this.updateClusterAutoResizeCpu()
+      },
+      () => {
+        this.updateJob();
+      }
+    );
+  }
+
+  troubleshoot() {
+    $.post(
+      '/metadata/api/workload_analytics/get_operation_execution_details',
+      {
+        operation_id: komapping.toJSON(this.id())
+      },
+      () => {}
+    );
+  }
+
+  redrawOnResize() {
+    huePubSub.publish('graph.draw.arrows');
+  }
+
+  initialArrowsDrawing() {
+    if (this.initialArrowsDrawingCount < 20) {
+      this.initialArrowsDrawingCount++;
+      huePubSub.publish('graph.draw.arrows');
+      window.setTimeout(this.initialArrowsDrawing, 100);
+    } else if (this.initialArrowsDrawingCount < 30) {
+      this.initialArrowsDrawingCount++;
+      huePubSub.publish('graph.draw.arrows');
+      window.setTimeout(this.initialArrowsDrawing, 500);
+    } else {
+      this.initialArrowsDrawingCount = 0;
+    }
+  }
+
+  updateWorkflowGraph() {
+    huePubSub.publish('graph.stop.refresh.view');
+
+    $('canvas').remove();
+
+    if (this.vm.job().type() === 'workflow') {
+      $(`#workflow-page-graph${this.suffix}`).html(
+        '<div class="hue-spinner"><i class="fa fa-spinner fa-spin hue-spinner-center hue-spinner-xlarge"></i></div>'
+      );
+      $.ajax({
+        url: '/oozie/list_oozie_workflow/' + this.vm.job().id(),
+        data: {
+          graph: true,
+          element: `workflow-page-graph${this.suffix}`,
+          is_jb2: true
+        },
+        beforeSend: xhr => {
+          xhr.setRequestHeader('X-Requested-With', 'Hue');
+        },
+        dataType: 'html',
+        success: response => {
+          this.workflowGraphLoaded = true;
+
+          huePubSub.publish('hue4.process.headers', {
+            response: response,
+            callback: r => {
+              $(`#workflow-page-graph${this.suffix}`).html(r);
+              this.initialArrowsDrawing();
+              $(window).on('resize', this.redrawOnResize);
+            }
+          });
+        }
+      });
+    }
+  }
+}
+
+class CoordinatorAction extends Job {
+  constructor(vm, job, coordinator) {
+    super(vm, job);
+    this.coordinator = coordinator;
+    this.canWrite = ko.pureComputed(() => this.coordinator.canWrite());
+    this.resumeEnabled = ko.pureComputed(() => false);
+  }
+}

+ 412 - 0
desktop/core/src/desktop/js/apps/jobBrowser/knockout/JobBrowserViewModel.js

@@ -0,0 +1,412 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+import * as ko from 'knockout';
+
+import { getLastKnownConfig } from 'config/hueConfig';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import { withLocalStorage } from 'utils/storageUtils';
+import changeURL from 'utils/url/changeURL';
+
+import Job from './Job';
+import Jobs from './Jobs';
+
+export default class JobBrowserViewModel {
+  constructor(isMiniJobBrowser) {
+    this.assistAvailable = ko.observable(true);
+    this.isLeftPanelVisible = ko.observable();
+    withLocalStorage('assist.assist_panel_visible', this.isLeftPanelVisible, true);
+    this.isLeftPanelVisible.subscribe(() => {
+      huePubSub.publish('assist.forceRender');
+    });
+
+    this.appConfig = ko.observable();
+    this.clusterType = ko.observable();
+    this.isMini = ko.observable(isMiniJobBrowser);
+
+    this.cluster = ko.observable();
+    this.compute = ko.observable();
+    this.compute.subscribe(() => {
+      if (this.interface()) {
+        this.jobs.fetchJobs();
+      }
+    });
+
+    this.availableInterfaces = ko.pureComputed(() => {
+      const isDialectEnabled = dialect =>
+        this.appConfig()?.editor?.interpreter_names?.indexOf(dialect) >= 0;
+
+      const historyInterfaceCondition = () => window.ENABLE_HISTORY_V2;
+
+      const jobsInterfaceCondition = () =>
+        !getLastKnownConfig().has_computes &&
+        this.appConfig()?.browser?.interpreter_names.indexOf('yarn') !== -1 &&
+        (!this.cluster() || this.cluster().type.indexOf('altus') === -1);
+
+      const dataEngInterfaceCondition = () => this.cluster()?.type === 'altus-de';
+
+      const enginesInterfaceCondition = () => this.cluster()?.type === 'altus-engines';
+
+      const dataWarehouseInterfaceCondition = () => this.cluster()?.type === 'altus-dw';
+
+      const dataWarehouse2InterfaceCondition = () => this.cluster()?.type === 'altus-dw2';
+
+      const schedulerInterfaceCondition = () =>
+        window.USER_HAS_OOZIE_ACCESS &&
+        (!this.cluster() || this.cluster().type.indexOf('altus') === -1) &&
+        this.appConfig()?.scheduler;
+
+      const schedulerExtraInterfaceCondition = () =>
+        !this.isMini() && schedulerInterfaceCondition();
+
+      const schedulerBeatInterfaceCondition = () =>
+        this.appConfig()?.scheduler?.interpreter_names.indexOf('celery-beat') !== -1;
+
+      const livyInterfaceCondition = () =>
+        !this.isMini() &&
+        this.appConfig()?.editor &&
+        (this.appConfig().editor.interpreter_names.indexOf('pyspark') !== -1 ||
+          this.appConfig().editor.interpreter_names.indexOf('sparksql') !== -1);
+
+      const queryInterfaceCondition = () =>
+        window.ENABLE_QUERY_BROWSER &&
+        !getLastKnownConfig().has_computes &&
+        this.appConfig()?.editor.interpreter_names.indexOf('impala') !== -1 &&
+        (!this.cluster() || this.cluster().type.indexOf('altus') === -1);
+
+      const queryHiveInterfaceCondition = () => {
+        return window.ENABLE_HIVE_QUERY_BROWSER && !getLastKnownConfig().has_computes;
+      };
+
+      const scheduleHiveInterfaceCondition = () => window.ENABLE_QUERY_SCHEDULING;
+
+      const hiveQueriesInterfaceCondition = () =>
+        window.ENABLE_QUERY_STORE && isDialectEnabled('hive');
+      const impalaQueriesInterfaceCondition = () =>
+        window.ENABLE_QUERY_STORE && isDialectEnabled('impala');
+
+      const interfaces = [
+        { interface: 'jobs', label: I18n('Jobs'), condition: jobsInterfaceCondition },
+        { interface: 'dataeng-jobs', label: I18n('Jobs'), condition: dataEngInterfaceCondition },
+        {
+          interface: 'dataeng-clusters',
+          label: I18n('Clusters'),
+          condition: dataEngInterfaceCondition
+        },
+        {
+          interface: 'dataware-clusters',
+          label: I18n('Clusters'),
+          condition: dataWarehouseInterfaceCondition
+        },
+        {
+          interface: 'dataware2-clusters',
+          label: I18n('Warehouses'),
+          condition: dataWarehouse2InterfaceCondition
+        },
+        { interface: 'engines', label: '', condition: enginesInterfaceCondition },
+        { interface: 'queries-impala', label: 'Impala', condition: queryInterfaceCondition },
+        { interface: 'queries-hive', label: 'Hive', condition: queryHiveInterfaceCondition },
+        {
+          interface: 'schedule-hive',
+          label: I18n('Hive Schedules'),
+          condition: scheduleHiveInterfaceCondition
+        },
+        {
+          interface: 'celery-beat',
+          label: I18n('Scheduled Tasks'),
+          condition: schedulerBeatInterfaceCondition
+        },
+        { interface: 'history', label: I18n('History'), condition: historyInterfaceCondition },
+        {
+          interface: 'workflows',
+          label: I18n('Workflows'),
+          condition: schedulerInterfaceCondition
+        },
+        {
+          interface: 'schedules',
+          label: I18n('Schedules'),
+          condition: schedulerInterfaceCondition
+        },
+        {
+          interface: 'bundles',
+          label: I18n('Bundles'),
+          condition: schedulerExtraInterfaceCondition
+        },
+        { interface: 'slas', label: I18n('SLAs'), condition: schedulerExtraInterfaceCondition },
+        { interface: 'livy-sessions', label: 'Livy', condition: livyInterfaceCondition },
+        {
+          interface: 'hive-queries',
+          label: I18n('Hive Queries'),
+          condition: hiveQueriesInterfaceCondition
+        },
+        {
+          interface: 'impala-queries',
+          label: I18n('Impala Queries'),
+          condition: impalaQueriesInterfaceCondition
+        }
+      ];
+
+      return interfaces.filter(i => i.condition());
+    });
+
+    this.availableInterfaces.subscribe(newInterfaces => {
+      if (
+        this.interface() &&
+        !newInterfaces.some(newInterface => newInterface.interface === this.interface())
+      ) {
+        this.selectInterface(newInterfaces[0]);
+      }
+    });
+
+    this.slasLoadedOnce = false;
+    this.slasLoading = ko.observable(true);
+
+    this.oozieInfoLoadedOnce = false;
+    this.oozieInfoLoading = ko.observable(true);
+
+    this.interface = ko.observable();
+
+    this.jobs = new Jobs(this);
+    this.job = ko.observable();
+
+    this.updateJobTimeout = -1;
+    this.updateJobsTimeout = -1;
+    this.jobUpdateCounter = 0;
+    this.exponentialFactor = 1;
+
+    this.job.subscribe(val => {
+      this.monitorJob(val);
+    });
+
+    this.breadcrumbs = ko.observableArray([]);
+
+    this.resetBreadcrumbs();
+  }
+
+  contextId(id) {
+    if (this.isMini()) {
+      return id + '-mini';
+    }
+    return id;
+  }
+
+  showTab(anchor) {
+    $(`a[href='${this.contextId(anchor)}']`).tab('show');
+  }
+
+  loadSlaPage() {
+    if (!this.slasLoadedOnce) {
+      $.ajax({
+        url: '/oozie/list_oozie_sla/?is_embeddable=true',
+        beforeSend: xhr => {
+          xhr.setRequestHeader('X-Requested-With', 'Hue');
+        },
+        dataType: 'html',
+        success: response => {
+          this.slasLoading(false);
+          $('#slas').html(response);
+        }
+      });
+    }
+  }
+
+  loadOozieInfoPage() {
+    if (!this.oozieInfoLoadedOnce) {
+      this.oozieInfoLoadedOnce = true;
+      this.oozieInfoLoading(true);
+      $.ajax({
+        url: '/oozie/list_oozie_info/?is_embeddable=true',
+        beforeSend: xhr => {
+          xhr.setRequestHeader('X-Requested-With', 'Hue');
+        },
+        dataType: 'html'
+      })
+        .done(response => {
+          $('#oozieInfo').html(response);
+        })
+        .fail(err => {
+          this.oozieInfoLoadedOnce = false;
+          this.selectInterface('schedules');
+          huePubSub.publish('hue.global.error', { message: I18n('Failed loading Oozie Info.') });
+        })
+        .always(() => {
+          this.oozieInfoLoading(false);
+        });
+    }
+  }
+
+  isValidInterface(name) {
+    const flatAvailableInterfaces = this.availableInterfaces().map(i => i.interface);
+    if (flatAvailableInterfaces.indexOf(name) !== -1 || name === 'oozie-info') {
+      return name;
+    } else {
+      return flatAvailableInterfaces[0];
+    }
+  }
+
+  selectInterface(selectedInterface) {
+    const validSelectedInterface = this.isValidInterface(selectedInterface);
+    this.interface(validSelectedInterface);
+    this.resetBreadcrumbs();
+
+    if (!this.isMini()) {
+      huePubSub.publish('graph.stop.refresh.view');
+      if (window.location.hash !== '#!' + validSelectedInterface) {
+        changeURL('#!' + validSelectedInterface);
+      }
+    }
+    this.jobs.selectedJobs([]);
+    this.job(null);
+
+    if (validSelectedInterface === 'slas' && !this.isMini()) {
+      this.loadSlaPage();
+    } else if (validSelectedInterface === 'oozie-info' && !this.isMini()) {
+      this.loadOozieInfoPage();
+    } else if (
+      validSelectedInterface !== 'hive-queries' ||
+      validSelectedInterface !== 'impala-queries'
+    ) {
+      this.jobs.fetchJobs();
+    }
+  }
+
+  onClusterSelect() {
+    let interfaceToSet = this.interface();
+    if (
+      !this.availableInterfaces().some(
+        availableInterface => availableInterface.interface === interfaceToSet
+      )
+    ) {
+      interfaceToSet = this.availableInterfaces()[0].interface;
+    }
+    this.selectInterface(interfaceToSet);
+  }
+
+  monitorJob(job) {
+    window.clearTimeout(this.updateJobTimeout);
+    window.clearTimeout(this.updateJobsTimeout);
+    this.jobUpdateCounter = 0;
+    this.exponentialFactor = 1;
+    if (
+      this.interface() &&
+      this.interface() !== 'slas' &&
+      this.interface() !== 'oozie-info' &&
+      this.interface !== 'hive-queries' &&
+      this.interface !== 'impala-queries'
+    ) {
+      if (job) {
+        if (job.apiStatus() === 'RUNNING') {
+          const _updateJob = () => {
+            this.jobUpdateCounter++;
+            const updateLogs = this.jobUpdateCounter % this.exponentialFactor === 0;
+            if (updateLogs && this.exponentialFactor < 50) {
+              this.exponentialFactor *= 2;
+            }
+            const def = job.updateJob(updateLogs);
+            if (def) {
+              def.done(() => {
+                this.updateJobTimeout = setTimeout(
+                  _updateJob,
+                  window.JB_SINGLE_CHECK_INTERVAL_IN_MILLIS
+                );
+              });
+            }
+          };
+          this.updateJobTimeout = setTimeout(_updateJob, window.JB_SINGLE_CHECK_INTERVAL_IN_MILLIS);
+        }
+      } else {
+        const _updateJobs = () => {
+          const updateRequest = this.jobs.updateJobs();
+          if (updateRequest?.done) {
+            updateRequest.done(() => {
+              setTimeout(_updateJobs, window.JB_MULTI_CHECK_INTERVAL_IN_MILLIS);
+            });
+          }
+        };
+        this.updateJobsTimeout = setTimeout(_updateJobs, window.JB_MULTI_CHECK_INTERVAL_IN_MILLIS);
+      }
+    }
+  }
+
+  resetBreadcrumbs(extraCrumbs) {
+    let crumbs = [{ id: '', name: this.interface(), type: this.interface() }];
+    if (extraCrumbs) {
+      crumbs = crumbs.concat(extraCrumbs);
+    }
+    this.breadcrumbs(crumbs);
+  }
+
+  getHDFSPath(path) {
+    if (path.startsWith('hdfs://')) {
+      const bits = path.substr(7).split('/');
+      bits.shift();
+      return '/' + bits.join('/');
+    }
+    return path;
+  }
+
+  formatProgress(progress) {
+    if (typeof progress === 'function') {
+      progress = progress();
+    }
+    if (!isNaN(progress)) {
+      return Math.round(progress * 100) / 100 + '%';
+    }
+    return progress;
+  }
+
+  load() {
+    let h = window.location.hash;
+    if (!this.isMini()) {
+      huePubSub.publish('graph.stop.refresh.view');
+    }
+
+    h = h.indexOf('#!') === 0 ? h.substr(2) : '';
+    switch (h) {
+      case '':
+        h = 'jobs';
+      case 'slas':
+      case 'oozie-info':
+      case 'jobs':
+      case 'queries-impala':
+      case 'queries-hive':
+      case 'celery-beat':
+      case 'schedule-hive':
+      case 'history':
+      case 'workflows':
+      case 'schedules':
+      case 'bundles':
+      case 'dataeng-clusters':
+      case 'dataware-clusters':
+      case 'dataware2-clusters':
+      case 'engines':
+      case 'dataeng-jobs':
+      case 'livy-sessions':
+      case 'hive-queries':
+      case 'impala-queries':
+        this.selectInterface(h);
+        break;
+      default:
+        if (h.indexOf('id=') === 0 && !this.isMini()) {
+          new Job(this, { id: h.substr(3) }).fetchJob();
+        } else {
+          this.selectInterface('reset');
+        }
+    }
+  }
+}

+ 435 - 0
desktop/core/src/desktop/js/apps/jobBrowser/knockout/Jobs.js

@@ -0,0 +1,435 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+import * as ko from 'knockout';
+import komapping from 'knockout.mapping';
+
+import apiHelper from 'api/apiHelper';
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+import Job from './Job';
+export default class Jobs {
+  constructor(vm) {
+    this.vm = vm;
+    this.apps = ko.observableArray().extend({ rateLimit: 50 });
+    this.runningApps = ko.pureComputed(() => this.apps().filter(app => app.isRunning()));
+    this.finishedApps = ko.pureComputed(() => this.apps().filter(app => !app.isRunning()));
+    this.totalApps = ko.observable(null);
+    this.isCoordinator = ko.observable(false);
+
+    this.loadingJobs = ko.observable(false);
+    this.selectedJobs = ko.observableArray();
+
+    this.hasKill = ko.pureComputed(
+      () =>
+        !this.isCoordinator() &&
+        [
+          'jobs',
+          'workflows',
+          'schedules',
+          'bundles',
+          'queries-impala',
+          'dataeng-jobs',
+          'dataeng-clusters',
+          'dataware-clusters',
+          'dataware2-clusters',
+          'celery-beat',
+          'schedule-hive',
+          'history'
+        ].indexOf(this.vm.interface()) !== -1
+    );
+    this.killEnabled = ko.pureComputed(
+      () =>
+        this.hasKill() &&
+        this.selectedJobs().length &&
+        this.selectedJobs().every(job => job.killEnabled())
+    );
+
+    this.hasResume = ko.pureComputed(
+      () =>
+        !this.isCoordinator() &&
+        [
+          'workflows',
+          'schedules',
+          'bundles',
+          'dataware2-clusters',
+          'celery-beat',
+          'schedule-hive',
+          'history'
+        ].indexOf(this.vm.interface()) !== -1
+    );
+    this.resumeEnabled = ko.pureComputed(
+      () =>
+        this.hasResume() &&
+        this.selectedJobs().length &&
+        this.selectedJobs().every(job => job.resumeEnabled())
+    );
+
+    this.hasRerun = ko.pureComputed(() => this.isCoordinator());
+    this.rerunEnabled = ko.pureComputed(() => {
+      const validSelectionCount =
+        this.selectedJobs().length === 1 ||
+        (this.selectedJobs().length > 1 && this.vm.interface() === 'schedules');
+      return (
+        this.hasRerun() &&
+        validSelectionCount &&
+        this.selectedJobs().every(job => job.rerunEnabled())
+      );
+    });
+
+    this.hasPause = ko.pureComputed(
+      () =>
+        !this.isCoordinator() &&
+        [
+          'workflows',
+          'schedules',
+          'bundles',
+          'dataware2-clusters',
+          'celery-beat',
+          'schedule-hive',
+          'history'
+        ].indexOf(this.vm.interface()) !== -1
+    );
+    this.pauseEnabled = ko.pureComputed(
+      () =>
+        this.hasPause() &&
+        this.selectedJobs().length &&
+        this.selectedJobs().every(job => job.pauseEnabled())
+    );
+
+    this.hasIgnore = ko.pureComputed(() => this.isCoordinator());
+    this.ignoreEnabled = ko.pureComputed(
+      () =>
+        this.hasIgnore() &&
+        this.selectedJobs().length &&
+        this.selectedJobs().every(job => job.ignoreEnabled())
+    );
+
+    this.textFilter = ko.observable(`user:${window.LOGGED_USERNAME} `).extend({
+      rateLimit: {
+        method: 'notifyWhenChangesStop',
+        timeout: 1000
+      }
+    });
+    this.statesValuesFilter = ko.observableArray([
+      komapping.fromJS({
+        value: 'completed',
+        name: I18n('Succeeded'),
+        checked: false,
+        klass: 'green'
+      }),
+      komapping.fromJS({
+        value: 'running',
+        name: I18n('Running'),
+        checked: false,
+        klass: 'orange'
+      }),
+      komapping.fromJS({ value: 'failed', name: I18n('Failed'), checked: false, klass: 'red' })
+    ]);
+
+    this.statesFilter = ko.computed(() =>
+      this.statesValuesFilter()
+        .filter(state => state.checked())
+        .map(state => state.value())
+    );
+    this.timeValueFilter = ko.observable(7).extend({ throttle: 500 });
+    this.timeUnitFilter = ko.observable('days').extend({ throttle: 500 });
+    this.timeUnitFilterUnits = ko.observable([
+      { value: 'days', name: I18n('days') },
+      { value: 'hours', name: I18n('hours') },
+      { value: 'minutes', name: I18n('minutes') }
+    ]);
+
+    this.hasPagination = ko.computed(
+      () => ['workflows', 'schedules', 'bundles'].indexOf(this.vm.interface()) !== -1
+    );
+    this.paginationPage = ko.observable(1);
+    this.paginationOffset = ko.observable(1); // Starting index
+    this.paginationResultPage = ko.observable(100);
+    this.pagination = ko.computed(() => ({
+      page: this.paginationPage(),
+      offset: this.paginationOffset(),
+      limit: this.paginationResultPage()
+    }));
+
+    this.showPreviousPage = ko.computed(() => this.paginationOffset() > 1);
+    this.showNextPage = ko.computed(
+      () =>
+        this.totalApps() && this.paginationOffset() + this.paginationResultPage() < this.totalApps()
+    );
+
+    this.searchFilters = ko.pureComputed(() => [
+      { text: this.textFilter() },
+      { time: { time_value: this.timeValueFilter(), time_unit: this.timeUnitFilter() } },
+      { states: komapping.toJS(this.statesFilter()) }
+    ]);
+    this.searchFilters.subscribe(() => {
+      this.paginationOffset(1);
+    });
+    this.paginationFilters = ko.pureComputed(() => [{ pagination: this.pagination() }]);
+    this.filters = ko.pureComputed(() => this.searchFilters().concat(this.paginationFilters()));
+    this.filters.subscribe(() => {
+      this.fetchJobs();
+    });
+
+    this.lastFetchJobsRequest = null;
+    this.lastUpdateJobsRequest = null;
+    this.showJobCountBanner = ko.pureComputed(() => this.apps().length === window.MAX_JOB_FETCH);
+
+    this.createClusterShow = ko.observable(false);
+    this.createClusterName = ko.observable('');
+    this.createClusterWorkers = ko.observable(1);
+    this.createClusterShowWorkers = ko.observable(false);
+    this.createClusterAutoResize = ko.observable(false);
+    this.createClusterAutoPause = ko.observable(false);
+  }
+
+  _control(app_ids, action, callback) {
+    $.post(
+      '/jobbrowser/api/job/action/' + this.vm.interface() + '/' + action,
+      {
+        app_ids: komapping.toJSON(app_ids),
+        interface: komapping.toJSON(this.vm.interface),
+        operation: komapping.toJSON({ action: action })
+      },
+      data => {
+        if (data.status === 0) {
+          if (callback) {
+            callback(data);
+          }
+          if (this.vm.interface().indexOf('clusters') !== -1 && action === 'kill') {
+            huePubSub.publish('context.catalog.refresh');
+            this.selectedJobs([]);
+          }
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    ).always(() => {});
+  }
+
+  previousPage() {
+    this.paginationOffset(this.paginationOffset() - this.paginationResultPage());
+  }
+
+  nextPage() {
+    this.paginationOffset(this.paginationOffset() + this.paginationResultPage());
+  }
+
+  _fetchJobs(callback) {
+    return $.post(
+      '/jobbrowser/api/jobs/' + this.vm.interface(),
+      {
+        cluster: komapping.toJSON(this.vm.compute),
+        interface: komapping.toJSON(this.vm.interface),
+        filters: komapping.toJSON(this.filters)
+      },
+      data => {
+        if (data.status === 0) {
+          if (data.apps?.length) {
+            huePubSub.publish('jobbrowser.data', data.apps);
+          }
+          if (callback) {
+            callback(data);
+          }
+        } else {
+          huePubSub.publish('hue.global.error', { message: data.message });
+        }
+      }
+    );
+  }
+
+  fetchJobs() {
+    if (this.vm.interface() === 'hive-queries' || this.vm.interface() === 'impala-queries') {
+      return;
+    }
+
+    apiHelper.cancelActiveRequest(this.lastUpdateJobsRequest);
+    apiHelper.cancelActiveRequest(this.lastFetchJobsRequest);
+
+    this.loadingJobs(true);
+    this.vm.job(null);
+    this.lastFetchJobsRequest = this._fetchJobs(data => {
+      const apps = [];
+      if (data?.apps) {
+        data.apps.forEach(job => {
+          apps.push(new Job(this.vm, job));
+        });
+      }
+      this.apps(apps);
+      this.totalApps(data?.total || 0);
+    }).always(() => {
+      this.loadingJobs(false);
+    });
+  }
+
+  updateJobs() {
+    if (this.vm.interface() === 'hive-queries' || this.vm.interface() === 'impala-queries') {
+      return;
+    }
+
+    apiHelper.cancelActiveRequest(this.lastUpdateJobsRequest);
+
+    this.lastFetchJobsRequest = this._fetchJobs(data => {
+      if (data?.apps) {
+        let i = 0,
+          j = 0;
+        const newJobs = [];
+
+        while ((this.apps().length === 0 || i < this.apps().length) && j < data.apps.length) {
+          // Nothing displayed or compare existing
+          if (this.apps().length === 0 || this.apps()[i].id() !== data.apps[j].id) {
+            // New Job
+            newJobs.unshift(new Job(this.vm, data.apps[j]));
+            j++;
+          } else {
+            // Updated jobs
+            if (this.apps()[i].status() !== data.apps[j].status) {
+              this.apps()[i].status(data.apps[j].status);
+              this.apps()[i].apiStatus(data.apps[j].apiStatus);
+              this.apps()[i].canWrite(data.apps[j].canWrite);
+            }
+            i++;
+            j++;
+          }
+        }
+
+        if (i < this.apps().length) {
+          this.apps.splice(i, this.apps().length - i);
+        }
+
+        newJobs.forEach(job => {
+          this.apps.unshift(job);
+        });
+
+        this.totalApps(data.total);
+      }
+    });
+    return this.lastFetchJobsRequest;
+  }
+
+  createClusterFormReset() {
+    this.createClusterName('');
+    this.createClusterWorkers(1);
+    this.createClusterAutoResize(false);
+    this.createClusterAutoPause(false);
+  }
+
+  createCluster() {
+    if (this.vm.interface().indexOf('dataeng') !== -1) {
+      $.post(
+        '/metadata/api/dataeng/create_cluster/',
+        {
+          cluster_name: 'cluster_name',
+          cdh_version: 'CDH515',
+          public_key: 'public_key',
+          instance_type: 'm4.xlarge',
+          environment_name:
+            'crn:altus:environments:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:environment:analytics/236ebdda-18bd-428a-9d2b-cd6973d42946',
+          workers_group_size: '3',
+          namespace_name:
+            'crn:altus:sdx:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:namespace:analytics/7ea35fe5-dbc9-4b17-92b1-97a1ab32e410'
+        },
+        () => {
+          this.updateJobs();
+          huePubSub.publish('context.catalog.refresh');
+        }
+      );
+    } else {
+      $.post(
+        '/metadata/api/analytic_db/create_cluster/',
+        {
+          is_k8: this.vm.interface().indexOf('dataware2-clusters') !== -1,
+          cluster_name: this.createClusterName(),
+          cluster_hdfs_host: 'hdfs-namenode',
+          cluster_hdfs_port: 9820,
+          cdh_version: 'CDH515',
+          public_key: 'public_key',
+          instance_type: 'm4.xlarge',
+          environment_name:
+            'crn:altus:environments:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:environment:jheyming-secure/b4e6d99a-261f-4ada-9b4a-576aa0af8979',
+          workers_group_size: this.createClusterWorkers(),
+          namespace_name:
+            'crn:altus:sdx:us-west-1:12a0079b-1591-4ca0-b721-a446bda74e67:namespace:analytics/7ea35fe5-dbc9-4b17-92b1-97a1ab32e410'
+        },
+        () => {
+          this.createClusterFormReset();
+          this.updateJobs();
+          huePubSub.publish('context.catalog.refresh');
+        }
+      );
+    }
+    this.createClusterShow(false);
+  }
+
+  control(action) {
+    const suffix = this.vm.isMini() ? '-mini' : '';
+    if (action === 'rerun') {
+      $.get(
+        `/oozie/rerun_oozie_coord/${this.vm.job().id()}/?format=json${
+          this.vm.isMini() ? '&is_mini=true' : ''
+        }`,
+        response => {
+          $(`#rerun-modal${suffix}`).modal('show');
+          this.vm.job().rerunModalContent(response);
+          // Force Knockout to handle the update of rerunModalContent before trying to modify its DOM
+          ko.tasks.runEarly();
+
+          const frag = document.createDocumentFragment();
+          this.vm
+            .job()
+            .coordinatorActions()
+            .selectedJobs()
+            .forEach(item => {
+              const option = $('<option>', {
+                value: item.properties.number(),
+                selected: true
+              });
+              option.appendTo($(frag));
+            });
+          $(`#id_actions${suffix}`).find('option').remove();
+          $(frag).appendTo(`#id_actions${suffix}`);
+        }
+      );
+    } else if (action === 'ignore') {
+      $.post(
+        '/oozie/manage_oozie_jobs/' + this.vm.job().id() + '/ignore',
+        {
+          actions: this.vm
+            .job()
+            .coordinatorActions()
+            .selectedJobs()
+            .map(wf => wf.properties.number())
+            .join(' ')
+        },
+        () => {
+          this.vm.job().apiStatus('RUNNING');
+          this.vm.job().updateJob();
+        }
+      );
+    } else {
+      this._control(
+        this.selectedJobs().map(job => job.id()),
+        action,
+        data => {
+          huePubSub.publish('hue.global.info', data);
+          this.updateJobs();
+        }
+      );
+    }
+  }
+}

+ 99 - 0
desktop/core/src/desktop/js/apps/jobBrowser/miniApp.js

@@ -0,0 +1,99 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. 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.
+
+import $ from 'jquery';
+import * as ko from 'knockout';
+
+import huePubSub from 'utils/huePubSub';
+import I18n from 'utils/i18n';
+
+import './components/hiveQueryPlan/webcomp';
+import './components/queriesList/webcomp';
+import './components/impalaQueries/webcomp';
+import './eventListeners';
+import JobBrowserViewModel from './knockout/JobBrowserViewModel';
+import Job from './knockout/Job';
+
+$(document).ready(() => {
+  const jobBrowserViewModel = new JobBrowserViewModel(true);
+  const openJob = id => {
+    if (jobBrowserViewModel.job() == null) {
+      jobBrowserViewModel.job(new Job(jobBrowserViewModel, {}));
+    }
+    jobBrowserViewModel.job().id(id);
+    jobBrowserViewModel.job().fetchJob();
+  };
+
+  ko.applyBindings(jobBrowserViewModel, $('#jobbrowserMiniComponents')[0]);
+
+  huePubSub.subscribe(
+    'app.gained.focus',
+    app => {
+      if (app === 'jobbrowser') {
+        huePubSub.publish('graph.draw.arrows');
+        loadHash();
+      }
+    },
+    'jobbrowser'
+  );
+
+  const loadHash = () => {
+    if (window.location.pathname.indexOf('jobbrowser') > -1) {
+      jobBrowserViewModel.load();
+    }
+  };
+
+  window.onhashchange = () => {
+    loadHash();
+  };
+
+  const configUpdated = clusterConfig => {
+    jobBrowserViewModel.appConfig(clusterConfig && clusterConfig['app_config']);
+    jobBrowserViewModel.clusterType(clusterConfig && clusterConfig['cluster_type']);
+    loadHash();
+  };
+
+  huePubSub.subscribe('cluster.config.set.config', configUpdated);
+  huePubSub.publish('cluster.config.get.config', configUpdated);
+
+  huePubSub.subscribe('submit.rerun.popup.return-mini', () => {
+    huePubSub.publish('hue.global.info', { message: I18n('Rerun submitted.') });
+    $('#rerun-modal-mini').modal('hide');
+
+    jobBrowserViewModel.job().apiStatus('RUNNING');
+    jobBrowserViewModel.monitorJob(jobBrowserViewModel.job());
+  });
+
+  huePubSub.subscribe('mini.jb.navigate', options => {
+    if (options.compute) {
+      jobBrowserViewModel.compute(options.compute);
+    }
+    $('#jobsPanel .nav-pills li').removeClass('active');
+    const interfaceName = jobBrowserViewModel.isValidInterface(options.section);
+    $('#jobsPanel .nav-pills li[data-interface="' + interfaceName + '"]').addClass('active');
+    jobBrowserViewModel.selectInterface(interfaceName);
+  });
+
+  huePubSub.subscribe('mini.jb.open.job', openJob);
+
+  huePubSub.subscribe('mini.jb.expand', () => {
+    if (jobBrowserViewModel.job()) {
+      huePubSub.publish('open.link', '/jobbrowser/#!id=' + jobBrowserViewModel.job().id());
+    } else {
+      huePubSub.publish('open.link', '/jobbrowser/#!' + jobBrowserViewModel.interface());
+    }
+  });
+});

+ 28 - 5
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -29,7 +29,8 @@
   from dashboard.conf import HAS_SQL_ENABLED
   from hadoop.conf import UPLOAD_CHUNK_SIZE
   from indexer.conf import ENABLE_NEW_INDEXER
-  from jobbrowser.conf import ENABLE_HISTORY_V2
+  from jobbrowser.conf import ENABLE_HISTORY_V2, ENABLE_QUERY_BROWSER, ENABLE_HIVE_QUERY_BROWSER, MAX_JOB_FETCH, \
+      QUERY_STORE
   from filebrowser.conf import SHOW_UPLOAD_BUTTON, REMOTE_STORAGE_HOME, MAX_FILE_SIZE_UPLOAD_LIMIT
   from indexer.conf import ENABLE_NEW_INDEXER
   from libsaml.conf import get_logout_redirect_url, CDP_LOGOUT_URL
@@ -175,8 +176,6 @@
 
   window.USE_DEFAULT_CONFIGURATION = '${ USE_DEFAULT_CONFIGURATION.get() }' === 'True';
 
-  window.USER_HAS_METADATA_WRITE_PERM = '${ user.has_hue_permission(action="write", app="metadata") }' === 'True';
-
   window.ENABLE_DOWNLOAD = '${ conf.ENABLE_DOWNLOAD.get() }' === 'True';
   window.ENABLE_NEW_CREATE_TABLE = '${ hasattr(ENABLE_NEW_CREATE_TABLE, 'get') and ENABLE_NEW_CREATE_TABLE.get()}' === 'True';
   window.ENABLE_HUE_5 = '${ ENABLE_HUE_5.get() }' === 'True';
@@ -189,7 +188,9 @@
   window.RAZ_IS_ENABLED = '${ RAZ.IS_ENABLED.get() }' === 'True';
 
   window.ENABLE_HISTORY_V2 = '${ hasattr(ENABLE_HISTORY_V2, 'get') and ENABLE_HISTORY_V2.get() }' === 'True';
-
+  window.ENABLE_HIVE_QUERY_BROWSER = '${ hasattr(ENABLE_QUERY_BROWSER, 'get') and ENABLE_QUERY_BROWSER.get() }' === 'True';
+  window.ENABLE_QUERY_BROWSER = '${ hasattr(ENABLE_QUERY_BROWSER, 'get') and ENABLE_QUERY_BROWSER.get() }' === 'True';
+  window.ENABLE_QUERY_STORE = '${ hasattr(QUERY_STORE.IS_ENABLED, 'get') and QUERY_STORE.IS_ENABLED.get() }' === 'True'
   window.ENABLE_SQL_SYNTAX_CHECK = '${ conf.ENABLE_SQL_SYNTAX_CHECK.get() }' === 'True';
 
   window.HAS_CATALOG = '${ has_catalog(request.user) }' === 'True';
@@ -216,7 +217,7 @@
 
   window.UPLOAD_CHUNK_SIZE = ${ UPLOAD_CHUNK_SIZE.get() };
   window.MAX_FILE_SIZE_UPLOAD_LIMIT = ${ MAX_FILE_SIZE_UPLOAD_LIMIT.get() if hasattr(MAX_FILE_SIZE_UPLOAD_LIMIT, 'get') and MAX_FILE_SIZE_UPLOAD_LIMIT.get() >= 0 else 'undefined' };
-
+  window.MAX_JOB_FETCH = ${ MAX_JOB_FETCH.get() if hasattr(MAX_JOB_FETCH, 'get') and MAX_JOB_FETCH.get() >= 0 else 500 };
   window.OTHER_APPS = [];
   % if other_apps:
     % for other in other_apps:
@@ -295,6 +296,7 @@
     'Browse table privileges': '${_('Browse table privileges')}',
     'Browsers': '${ _('Browsers') }',
     'Bundle': '${ _('Bundle') }',
+    'Bundles': '${ _('Bundles') }',
     'Canada': '${ _('Canada') }',
     'Cancel upload': '${ _('Cancel upload') }',
     'Cancel': '${_('Cancel')}',
@@ -349,6 +351,7 @@
     'Database': '${ _('Database') }',
     'database': '${ _('database') }',
     'Databases': '${ _('Databases') }',
+    'days': '${ _('days') }',
     'default': '${ _('default') }',
     'Delete document': '${ _('Delete document') }',
     'Delete this privilege': '${ _('Delete this privilege') }',
@@ -434,11 +437,16 @@
     'Help': '${ _('Help') }',
     'Hide advanced': '${_('Hide advanced')}',
     'Hide Details': '${ _('Hide Details') }',
+    'History': '${ _('History') }',
     'Hive Query': '${_('Hive Query')}',
+    'Hive Queries': '${_('Hive Queries')}',
+    'Hive Schedules': '${_('Hive Schedules')}',
+    'hours': '${ _('hours') }',
     'Hover on the app name to star it as your favorite application.': '${ _('Hover on the app name to star it as your favorite application.') }',
     'Identifiers': '${ _('Identifiers') }',
     'Ignore this type of error': '${_('Ignore this type of error')}',
     'Impala Query': '${_('Impala Query')}',
+    'Impala Queries': '${_('Impala Queries')}',
     'Import complete!': '${ _('Import complete!') }',
     'Import failed!': '${ _('Import failed!') }',
     'Import Hue Documents': '${ _('Import Hue Documents') }',
@@ -484,6 +492,7 @@
     'longitude': '${ _('longitude') }',
     'Manage Users': '${ _('Manage Users') }',
     'Manual refresh': '${_('Manual refresh')}',
+    'Map': '${ _('Map') }',
     'MapReduce Job': '${_('MapReduce Job')}',
     'Marker Map': '${ _('Marker Map') }',
     'Markers': '${ _('Markers') }',
@@ -491,6 +500,7 @@
     'Memory': '${ _('Memory') }',
     'Metrics': '${ _('Metrics') }',
     'min': '${ _('min') }',
+    'minutes': '${ _('minutes') }',
     'Missing label configuration.': '${ _('Missing label configuration.') }',
     'Missing latitude configuration.': '${ _('Missing latitude configuration.') }',
     'Missing legend configuration.': '${ _('Missing legend configuration.') }',
@@ -520,6 +530,7 @@
     'No documents found': '${ _('No documents found') }',
     'No entries found': '${ _('No entries found') }',
     'No indexes selected.': '${ _('No indexes selected.') }',
+    'No log available': '${ _('No log available') }',
     'No logs available at this moment.': '${ _('No logs available at this moment.') }',
     'No match found': '${ _('No match found') }',
     'No matching records': '${ _('No matching records') }',
@@ -589,10 +600,12 @@
     'Re-create session': '${ _('Re-create session') }',
     'Re-create': '${ _('Re-create') }',
     'Read': '${ _('Read') }',
+    'Reduce': '${ _('Reduce') }',
     'Refresh': '${ _('Refresh') }',
     'region': '${ _('region') }',
     'Remove': '${ _('Remove') }',
     'Replace the editor content...': '${ _('Replace the editor content...') }',
+    'Rerun submitted.': '${ _('Rerun submitted.') }',
     'Result available': '${ _('Result available') }',
     'Result expired': '${ _('Result expired') }',
     'Result image': '${ _('Result image') }',
@@ -612,6 +625,8 @@
     'Scatter Plot': '${ _('Scatter Plot') }',
     'scatter size': '${ _('scatter size') }',
     'Schedule': '${ _('Schedule') }',
+    'Schedules': '${ _('Schedules') }',
+    'Scheduled Tasks': '${ _('Scheduled Tasks') }',
     'scope': '${ _('scope') }',
     'Search Dashboard': '${_('Search Dashboard')}',
     'Search data and saved documents...': '${ _('Search data and saved documents...') }',
@@ -649,6 +664,7 @@
     'Sidebar for navigation': '${_('Sidebar for navigation')}',
     'Sign out': '${ _('Sign out') }',
     'Size': '${ _('Size') }',
+    'SLAs': '${ _('SLAs') }',
     'Solr Search': '${ _('Solr Search') }',
     'Some apps have a right panel with additional information to assist you in your data discovery.': '${ _('Some apps have a right panel with additional information to assist you in your data discovery.') }',
     'Sort ascending': '${ _('Sort ascending') }',
@@ -666,7 +682,9 @@
     'Stopped': '${_('Stopped')}',
     'Stopping': '${ _('Stopping') }',
     'Streams': '${ _('Streams') }',
+    'Succeeded': '${ _('Succeeded') }',
     'Success.': '${ _('Success.') }',
+    'Successfully updated Coordinator Job Properties': '${ _('Successfully updated Coordinator Job Properties') }',
     'Summary': '${_('Summary')}',
     'Table Browser': '${ _('Table Browser') }',
     'Table': '${ _('Table') }',
@@ -726,12 +744,14 @@
     'view': '${ _('view') }',
     'Views': '${ _('Views') }',
     'virtual': '${ _('virtual') }',
+    'Warehouses': '${ _('Warehouses') }',
     'We want to introduce you to the new interface. It takes less than a minute. Ready?': '${ _('We want to introduce you to the new interface. It takes less than a minute. Ready?') }',
     'Welcome Tour': '${ _('Welcome Tour') }',
     'Welcome to Hue 4!': '${ _('Welcome to Hue 4!') }',
     'With grant option': '${ _('With grant option') }',
     'With grant': '${ _('With grant') }',
     'Workflow': '${ _('Workflow') }',
+    'Workflows': '${ _('Workflows') }',
     'World': '${ _('World') }',
     'x-axis': '${ _('x-axis') }',
     'y-axis': '${ _('y-axis') }',
@@ -769,6 +789,9 @@
   };
 
   window.USER_VIEW_EDIT_USER_ENABLED = '${ user.has_hue_permission(action="access_view:useradmin:edit_user", app="useradmin") or is_admin(user) }' === 'True';
+  window.USER_HAS_METADATA_WRITE_PERM = '${ user.has_hue_permission(action="write", app="metadata") }' === 'True';
+  window.USER_HAS_OOZIE_ACCESS = '${ user.has_hue_permission(action="access", app="oozie") }' === 'True';
+
   window.USER_IS_ADMIN = '${ is_admin(user) }' === 'True';
   window.USER_IS_HUE_ADMIN = '${ is_hue_admin(user) }' === 'True';
   window.DJANGO_DEBUG_MODE = '${ conf.DJANGO_DEBUG_MODE.get() }' === 'True';

+ 7 - 0
desktop/core/src/desktop/templates/hue.mako

@@ -35,6 +35,7 @@
 
 <%namespace name="hueIcons" file="/hue_icons.mako" />
 <%namespace name="commonHeaderFooterComponents" file="/common_header_footer_components.mako" />
+<%namespace name="jbCommon" file="/job_browser_common.mako" />
 
 <!DOCTYPE html>
 <html lang="en" dir="ltr">
@@ -340,6 +341,12 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
 
 ${ commonHeaderFooterComponents.footer(messages) }
 
+## This includes common knockout templates that are shared with the Job Browser page and the mini job browser panel
+## available in the upper right corner throughout Hue
+%if 'jobbrowser' in apps:
+${ jbCommon.include() }
+%endif
+
 <div class="monospace-preload" style="opacity: 0; height: 0; width: 0;">
   ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') }
   <b>Query. Explore. Repeat.</b>

+ 3038 - 0
desktop/core/src/desktop/templates/job_browser_common.mako

@@ -0,0 +1,3038 @@
+## Licensed to Cloudera, Inc. under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  Cloudera, Inc. 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.
+
+<%!
+  import sys
+
+  from impala.conf import COORDINATOR_URL as IMPALA_COORDINATOR_URL
+  from jobbrowser.conf import DISABLE_KILLING_JOBS
+
+  if sys.version_info[0] > 2:
+    from django.utils.translation import gettext as _
+  else:
+    from django.utils.translation import ugettext as _
+%>
+
+<%def name="include()">
+<script type="text/html" id="jb-apps-list">
+  <table data-bind="attr: {id: tableId}"
+         class="datatables table table-condensed status-border-container hue-jobs-table">
+    <thead>
+    <tr>
+      <th width="1%" class="vertical-align-middle">
+        <div class="select-all hue-checkbox fa"
+             data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div>
+      </th>
+      <th width="20%">${_('Name')}</th>
+      <th width="6%">${_('User')}</th>
+      <th width="6%">${_('Type')}</th>
+      <th width="5%">${_('Status')}</th>
+      <th width="3%">${_('Progress')}</th>
+      <th width="5%">${_('Group')}</th>
+      <th width="10%" data-bind="text: $root.interface() != 'schedules' ? '${_('Started')}' : '${_('Modified')}'"></th>
+      <th width="6%">${_('Duration')}</th>
+      <th width="15%">${_('Id')}</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: apps">
+    <tr class="status-border pointer" data-bind="
+      css: {
+        'completed': apiStatus() == 'SUCCEEDED',
+        'info': apiStatus() == 'PAUSED',
+        'running': apiStatus() == 'RUNNING',
+        'failed': apiStatus() == 'FAILED'
+      },
+      click: function (data, event) {
+        onTableRowClick(event, id());
+      }
+    ">
+      <td data-bind="click: function() {}, clickBubble: false">
+        <div class="hue-checkbox fa" data-bind="
+          click: function() {},
+          clickBubble: false,
+          multiCheck: '#' + $parent.tableId,
+          value: $data,
+          hueChecked: $parent.selectedJobs
+        "></div>
+      </td>
+      <td data-bind="text: name"></td>
+      <td data-bind="text: user"></td>
+      <td data-bind="text: type"></td>
+      <td><span class="label job-status-label" data-bind="text: status"></span></td>
+      <!-- ko if: progress() !== '' -->
+      <td data-bind="text: $root.formatProgress(progress)"></td>
+      <!-- /ko -->
+      <!-- ko if: progress() === '' -->
+      <td data-bind="text: ''"></td>
+      <!-- /ko -->
+      <td data-bind="text: queue"></td>
+      <td data-bind="moment: {data: submitted, format: 'LLL'}"></td>
+      <td data-bind="text: duration() ? duration().toHHMMSS() : ''"></td>
+      <td data-bind="text: id"></td>
+    </tr>
+    </tbody>
+  </table>
+</script>
+
+<script type="text/html" id="jb-create-cluster-content">
+  <form>
+    <fieldset>
+      <label for="clusterCreateName">${ _('Name') }</label>
+      <input id="clusterCreateName" type="text" placeholder="${ _('Name') }"
+             data-bind="clearable: jobs.createClusterName, valueUpdate: 'afterkeydown'">
+
+      <!-- ko if: $root.interface() == 'dataware2-clusters' -->
+      <label for="clusterCreateWorkers">${ _('Workers') }</label>
+      <input id="clusterCreateWorkers" type="number" min="1"
+             data-bind="value: jobs.createClusterWorkers, valueUpdate: 'afterkeydown'" class="input-mini"
+             placeholder="${_('Size')}">
+      <label class="checkbox" style="float: right;">
+        <input type="checkbox" data-bind="checked: jobs.createClusterAutoPause"> ${ _('Auto pause') }
+      </label>
+      <label class="checkbox" style="margin-right: 10px; float: right;">
+        <input type="checkbox" data-bind="checked: jobs.createClusterAutoResize"> ${ _('Auto resize') }
+      </label>
+      <!-- /ko -->
+      <!-- ko if: $root.cluster() && $root.cluster()['type'] == 'altus-engines' -->
+      <label for="clusterCreateSize">${ _('Size') }</label>
+      <select id="clusterCreateSize" class="input-small" data-bind="visible: !jobs.createClusterShowWorkers()">
+        <option>${ _('X-Large') }</option>
+        <option>${ _('Large') }</option>
+        <option>${ _('Medium') }</option>
+        <option>${ _('Small') }</option>
+        <option>${ _('X-Small') }</option>
+      </select>
+      <label for="clusterCreateEnvironment">${ _('Environment') }</label>
+      <select id="clusterCreateEnvironment">
+        <option>AWS-finance-secure</option>
+        <option>GCE-east</option>
+        <option>OpenShift-prem</option>
+      </select>
+      <!-- /ko -->
+    </fieldset>
+  </form>
+  <div style="width: 100%; text-align: right;">
+    <button class="btn close-template-popover" title="${ _('Cancel') }">${ _('Cancel') }</button>
+    <button class="btn btn-primary close-template-popover" data-bind="
+      click: jobs.createCluster,
+      enable: jobs.createClusterName().length > 0 && jobs.createClusterWorkers() > 0
+    " title="${ _('Start creation') }">
+      ${ _('Create') }
+    </button>
+  </div>
+</script>
+
+<script type="text/html" id="jb-configure-cluster-content">
+  <form>
+    <fieldset>
+      <label for="clusterConfigureWorkers">${ _('Workers') }</label>
+      <span data-bind="visible: !updateClusterAutoResize()">
+        <input id="clusterConfigureWorkers" type="number" min="1"
+               data-bind="value: updateClusterWorkers, valueUpdate: 'afterkeydown'" class="input-mini"
+               placeholder="${_('Size')}">
+      </span>
+      <span data-bind="visible: updateClusterAutoResize()">
+        <input type="number" min="0" data-bind="value: updateClusterAutoResizeMin, valueUpdate: 'afterkeydown'"
+               class="input-mini" placeholder="${_('Min')}">
+        <input type="number" min="0" data-bind="value: updateClusterAutoResizeMax, valueUpdate: 'afterkeydown'"
+               class="input-mini" placeholder="${_('Max')}">
+        <input type="number" min="0" data-bind="value: updateClusterAutoResizeCpu, valueUpdate: 'afterkeydown'"
+               class="input-mini" placeholder="${_('CPU')}">
+      </span>
+
+      <label class="checkbox" style="margin-right: 10px; float: right;">
+        <input type="checkbox" data-bind="checked: updateClusterAutoResize"> ${ _('Auto resize') }
+      </label>
+    </fieldset>
+  </form>
+  <div style="width: 100%; text-align: right;">
+    <button class="btn close-template-popover" title="${ _('Cancel') }">${ _('Cancel') }</button>
+    <button class="btn btn-primary close-template-popover"
+            data-bind="click: updateCluster, enable: clusterConfigModified" title="${ _('Update') }">
+      ${ _('Update') }
+    </button>
+  </div>
+</script>
+
+<script type="text/html" id="jb-hive-queries-template">
+  <queries-list></queries-list>
+</script>
+
+<script type="text/html" id="jb-impala-queries-template">
+  <impala-queries></impala-queries>
+</script>
+
+<script type="text/html" id="jb-breadcrumbs-icons">
+  <!-- ko switch: type -->
+  <!-- ko case: 'workflow' -->
+  <img src="${ static('oozie/art/icon_oozie_workflow_48.png') }" class="app-icon" alt="${ _('Oozie workflow icon') }" />
+  <!-- /ko -->
+  <!-- ko case: 'workflow-action' -->
+  <i class="fa fa-fw fa-code-fork"></i>
+  <!-- /ko -->
+  <!-- ko case: 'schedule' -->
+  <img src="${ static('oozie/art/icon_oozie_coordinator_48.png') }" class="app-icon"
+       alt="${ _('Oozie coordinator icon') }" />
+  <!-- /ko -->
+  <!-- ko case: 'bundle' -->
+  <img src="${ static('oozie/art/icon_oozie_bundle_48.png') }" class="app-icon" alt="${ _('Oozie bundle icon') }" />
+  <!-- /ko -->
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-breadcrumbs">
+  <h3 class="jb-breadcrumbs">
+    <ul class="inline hue-breadcrumbs-bar">
+      <!-- ko foreach: breadcrumbs -->
+      <li>
+        <!-- ko if: $index() > 1 -->
+        <span class="divider">&gt;</span>
+        <!-- /ko -->
+
+        <!-- ko if: $index() != 0 -->
+        <!-- ko if: $index() != $parent.breadcrumbs().length - 1 -->
+        <a href="javascript:void(0)" data-bind="
+          click: function() {
+            $parent.breadcrumbs.splice($index());
+            $root.job().id(id);
+            $root.job().fetchJob();
+          }
+        ">
+          <span data-bind="template: 'jb-breadcrumbs-icons'"></span>
+          <span data-bind="text: name"></span></a>
+        <!-- /ko -->
+        <!-- ko if: $index() == $parent.breadcrumbs().length - 1 -->
+        <span data-bind="template: 'jb-breadcrumbs-icons'"></span>
+        <span data-bind="text: name, attr: { title: id }"></span>
+        <!-- /ko -->
+        <!-- /ko -->
+      </li>
+      <!-- /ko -->
+
+      <!-- ko ifnot: $root.isMini() -->
+      <!-- ko if: ['workflows', 'schedules', 'bundles', 'slas'].indexOf(interface()) > -1 -->
+      <li class="pull-right">
+        <a href="javascript:void(0)" data-bind="
+          click: function() {
+            $root.selectInterface('oozie-info')
+          }
+        ">${ _('Configuration') }</a>
+      </li>
+      <!-- /ko -->
+      <!-- /ko -->
+    </ul>
+  </h3>
+</script>
+
+<script type="text/html" id="jb-pagination">
+  <!-- ko ifnot: hasPagination -->
+  <div class="inline">
+    <span data-bind="text: totalApps()"></span>
+    <!-- ko if: $root.interface() === 'dataware2-clusters' -->
+    ${ _('warehouses') }
+    <!-- /ko -->
+    <!-- ko if: $root.interface() !== 'dataware2-clusters' -->
+    ${ _('jobs') }
+    <!-- /ko -->
+  </div>
+  <!-- /ko -->
+
+  <!-- ko if: hasPagination -->
+  <div class="inline">
+    <div class="inline">
+      ${ _('Showing') }
+      <span data-bind="text: Math.min(paginationOffset(), totalApps())"></span>
+      ${ _('to')}
+      <span data-bind="text: Math.min(paginationOffset() - 1 + paginationResultPage(), totalApps())"></span>
+      ${ _('of') }
+      <span data-bind="text: totalApps"></span>
+    </div>
+
+    <ul class="inline">
+      <li class="previous-page" data-bind="visible: showPreviousPage">
+        <a href="javascript:void(0);" data-bind="click: previousPage" title="${_('Previous page')}"><i
+          class="fa fa-backward"></i></a>
+      </li>
+      <li class="next-page" data-bind="visible: showNextPage">
+        <a href="javascript:void(0);" data-bind="click: nextPage" title="${_('Next page')}"><i
+          class="fa fa-forward"></i></a>
+      </li>
+    </ul>
+  </div>
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-job-page">
+  <!-- ko if: type() == 'MAPREDUCE' -->
+  <div data-bind="template: { name: 'jb-job-mapreduce-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'MAP' || type() == 'REDUCE' -->
+  <div data-bind="template: { name: 'jb-job-mapreduce-task-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'MAP_ATTEMPT' || type() == 'REDUCE_ATTEMPT' -->
+  <div data-bind="template: { name: 'jb-job-mapreduce-task-attempt-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'YARN' -->
+  <div data-bind="template: { name: 'jb-job-yarn-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'YarnV2' -->
+  <div data-bind="template: { name: 'jb-job-yarnv2-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'YarnV2_ATTEMPT' -->
+  <div data-bind="template: { name: 'jb-job-yarnv2-attempt-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'SPARK' -->
+  <div data-bind="template: { name: 'jb-job-spark-page', data: $root.job() }"></div>
+  <!-- /ko -->
+
+  <!-- ko if: type() == 'SPARK_EXECUTOR' -->
+  <div data-bind="template: { name: 'jb-job-spark-executor-page', data: $root.job() }"></div>
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-history-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
+              <span data-bind="text: name"></span>
+            </a>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-danger': apiStatus() === 'FAILED',
+                'progress-warning': apiStatus() === 'RUNNING',
+                'progress-success': apiStatus() === 'SUCCEEDED'
+              }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+
+      <ul class="nav nav-pills margin-top-20">
+        <li>
+          <a data-bind="
+            attr: {
+              href: $root.contextId('#history-page-statements')
+            },
+            click: function() {
+              fetchProfile('properties');
+              $root.showTab('#history-page-statements');
+            }
+          ">${ _('Properties') }</a>
+        </li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('history-page-statements')
+          }
+        ">
+          <table id="actionsTable" class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Id')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Output')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['statements']">
+            <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
+              <td>
+                <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
+                  <i class="fa fa-tasks"></i>
+                </a>
+              </td>
+              <td data-bind="text: state"></td>
+              <td data-bind="text: output"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-yarn-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                'progress-danger': apiStatus() === 'FAILED'
+              }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="text: submitted"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <div class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-mapreduce-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini()}">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li><span data-bind="text: id"></span></li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li data-bind="visible: id() != name()" class="nav-header">${ _('Name') }</li>
+          <li data-bind="visible: id() != name(), attr: {title: name}"><span
+            data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                'progress-danger': apiStatus() === 'FAILED'
+              },
+              attr: { title: status }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('Map') }</li>
+          <li><span data-bind="text: maps_percent_complete"></span>% <span data-bind="text: finishedMaps"></span> /<span
+            data-bind="text: desiredMaps"></span></li>
+          <li class="nav-header">${ _('Reduce') }</li>
+          <li><span data-bind="text: reduces_percent_complete"></span>% <span data-bind="text: finishedReduces"></span>
+            / <span data-bind="text: desiredReduces"></span></li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: durationFormatted"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="text: startTimeFormatted"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="jb-logs-link" data-toggle="tab" data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-page-logs')
+          }
+        ">${ _('Logs') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-page-tasks')
+          },
+          click: function() {
+            fetchProfile('tasks');
+            $root.showTab('#job-mapreduce-page-tasks');
+          }
+        ">${ _('Tasks') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-page-metadata')
+          },
+          click: function() {
+            fetchProfile('metadata');
+            $root.showTab('#job-mapreduce-page-metadata');
+          }
+        ">${ _('Metadata') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-page-counters')
+          },
+          click: function() {
+            fetchProfile('counters');
+            $root.showTab('#job-mapreduce-page-counters');
+          }
+        ">${ _('Counters') }</a></li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-page-logs')
+          }
+        ">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('default');
+                logActive('default');
+              }
+            ">default</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stdout');
+                logActive('stdout');
+              }
+            ">stdout</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stderr');
+                logActive('stderr');
+              }
+            ">stderr</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('syslog');
+                logActive('syslog');
+              }
+            ">syslog</a></li>
+          </ul>
+          <!-- ko if: properties.diagnostics() -->
+          <pre data-bind="text: properties.diagnostics"></pre>
+          <!-- /ko -->
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-page-tasks')
+          }
+        ">
+          <form class="form-inline">
+            <input data-bind="textFilter: textFilter, clearable: {value: textFilter}, valueUpdate: 'afterkeydown'"
+                   type="text" class="input-xlarge search-query" placeholder="${_('Filter by name')}">
+
+            <span data-bind="foreach: statesValuesFilter">
+              <label class="checkbox">
+                <div class="pull-left margin-left-5 status-border status-content"
+                     data-bind="css: value, hueCheckbox: checked"></div>
+                <div class="inline-block" data-bind="text: name, toggle: checked"></div>
+              </label>
+            </span>
+
+            <span data-bind="foreach: typesValuesFilter" class="margin-left-30">
+              <label class="checkbox">
+                <div class="pull-left margin-left-5" data-bind="css: value, hueCheckbox: checked"></div>
+                <div class="inline-block" data-bind="text: name, toggle: checked"></div>
+              </label>
+            </span>
+          </form>
+
+          <table class="table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Type')}</th>
+              <th>${_('Id')}</th>
+              <th>${_('Elapsed Time')}</th>
+              <th>${_('Progress')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Start Time')}</th>
+              <th>${_('Successful Attempt')}</th>
+              <th>${_('Finish Time')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['tasks']()['task_list']">
+            <tr data-bind="
+              click: function() {
+                $root.job().id(id);
+                $root.job().fetchJob();
+              },
+              css: {
+                'completed': apiStatus == 'SUCCEEDED',
+                'running': apiStatus == 'RUNNING',
+                'failed': apiStatus == 'FAILED'
+              }
+            " class="status-border pointer">
+              <td data-bind="text: type"></td>
+              <td data-bind="text: id"></td>
+              <td data-bind="text: elapsedTime ? elapsedTime.toHHMMSS() : ''"></td>
+              <td data-bind="text: progress"></td>
+              <td><span class="label job-status-label" data-bind="text: state"></span></td>
+              <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
+              <td data-bind="text: successfulAttempt"></td>
+              <td data-bind="moment: {data: finishTime, format: 'LLL'}"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-page-metadata')
+          }
+        ">
+          <div data-bind="template: { name: 'jb-render-metadata', data: properties['metadata'] }"></div>
+        </div>
+
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-page-counters')
+          }
+        ">
+          <div data-bind="template: { name: 'jb-render-page-counters', data: properties['counters'] }"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-mapreduce-task-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                'progress-danger': apiStatus() === 'FAILED'
+              },
+              attr: { title: status }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('State') }</li>
+          <li><span data-bind="text: state"></span></li>
+          <li class="nav-header">${ _('Start time') }</li>
+          <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Successful attempt') }</li>
+          <li><span data-bind="text: successfulAttempt"></span></li>
+          <li class="nav-header">${ _('Finish time') }</li>
+          <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Elapsed time') }</li>
+          <li><span data-bind="text: elapsedTime() ? elapsedTime().toHHMMSS() : ''"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="jb-logs-link" data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-task-page-logs')
+          }
+        " data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-task-page-attempts')
+          },
+          click: function() {
+            fetchProfile('attempts');
+            $root.showTab('#job-mapreduce-task-page-attempts');
+          }
+        ">${ _('Attempts') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-task-page-counters')
+          },
+          click: function() {
+            fetchProfile('counters');
+            $root.showTab('#job-mapreduce-task-page-counters');
+          }
+        ">${ _('Counters') }</a></li>
+      </ul>
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-task-page-logs')
+          }
+        ">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stdout');
+                logActive('stdout');
+              }
+            ">stdout</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stderr');
+                logActive('stderr');
+              }
+            ">stderr</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('syslog');
+                logActive('syslog');
+              }
+            ">syslog</a></li>
+          </ul>
+
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-task-page-attempts')
+          }
+        ">
+          <table class="table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Assigned Container Id')}</th>
+              <th>${_('Progress')}</th>
+              <th>${_('Elapsed Time')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Rack')}</th>
+              <th>${_('Node Http Address')}</th>
+              <th>${_('Type')}</th>
+              <th>${_('Start Time')}</th>
+              <th>${_('Id')}</th>
+              <th>${_('Finish Time')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['attempts']()['task_list']">
+            <tr class="pointer" data-bind="click: function() { $root.job().id(id); $root.job().fetchJob(); }">
+              <td data-bind="text: assignedContainerId"></td>
+              <td data-bind="text: progress"></td>
+              <td data-bind="text: elapsedTime ? elapsedTime.toHHMMSS() : ''"></td>
+              <td data-bind="text: state"></td>
+              <td data-bind="text: rack"></td>
+              <td data-bind="text: nodeHttpAddress"></td>
+              <td data-bind="text: type"></td>
+              <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
+              <td data-bind="text: id"></td>
+              <td data-bind="moment: {data: finishTime, format: 'LLL'}"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-task-page-counters')
+          }
+        ">
+          <div data-bind="template: { name: 'jb-render-task-counters', data: properties['counters'] }"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-yarnv2-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: applicationType"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() == 'RUNNING',
+                'progress-success': apiStatus() == 'SUCCEEDED',
+                'progress-danger': apiStatus() == 'FAILED'
+              },
+              attr: { title: status }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('State') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('Start time') }</li>
+          <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Finish time') }</li>
+          <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Elapsed time') }</li>
+          <li><span data-bind="text: elapsedTime() ? elapsedTime().toHHMMSS() : ''"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="jb-logs-link" data-bind="
+          attr: {
+            href: $root.contextId('#job-yarnv2-page-logs')
+          }
+        " data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-yarnv2-page-attempts')
+          },
+          click: function() {
+            fetchProfile('attempts');
+            $root.showTab('#job-yarnv2-page-attempts');
+          }
+        ">${ _('Attempts') }</a></li>
+      </ul>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('job-yarnv2-page-logs')
+          }
+        ">
+          <ul class="nav nav-tabs scrollable" data-bind="foreach: logsList">
+            <li data-bind="
+              css: {
+                'active': $data == $parent.logActive()
+              }
+            "><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $parent.fetchLogs($data);
+                $parent.logActive($data);
+              },
+              text: $data
+            "></a></li>
+          </ul>
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('job-yarnv2-page-attempts') }">
+          <table class="table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Assigned Container Id')}</th>
+              <th>${_('Node Id')}</th>
+              <th>${_('Application Attempt Id')}</th>
+              <th>${_('Start Time')}</th>
+              <th>${_('Finish Time')}</th>
+              <th>${_('Node Http Address')}</th>
+              <th>${_('Blacklisted Nodes')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['attempts']()['task_list']">
+            <tr class="pointer" data-bind="click: function() { $root.job().id(appAttemptId); $root.job().fetchJob(); }">
+              <td data-bind="text: containerId"></td>
+              <td data-bind="text: nodeId"></td>
+              <td data-bind="text: id"></td>
+              <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
+              <td data-bind="moment: {data: finishedTime, format: 'LLL'}"></td>
+              <td data-bind="text: nodeHttpAddress"></td>
+              <td data-bind="text: blacklistedNodes"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-yarnv2-attempt-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('Attempt Id') }</li>
+          <li class="break-word"><span data-bind="text: appAttemptId"></span></li>
+          <li class="nav-header">${ _('State') }</li>
+          <li><span data-bind="text: state"></span></li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Start time') }</li>
+          <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Node Http Address') }</li>
+          <li><span data-bind="text: nodeHttpAddress"></span></li>
+          <li class="nav-header">${ _('Elapsed time') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-mapreduce-task-attempt-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() == 'RUNNING',
+                'progress-success': apiStatus() == 'SUCCEEDED',
+                'progress-danger': apiStatus() == 'FAILED'
+              },
+              attr: { title: status }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('State') }</li>
+          <li><span data-bind="text: state"></span></li>
+          <li class="nav-header">${ _('Assigned Container ID') }</li>
+          <li><span data-bind="text: assignedContainerId"></span></li>
+          <li class="nav-header">${ _('Rack') }</li>
+          <li><span data-bind="text: rack"></span></li>
+          <li class="nav-header">${ _('Node HTTP address') }</li>
+          <li><span data-bind="text: nodeHttpAddress"></span></li>
+          <li class="nav-header">${ _('Start time') }</li>
+          <li><span data-bind="moment: {data: startTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Finish time') }</li>
+          <li><span data-bind="moment: {data: finishTime, format: 'LLL'}"></span></li>
+          <li class="nav-header">${ _('Elapsed time') }</li>
+          <li><span data-bind="text: elapsedTime() ? elapsedTime().toHHMMSS() : ''"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="jb-logs-link" data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-task-attempt-page-logs')
+          }
+        " data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-mapreduce-task-attempt-page-counters')
+          },
+          click: function() {
+            fetchProfile('counters');
+            $root.showTab('#job-mapreduce-task-attempt-page-counters');
+          }
+        ">${ _('Counters') }</a></li>
+      </ul>
+
+      <div class="tab-content">
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('job-mapreduce-task-attempt-page-logs') }">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stdout');
+                logActive('stdout');
+              }
+            ">stdout</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stderr');
+                logActive('stderr');
+              }">stderr</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('syslog');
+                logActive('syslog');
+              }">syslog</a></li>
+          </ul>
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-mapreduce-task-attempt-page-counters')
+          }
+        ">
+          <div data-bind="
+            template: {
+              name: 'jb-render-attempt-counters',
+              data: properties['counters']
+            }
+          "></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-spark-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                'progress-danger': apiStatus() === 'FAILED'
+              }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="text: submitted"></span></li>
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="job-spark-logs-link" data-bind="
+          attr: {
+            href: $root.contextId('#job-spark-page-logs')
+          }
+        " data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+          attr: {
+            href: $root.contextId('#job-spark-page-executors')
+          },
+          click: function() {
+            fetchProfile('executors');
+            $root.showTab('#job-spark-page-executors');
+          }
+        ">${ _('Executors') }</a></li>
+        <li><a data-bind="attr: { href: $root.contextId('#job-spark-page-properties') }"
+               data-toggle="tab">${ _('Properties') }</a></li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('job-spark-page-logs')
+          }
+        ">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stdout');
+                logActive('stdout');
+              }
+            ">stdout</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stderr');
+                logActive('stderr');
+              }
+            ">stderr</a></li>
+          </ul>
+
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-spark-page-executors')
+          }
+        ">
+          <form class="form-inline">
+            <input data-bind="
+              textFilter: textFilter,
+              clearable: { value: textFilter },
+              valueUpdate: 'afterkeydown'
+            " type="text" class="input-xlarge search-query" placeholder="${_('Filter by name')}">
+          </form>
+
+          <table class="table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Executor Id')}</th>
+              <th>${_('Address')}</th>
+              <th>${_('RDD Blocks')}</th>
+              <th>${_('Storage Memory')}</th>
+              <th>${_('Disk Used')}</th>
+              <th>${_('Active Tasks')}</th>
+              <th>${_('Failed Tasks')}</th>
+              <th>${_('Complete Tasks')}</th>
+              <th>${_('Task Time')}</th>
+              <th>${_('Input')}</th>
+              <th>${_('Shuffle Read')}</th>
+              <th>${_('Shuffle Write')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['executors']()['executor_list']">
+            <tr data-bind="click: function() { $root.job().id(id); $root.job().fetchJob(); }"
+                class="status-border pointer">
+              <td data-bind="text: executor_id"></td>
+              <td data-bind="text: address"></td>
+              <td data-bind="text: rdd_blocks"></td>
+              <td data-bind="text: storage_memory"></td>
+              <td data-bind="text: disk_used"></td>
+              <td data-bind="text: active_tasks"></td>
+              <td data-bind="text: failed_tasks"></td>
+              <td data-bind="text: complete_tasks"></td>
+              <td data-bind="text: task_time"></td>
+              <td data-bind="text: input"></td>
+              <td data-bind="text: shuffle_read"></td>
+              <td data-bind="text: shuffle_write"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+        <div class="tab-pane" data-bind="
+          attr: {
+            id: $root.contextId('job-spark-page-properties')
+          }
+        ">
+          <table class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Name')}</th>
+              <th>${_('Value')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['metadata']">
+            <tr>
+              <td data-bind="text: name"></td>
+              <td><!-- ko template: { name: 'jb-link-or-text', data: { name: name, value: value } } --><!-- /ko --></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-spark-executor-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: executor_id"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <!-- ko ifnot: $root.isMini() -->
+          <!-- ko with: properties -->
+          <li class="nav-header">${ _('Address') }</li>
+          <li><span data-bind="text: address"></span></li>
+          <li class="nav-header">${ _('RDD Blocks') }</li>
+          <li><span data-bind="text: rdd_blocks"></span></li>
+          <li class="nav-header">${ _('Storage Memory') }</li>
+          <li><span data-bind="text: storage_memory"></span></li>
+          <li class="nav-header">${ _('Disk Used') }</li>
+          <li><span data-bind="text: disk_used"></span></li>
+          <li class="nav-header">${ _('Active Tasks') }</li>
+          <li><span data-bind="text: active_tasks"></span></li>
+          <li class="nav-header">${ _('Failed Tasks') }</li>
+          <li><span data-bind="text: failed_tasks"></span></li>
+          <li class="nav-header">${ _('Complet Tasks') }</li>
+          <li><span data-bind="text: complete_tasks"></span></li>
+          <li class="nav-header">${ _('Input') }</li>
+          <li><span data-bind="text: input"></span></li>
+          <li class="nav-header">${ _('Shuffle Read') }</li>
+          <li><span data-bind="text: shuffle_read"></span></li>
+          <li class="nav-header">${ _('Shuffle Write') }</li>
+          <li><span data-bind="text: shuffle_write"></span></li>
+          <!-- /ko -->
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12': $root.isMini() }">
+
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a class="jb-logs-link" data-bind="
+          attr: {
+            href: $root.contextId('#job-spark-executor-page-logs')
+          }
+        " data-toggle="tab">${ _('Logs') }</a></li>
+      </ul>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="
+          attr: {
+            id: $root.contextId('job-spark-executor-page-logs')
+          }
+        ">
+          <ul class="nav nav-tabs">
+            <li class="active"><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stdout');
+                logActive('stdout');
+              }
+            ">stdout</a></li>
+            <li><a href="javascript:void(0)" data-bind="
+              click: function(data, e) {
+                $(e.currentTarget).parent().siblings().removeClass('active');
+                $(e.currentTarget).parent().addClass('active');
+                fetchLogs('stderr');
+                logActive('stderr');
+              }
+            ">stderr</a></li>
+          </ul>
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-dataeng-job-page">
+  <button class="btn" title="${ _('Troubleshoot') }" data-bind="click: troubleshoot">
+    <i class="fa fa-tachometer"></i> ${ _('Troubleshoot') }
+  </button>
+
+  <!-- ko if: type() == 'dataeng-job-HIVE' -->
+  <div data-bind="template: { name: 'jb-dataeng-job-hive-page', data: $root.job() }"></div>
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-dataware-clusters-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: properties['properties']['cdhVersion']"></span></li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                'progress-danger': apiStatus() === 'FAILED'
+              }
+            ">
+              <div class="bar" data-bind="style: {'width': '100%'}"></div>
+            </div>
+          </li>
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <div class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></div>
+    </div>
+  </div>
+
+  <br>
+
+  <button class="btn" title="${ _('Troubleshoot') }" data-bind="click: troubleshoot">
+    <i class="fa fa-tachometer"></i> ${ _('Troubleshoot') }
+  </button>
+</script>
+
+<script type="text/html" id="jb-dataware2-clusters-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('Workers Online') }</li>
+          <li>
+            <span data-bind="text: properties['properties']['workerReplicasOnline']"></span>
+            /
+            <span data-bind="text: properties['properties']['workerReplicas']"></span>
+            <!-- ko if: properties['properties']['workerAutoResize'] -->
+            - ${ _('CPU') } <span
+            data-bind="text: properties['properties']['workercurrentCPUUtilizationPercentage']"></span>%
+            <!-- /ko -->
+            <!-- ko if: status() == 'SCALING_UP' || status() == 'SCALING_DOWN' -->
+            <i class="fa fa-spinner fa-spin fa-fw"></i>
+            <!-- /ko -->
+          </li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-warning': status() == 'SCALING_UP' || status() == 'SCALING_DOWN',
+                'progress-success': status() == 'ONLINE',
+                'progress-danger': apiStatus() === 'FAILED'
+              }
+            ">
+              <div class="bar" data-bind="
+                style: {
+                  'width': Math.min(
+                    properties['properties']['workerReplicas'](),
+                    properties['properties']['workerReplicasOnline']()
+                  ) / Math.max(
+                    properties['properties']['workerReplicasOnline'](),
+                    properties['properties']['workerReplicas']()
+                  ) * 100 + '%'
+                }
+              "></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Auto resize') }</li>
+          <li>
+            <i data-bind="visible: !properties['properties']['workerAutoResize']()" class="fa fa-square-o fa-fw"></i>
+            <span data-bind="visible: properties['properties']['workerAutoResize']">
+              <i class="fa fa-check-square-o fa-fw"></i>
+              <span data-bind="text: properties['properties']['workerAutoResizeMin']"></span> -
+              <span data-bind="text: properties['properties']['workerAutoResizeMax']"></span>
+              ${ _('CPU:') } <span data-bind="text: properties['properties']['workerAutoResizeCpu']"></span>%
+            </span>
+          </li>
+          <li class="nav-header">${ _('Auto pause') }</li>
+          <li><i class="fa fa-square-o fa-fw"></i></li>
+          <li class="nav-header">${ _('Impalad') }</li>
+          <li>
+            <a href="#"
+               data-bind="attr: { 'href': properties['properties']['coordinatorEndpoint']['publicHost']() + ':25000' }">
+              <span data-bind="text: properties['properties']['coordinatorEndpoint']['publicHost']"></span>
+              <i class="fa fa-external-link fa-fw"></i>
+            </a>
+          </li>
+          <li class="nav-header">${ _('Id') }</li>
+          <li><span class="break-word" data-bind="text: id"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }" style="position: relative;">
+      <div style="position: absolute; top: 0; right: 0">
+        <button class="btn" title="Create cluster" data-bind="
+          enable: isRunning(),
+          visible: $root.interface() == 'dataware2-clusters',
+          templatePopover: {
+            placement: 'bottom',
+            contentTemplate: 'jb-configure-cluster-content',
+            minWidth: '320px',
+            trigger: 'click'
+          },
+          click: updateClusterShow
+        ">${ _('Configure') } <i class="fa fa-chevron-down"></i></button>
+
+        <a class="btn" title="${ _('Pause') }">
+          <i class="fa fa-pause"></i>
+        </a>
+
+        <a class="btn" title="${ _('Refresh') }" data-bind="click: function() { fetchJob(); }">
+          <i class="fa fa-refresh"></i>
+        </a>
+      </div>
+
+      <div class="acl-panel-content">
+        <ul class="nav nav-tabs">
+          <li class="active"><a href="#servicesLoad" data-toggle="tab">${ _("Load") }</a></li>
+          <li><a href="#servicesPrivileges" data-toggle="tab">${ _("Privileges") }</a></li>
+          <li><a href="#servicesTroubleshooting" data-toggle="tab">${ _("Troubleshooting") }</a></li>
+        </ul>
+
+        <div class="tab-content">
+          <div class="tab-pane active" id="servicesLoad">
+            <div class="wxm-poc" style="clear: both;">
+              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
+                ${ _("Metrics are not setup") }
+              </div>
+            </div>
+          </div>
+          <div class="tab-pane" id="servicesPrivileges">
+            <div class="acl-block-title">
+              <i class="fa fa-cube muted"></i> <a class="pointer"><span>admin</span></a>
+            </div>
+            <div>
+              <div class="acl-block acl-block-airy">
+                <span class="muted" title="3 months ago">CLUSTER</span>
+                <span>
+                  <a class="muted" style="margin-left: 4px" title="Open in Sentry" href="/security/hive"><i
+                    class="fa fa-external-link"></i></a>
+                </span>
+                <br>
+                server=<span>server1</span>
+                <span>
+                  <i class="fa fa-long-arrow-right"></i> db=<a class="pointer" title="Browse db privileges">gke_gcp-eng-dsdw_us-west2-b_impala-demo</a>
+                </span>
+                <i class="fa fa-long-arrow-right"></i> action=ALL
+              </div>
+            </div>
+            <div class="acl-block-title">
+              <i class="fa fa-cube muted"></i> <a class="pointer"><span>eng</span></a>
+            </div>
+            <div>
+              <div class="acl-block acl-block-airy">
+                <span class="muted" title="3 months ago">CLUSTER</span>
+                <span>
+                  <a class="muted" style="margin-left: 4px" title="Open in Sentry" href="/security/hive"><i
+                    class="fa fa-external-link"></i></a>
+                </span>
+                <br>
+                server=server1
+                <span>
+                  <i class="fa fa-long-arrow-right"></i> db=<a class="pointer" title="Browse db privileges">gke_gcp-eng-dsdw_us-west2-b_impala-demo</a>
+                </span>
+                <i class="fa fa-long-arrow-right"></i> action=<span>ACCESS</span>
+              </div>
+            </div>
+            <div class="acl-block acl-actions">
+              <span class="pointer" title="Show 50 more..." style="display: none;"><i
+                class="fa fa-ellipsis-h"></i></span>
+              <span class="pointer" title="Add privilege"><i class="fa fa-plus"></i></span>
+              <span class="pointer" title="Undo" style="display: none;"> &nbsp; <i class="fa fa-undo"></i></span>
+              <span class="pointer" title="Save" style="display: none;"> &nbsp; <i class="fa fa-save"></i></span>
+            </div>
+          </div>
+          <div class="tab-pane" id="servicesTroubleshooting">
+            <div class="wxm-poc" style="clear: both;">
+              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
+                <h4>Outliers</h4>
+                <img src="${ static('desktop/art/wxm_fake/outliers.svg') }" style="height: 440px" />
+              </div>
+              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
+                <h4>Statement Types</h4>
+                <img src="${ static('desktop/art/wxm_fake/statement_types.svg') }" style="height: 440px" />
+              </div>
+              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
+                <h4>Duration</h4>
+                <img src="${ static('desktop/art/wxm_fake/duration.svg') }" style="height: 440px" />
+              </div>
+              <div style="float:left; margin-right: 10px; margin-bottom: 10px;">
+                <h4>Memory Utilization</h4>
+                <img src="${ static('desktop/art/wxm_fake/memory.svg') }" style="height: 440px" />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-dataeng-job-hive-page">
+  ${ _('Id') } <span data-bind="text: id"></span>
+  ${ _('Name') } <span data-bind="text: name"></span>
+  ${ _('Type') } <span data-bind="text: type"></span>
+  ${ _('Status') } <span data-bind="text: status"></span>
+  ${ _('User') } <span data-bind="text: user"></span>
+  ${ _('Progress') } <span data-bind="text: progress"></span>
+  ${ _('Duration') } <span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span>
+  ${ _('Submitted') } <span data-bind="text: submitted"></span>
+  <br>
+  <span data-bind="text: ko.mapping.toJSON(properties['properties'])"></span>
+</script>
+
+<script type="text/html" id="jb-queries-page">
+  <div class="row-fluid" data-jobType="queries">
+    <!-- ko if: id() -->
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Id') }</li>
+          <li>
+            % if hasattr(IMPALA_COORDINATOR_URL, 'get') and not IMPALA_COORDINATOR_URL.get():
+            <a data-bind="attr: { href: doc_url_modified }" target="_blank" title="${ _('Open in Impalad') }">
+              <span data-bind="text: id"></span>
+            </a>
+            % else:
+            <span data-bind="text: id"></span>
+            % endif
+            <!-- ko if: $root.isMini() -->
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%; height: 4px" data-bind="
+              css: {
+                'progress-danger': apiStatus() === 'FAILED',
+                'progress-warning': apiStatus() === 'RUNNING',
+                'progress-success': apiStatus() === 'SUCCEEDED'
+              },
+              attr: { 'title': progress() + '%' }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+            <!-- /ko -->
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li>
+            <span data-bind="text: progress"></span>%
+          </li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+              css: {
+                'progress-danger': apiStatus() === 'FAILED',
+                'progress-warning': apiStatus() === 'RUNNING',
+                'progress-success': apiStatus() === 'SUCCEEDED'
+              }
+            ">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <!-- ko if: properties.plan && properties.plan().status && properties.plan().status.length > 2 -->
+          <li class="nav-header">${ _('Status Text') }</li>
+          <li><span data-bind="text: properties.plan().status"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Open Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <!-- ko if: $root.job().mainType() == 'queries-impala' -->
+        <li>
+          <a data-bind="
+            attr: {
+              href: $root.contextId('#queries-page-plan')
+            },
+            click: function() {
+              $root.showTab('#queries-page-plan');
+            },
+            event: {
+              'shown': function () {
+                if (!properties.plan || !properties.plan()) {
+                  fetchProfile('plan');
+                }
+              }
+            }
+          ">${ _('Plan') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-stmt')
+              },
+              click: function() {
+                $root.showTab('#queries-page-stmt');
+              }">${ _('Query') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-plan-text')
+              },
+              click: function() {
+                $root.showTab('#queries-page-plan-text');
+              }">${ _('Text Plan') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-summary')
+              },
+              click: function() {
+                $root.showTab('#queries-page-summary');
+              }">${ _('Summary') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-profile')
+              },
+              click: function() {
+                $root.showTab('#queries-page-profile');
+              },
+              event: {
+                'shown': function () {
+                  if (!properties.profile || !properties.profile().profile) {
+                    fetchProfile('profile');
+                  }
+                }
+              }">${ _('Profile') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-memory')
+              },
+              click: function() {
+                $root.showTab('#queries-page-memory');
+              },
+              event: {
+                'shown': function () {
+                  if (!properties.memory || !properties.memory().mem_usage) {
+                    fetchProfile('memory');
+                  }
+                }
+              }">${ _('Memory') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-backends')
+              },
+              click: function() {
+                $root.showTab('#queries-page-backends');
+              },
+              event: {
+                'shown': function () {
+                  if (!properties.backends || !properties.backends().backend_states) {
+                    fetchProfile('backends');
+                  }
+                }
+              }">${ _('Backends') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-finstances')
+              },
+              click: function() {
+                $root.showTab('#queries-page-finstances');
+              },
+              event: {
+                'shown': function () {
+                  if (!properties.finstances || !properties.finstances().backend_instances) {
+                    fetchProfile('finstances');
+                  }
+                }
+              }">${ _('Instances') }</a>
+        </li>
+        <!-- /ko -->
+        <!-- ko if: $root.job().mainType() == 'queries-hive' -->
+        <li class="active">
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-hive-plan-text')
+              },
+              click: function() {
+                $root.showTab('#queries-page-hive-plan-text');
+              }">${ _('Plan') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-hive-stmt')
+              },
+              click: function() {
+                $root.showTab('#queries-page-hive-stmt');
+              }">${ _('Query') }</a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#queries-page-hive-perf')
+              },
+              click: function() {
+                $root.showTab('#queries-page-hive-perf');
+              }">${ _('Perf') }</a>
+        </li>
+        <!-- /ko -->
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <!-- ko if: $root.job().mainType() == 'queries-impala' -->
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-plan') }"
+             data-profile="plan">
+          <div
+            data-bind="visible:properties.plan && properties.plan().plan_json && properties.plan().plan_json.plan_nodes.length">
+            <div class="query-plan" data-bind="
+               attr: { id: $root.contextId('queries-page-plan-graph') }
+               impalaDagre: { value: properties.plan && properties.plan(), height: $root.isMini() ? 535 : 600 }
+            ">
+              <svg style="width:100%;height:100%;position:relative;"
+                   data-bind="attr: { id: $root.contextId('queries-page-plan-svg') }">
+                <g></g>
+              </svg>
+            </div>
+          </div>
+          <pre data-bind="
+              visible:!properties.plan || !properties.plan().plan_json || !properties.plan().plan_json.plan_nodes.length
+            ">${ _('The selected tab has no data') }</pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-stmt') }"
+             data-profile="plan">
+          <!-- ko if: properties.plan && properties.plan().stmt -->
+          <pre data-bind="
+              highlight: {
+                value: properties.plan().stmt.trim(),
+                enableOverflow: true,
+                formatted: true,
+                dialect: 'impala'
+              }"></pre>
+          <!-- /ko -->
+          <!-- ko ifnot: properties.plan && properties.plan().stmt -->
+          <pre>
+                ${ _('The selected tab has no data') }
+              </pre>
+          <!-- /ko -->
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-plan-text') }"
+             data-profile="plan">
+          <pre data-bind="text: (properties.plan && properties.plan().plan) || _('The selected tab has no data')"></pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-summary') }"
+             data-profile="plan">
+            <pre
+              data-bind="text: (properties.plan && properties.plan().summary) || _('The selected tab has no data')"></pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-profile') }"
+             data-profile="profile">
+          <button class="btn" type="button" data-clipboard-target="#query-impala-profile" style="float: right;"
+                  data-bind="
+                visible: properties.profile && properties.profile().profile,
+                clipboard: { onSuccess: function() { $.jHueNotify.info('${ _("Profile copied to clipboard!") }'); } }">
+          <i class="fa fa-fw fa-clipboard"></i> ${ _('Clipboard') }
+          </button>
+          <button class="btn" type="button" style="float: right;" data-bind="
+                click: function(){ submitQueryProfileDownloadForm('download-profile'); },
+                visible: properties.profile && properties.profile().profile">
+            <i class="fa fa-fw fa-download"></i> ${ _('Download') }
+          </button>
+          <div id="downloadProgressModal"></div>
+          <pre id="query-impala-profile" style="float: left; margin-top: 8px" data-bind="
+                text: (properties.profile && properties.profile().profile) || _('The selected tab has no data')"></pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-memory') }"
+             data-profile="mem_usage">
+            <pre
+              data-bind="text: (properties.memory && properties.memory().mem_usage) || _('The selected tab has no data')"></pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-backends') }"
+             data-profile="backends">
+          <!-- ko if: properties.backends && properties.backends().backend_states -->
+          <div data-bind="attr: { id: $root.contextId('queries-page-memory-backends-template') }"
+               style="overflow-x: scroll;">
+            <table class="table table-condensed">
+              <thead>
+              <tr>
+                <!-- ko foreach: Object.keys(properties.backends().backend_states[0]).sort() -->
+                <th data-bind="text: $data"></th>
+                <!-- /ko -->
+              </tr>
+              </thead>
+              <tbody data-bind="
+                  foreach: {
+                    data: $data.properties.backends().backend_states,
+                    minHeight: 20,
+                    container: $root.contextId('#queries-page-memory-backends-template')
+                  }">
+              <tr>
+                <!-- ko foreach: Object.keys($data).sort() -->
+                <td data-bind="text: $parent[$data]"></td>
+                <!-- /ko -->
+              </tr>
+              </tbody>
+            </table>
+          </div>
+          <!-- /ko -->
+          <!-- ko if: !properties.backends || !properties.backends().backend_states -->
+          <pre data-bind="text: _('The selected tab has no data')"></pre>
+          <!-- /ko -->
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-finstances') }"
+             data-profile="finstances">
+          <!-- ko if: properties.finstances && properties.finstances().backend_instances -->
+          <div data-bind="attr: { id: $root.contextId('queries-page-memory-finstances-template') }"
+               style="overflow-x: scroll;">
+            <table class="table table-condensed">
+              <thead>
+              <tr>
+                <!-- ko foreach: [_('host')].concat(Object.keys($data.properties.finstances().backend_instances[0].instance_stats[0])).sort() -->
+                <th data-bind="text: $data"></th>
+                <!-- /ko -->
+              </tr>
+              </thead>
+              <tbody data-bind="
+                  foreach: {
+                    data: $data.properties.finstances().backend_instances.reduce(function(arr, instance) {
+                      instance.instance_stats.forEach(function(stats) { stats.host = instance.host; });
+                      return arr.concat(instance.instance_stats);
+                    }, []),
+                    minHeight: 20,
+                    container: $root.contextId('#queries-page-memory-finstances-template')
+                  }">
+              <tr>
+                <!-- ko foreach: Object.keys($data).sort() -->
+                <td data-bind="text: $parent[$data]"></td>
+                <!-- /ko -->
+              </tr>
+              </tbody>
+            </table>
+          </div>
+          <!-- /ko -->
+          <!-- ko if: !properties.finstances || !properties.finstances().backend_instances -->
+          <pre data-bind="text: _('The selected tab has no data')"></pre>
+          <!-- /ko -->
+        </div>
+        <!-- /ko -->
+
+        <!-- ko if: $root.job().mainType() == 'queries-hive' -->
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('queries-page-hive-plan-text') }"
+             data-profile="plan">
+          <!-- ko if: properties.plan && properties.plan().plan -->
+          <pre data-bind="text: properties.plan().plan || _('The selected tab has no data')"></pre>
+          <!-- /ko -->
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-hive-stmt') }"
+             data-profile="stmt">
+          <pre data-bind="text: (properties.plan && properties.plan().stmt) || _('The selected tab has no data')"></pre>
+        </div>
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('queries-page-hive-perf') }"
+             data-profile="perf">
+          <hive-query-plan></hive-query-plan>
+        </div>
+        <!-- /ko -->
+      </div>
+    </div>
+    <!-- /ko -->
+  </div>
+</script>
+
+<script type="text/html" id="jb-livy-session-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
+              <span data-bind="text: name"></span>
+            </a>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-danger': apiStatus() === 'FAILED',
+                  'progress-warning': apiStatus() === 'RUNNING',
+                  'progress-success': apiStatus() === 'SUCCEEDED'
+                }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{ 'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#livy-session-page-statements')
+              },
+              click: function() {
+                fetchProfile('properties');
+                $root.showTab('#livy-session-page-statements');
+              }">${ _('Properties') }</a>
+        </li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('livy-session-page-statements') }">
+          <table id="actionsTable" class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Id')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Output')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['statements']">
+            <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
+              <td>
+                <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
+                  <i class="fa fa-tasks"></i>
+                </a>
+              </td>
+              <td data-bind="text: state"></td>
+              <td data-bind="text: output"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-celery-beat-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
+              <span data-bind="text: name"></span>
+            </a>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-danger': apiStatus() === 'FAILED',
+                  'progress-warning': apiStatus() === 'RUNNING',
+                  'progress-success': apiStatus() === 'SUCCEEDED'
+                }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#celery-beat-page-statements')
+              },
+              click: function() {
+                fetchProfile('properties');
+                $root.showTab('#celery-beat-page-statements');
+              }">${ _('Properties') }</a>
+        </li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: $root.contextId('celery-beat-page-statements') }">
+          <table class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Id')}</th>
+              <th>${_('State')}</th>
+              <th>${_('Output')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['statements']">
+            <tr data-bind="click: function() {  $root.job().id(id); $root.job().fetchJob(); }" class="pointer">
+              <td>
+                <a data-bind="hueLink: '/jobbrowser/jobs/' + id(), clickBubble: false">
+                  <i class="fa fa-tasks"></i>
+                </a>
+              </td>
+              <td data-bind="text: state"></td>
+              <td data-bind="text: output"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-schedule-hive-page">
+  <div class="row-fluid">
+    <div data-bind="css: {'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <a data-bind="hueLink: doc_url" title="${ _('Open in editor') }">
+              <span data-bind="text: name"></span>
+            </a>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-danger': apiStatus() === 'FAILED',
+                  'progress-warning': apiStatus() === 'RUNNING',
+                  'progress-success': apiStatus() === 'SUCCEEDED'
+                }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active">
+          <a data-bind="attr: { href: $root.contextId('#schedule-hive-page-properties') }"
+             data-toggle="tab">
+            ${ _('Properties') }
+          </a>
+        </li>
+        <li>
+          <a data-bind="
+              attr: {
+                href: $root.contextId('#schedule-hive-page-queries')
+              },
+              click: function() {
+                fetchProfile('tasks');
+                $root.showTab('#schedule-hive-queries');
+              }"
+             data-toggle="tab">${ _('Queries') }</a>
+        </li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('schedule-hive-page-properties') }">
+          <pre data-bind="highlight: { value: properties['query'], formatted: true, dialect: 'hive' }"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('schedule-hive-page-queries') }">
+          <!-- ko with: coordinatorActions() -->
+          <form class="form-inline">
+            <div data-bind="template: { name: 'jb-job-actions' }" class="pull-right"></div>
+          </form>
+
+          <table data-bind="attr: { id: $root.contextId('schedulesHiveTable') }" class="datatables table table-condensed status-border-container">
+            <thead>
+            <tr>
+              <th width="1%">
+                <div class="select-all hue-checkbox fa"
+                     data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div>
+              </th>
+              <th>${_('Status')}</th>
+              <th>${_('Title')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: apps">
+            <tr class="status-border pointer" data-bind="
+                  css: {
+                    'completed': properties.status() == 'SUCCEEDED',
+                    'running': ['RUNNING', 'FAILED', 'KILLED'].indexOf(properties.status()) != -1,
+                    'failed': properties.status() == 'FAILED' || properties.status() == 'KILLED'
+                  },
+                  click: function(data, event) {
+                    $root.job().onTableRowClick(event, properties.externalId());
+                  }">
+              <td data-bind="click: function() {}, clickBubble: false">
+                <div class="hue-checkbox fa" data-bind="
+                    click: function() {},
+                    clickBubble: false,
+                    multiCheck: $root.contextId('#schedulesHiveTable'),
+                    value: $data,
+                    hueChecked: $parent.selectedJobs"></div>
+              </td>
+              <td><span class="label job-status-label" data-bind="text: properties.status"></span></td>
+              <td data-bind="text: properties.title"></td>
+            </tr>
+            </tbody>
+          </table>
+          <!-- /ko -->
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-job-actions">
+  <div class="btn-group">
+    <!-- ko if: $root.job() && $root.job().type() === 'schedule' -->
+    <button class="btn" title="${ _('Sync Coordinator') }"
+            data-bind="click: function() { $root.job().showSyncCoorModal() }, enable: killEnabled">
+      <i class="fa fa-refresh"></i> <!-- ko ifnot: $root.isMini -->${ _('Coordinator') }<!-- /ko -->
+    </button>
+
+    <button class="btn" title="${ _('Sync Workflow') }"
+            data-bind="click: function(){ control('sync_workflow'); }, enable: killEnabled">
+      <i class="fa fa-refresh"></i> <!-- ko ifnot: $root.isMini -->${ _('Workflow') }<!-- /ko -->
+    </button>
+    <!-- /ko -->
+  </div>
+
+  <div class="btn-group">
+    <!-- ko if: hasResume -->
+    <button class="btn" title="${ _('Resume selected') }"
+            data-bind="click: function() { control('resume'); }, enable: resumeEnabled">
+      <i class="fa fa-play"></i> <!-- ko ifnot: $root.isMini -->${ _('Resume') }<!-- /ko -->
+    </button>
+    <!-- /ko -->
+
+    <!-- ko if: hasPause -->
+    <button class="btn" title="${ _('Suspend selected') }"
+            data-bind="click: function() { control('suspend'); }, enable: pauseEnabled">
+      <i class="fa fa-pause"></i> <!-- ko ifnot: $root.isMini -->${ _('Suspend') }<!-- /ko -->
+    </button>
+    <!-- /ko -->
+
+    <!-- ko if: hasRerun -->
+    <button class="btn" title="${ _('Rerun selected') }"
+            data-bind="click: function() { control('rerun'); }, enable: rerunEnabled">
+      <i class="fa fa-repeat"></i> <!-- ko ifnot: $root.isMini -->${ _('Rerun') }<!-- /ko -->
+    </button>
+    <!-- /ko -->
+
+    % if not DISABLE_KILLING_JOBS.get():
+    <!-- ko if: hasKill -->
+    <button class="btn btn-danger disable-feedback" title="${_('Stop selected')}" data-bind="
+        click: function() { $($root.contextId('#killModal')).modal('show'); }, enable: killEnabled
+      ">
+      <i class="fa fa-times"></i> <!-- ko ifnot: $root.isMini -->${_('Kill')}<!-- /ko -->
+    </button>
+    <!-- /ko -->
+    % endif
+
+    <!-- ko if: hasIgnore -->
+    <button class="btn btn-danger disable-feedback" title="${_('Ignore selected')}"
+            data-bind="click: function() { control('ignore'); }, enable: ignoreEnabled">
+      ## TODO confirmation
+      <i class="fa fa-eraser"></i> <!-- ko ifnot: $root.isMini -->${_('Ignore')}<!-- /ko -->
+    </button>
+    <!-- /ko -->
+  </div>
+</script>
+
+<script type="text/html" id="jb-workflow-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- /ko -->
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <div>
+              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }"></a>
+              <a data-bind="
+                  documentContextPopover: { uuid: doc_url().split('=')[1], orientation: 'bottom', offset: { top: 5 } }"
+                 href="javascript: void(0);" title="${ _('Preview document') }">
+                <i class="fa fa-info"></i>
+              </a>
+            </div>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-danger': apiStatus() === 'FAILED',
+                  'progress-warning': apiStatus() === 'RUNNING',
+                  'progress-success': apiStatus() === 'SUCCEEDED'
+                },
+                attr: { title: status }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Duration') }</li>
+          <li><span data-bind="text: duration() ? duration().toHHMMSS() : ''"></span></li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="moment: {data: submitted, format: 'LLL'}"></span></li>
+          <!-- /ko -->
+          <!-- ko if: properties['parameters'].length > 0 -->
+          <li class="nav-header">${ _('Variables') }</li>
+          <li>
+            <ul class="unstyled" data-bind="foreach: properties['parameters']">
+              <li class="margin-top-5">
+                <span data-bind="text: name, attr: { title: value }" class="muted"></span><br>
+                &nbsp;
+                <!-- ko if: link -->
+                <a data-bind="hueLink: link, text: value, attr: { title: value }" href="javascript: void(0);">
+                </a>
+                <!-- /ko -->
+                <!-- ko ifnot: link -->
+                <span data-bind="text: value, attr: { title: value }"></span>
+                <!-- /ko -->
+              </li>
+            </ul>
+          </li>
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css: { 'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <!-- ko ifnot: $root.isMini() -->
+        <li class="active"><a href="#workflow-page-graph" data-toggle="tab">${ _('Graph') }</a></li>
+        <!-- /ko -->
+        <li><a data-bind="
+            attr: {
+              href: $root.contextId('#workflow-page-metadata')
+            },
+            click: function() {
+              fetchProfile('properties');
+              $root.showTab('#workflow-page-metadata');
+            }">${_('Properties') }</a></li>
+        <li><a class="jb-logs-link" data-bind="attr: { href: $root.contextId('#workflow-page-logs') }"
+               data-toggle="tab">${ _('Logs') }</a></li>
+        <li data-bind="css: { 'active': $root.isMini() }"><a
+          data-bind="attr: { href: $root.contextId('#workflow-page-tasks') }" data-toggle="tab">${
+          _('Tasks') }</a></li>
+        <li><a data-bind="
+            attr: {
+              href: $root.contextId('#workflow-page-xml')
+            },
+            click: function() {
+              fetchProfile('xml');
+              $root.showTab('#workflow-page-xml');
+            }">${ _('XML') }</a></li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <!-- ko ifnot: $root.isMini() -->
+        <div class="tab-pane active dashboard-container" id="workflow-page-graph"></div>
+        <!-- /ko -->
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('workflow-page-logs') }">
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="
+            attr: { id: $root.contextId('workflow-page-tasks') }, css: { 'active': $root.isMini() }">
+          <table class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Log')}</th>
+              <th>${_('Status')}</th>
+              <th>${_('Error message')}</th>
+              <th>${_('Error code')}</th>
+              <th>${_('External id')}</th>
+              <th>${_('Id')}</th>
+              <th>${_('Start time')}</th>
+              <th>${_('End time')}</th>
+              <th>${_('Data')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['actions']">
+            <tr>
+              <td>
+                <a data-bind="hueLink: '/jobbrowser/jobs/' + ko.unwrap(externalId), clickBubble: false">
+                  <i class="fa fa-tasks"></i>
+                </a>
+              </td>
+              <td data-bind="text: status"></td>
+              <td data-bind="text: errorMessage"></td>
+              <td data-bind="text: errorCode"></td>
+              <td data-bind="
+                  text: externalId,
+                  click: function() {
+                    $root.job().id(ko.unwrap(externalId));
+                    $root.job().fetchJob();
+                  },
+                  style: { color: '#0B7FAD' }" class="pointer"></td>
+              <td data-bind="
+                  text: id,
+                  click: function(data, event) {
+                    $root.job().onTableRowClick(event, id);
+                  },
+                  style: { color: '#0B7FAD' }" class="pointer"></td>
+              <td data-bind="moment: {data: startTime, format: 'LLL'}"></td>
+              <td data-bind="moment: {data: endTime, format: 'LLL'}"></td>
+              <td data-bind="text: data"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('workflow-page-metadata') }">
+          <div data-bind="
+              template: {
+                name: 'jb-render-properties',
+                data: properties['properties']
+              }"></div>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('workflow-page-xml') }">
+          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-workflow-action-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Job') }</li>
+          <li>
+            <a data-bind="hueLink: '/jobbrowser/jobs/' + properties['externalId']()" href="javascript: void(0);">
+              <span data-bind="text: properties['externalId']"></span>
+            </a>
+          </li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+        </ul>
+      </div>
+    </div>
+
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a
+          data-bind="attr: { href: $root.contextId('#workflow-action-page-metadata') }"
+          data-toggle="tab">${ _('Properties') }</a></li>
+        <li><a data-bind="attr: { href: $root.contextId('#workflow-action-page-tasks') }"
+               data-toggle="tab">${ _('Child jobs') }</a></li>
+        <li><a data-bind="attr: { href: $root.contextId('#workflow-action-page-xml') }"
+               data-toggle="tab">${ _('XML') }</a></li>
+      </ul>
+
+      <div class="tab-content">
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('workflow-action-page-metadata') }">
+          <table class="datatables table table-condensed">
+            <thead>
+            <tr>
+              <th>${_('Name')}</th>
+              <th>${_('Value')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['properties']">
+            <tr>
+              <td data-bind="text: name"></td>
+              <td data-bind="text: value"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('workflow-action-page-tasks') }">
+          <!-- ko if: properties['externalChildIDs'].length > 0 -->
+          <table class="table table-condensed datatables">
+            <thead>
+            <tr>
+              <th>${ _('Ids') }</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['externalChildIDs']">
+            <tr>
+              <td>
+                <a data-bind="hueLink: '/jobbrowser/jobs/' + $data, text: $data" href="javascript: void(0);">
+                </a>
+              </td>
+            </tr>
+            </tbody>
+          </table>
+          <!-- /ko -->
+
+          <!-- ko if: properties['externalChildIDs'].length == 0 -->
+          ${ _('No external jobs') }
+          <!-- /ko -->
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('workflow-action-page-xml' }">
+          <div data-bind="readOnlyAce: properties['conf'], type: 'xml'"></div>
+        </div>
+
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-schedule-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- /ko -->
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <div>
+              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }"></a>
+              <a data-bind="
+                  documentContextPopover: {
+                    uuid: doc_url().split('=')[1],
+                    orientation: 'bottom',
+                    offset: { top: 5 }
+                  }" href="javascript: void(0);" title="${ _('Preview document') }"><i class="fa fa-info"></i></a>
+            </div>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                  'progress-success': apiStatus() !== 'FAILED' && progress() === 100,
+                  'progress-danger': apiStatus() === 'FAILED'
+                },
+                attr: { title: status }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <!-- ko ifnot: $root.isMini() -->
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="text: submitted"></span></li>
+          <li class="nav-header">${ _('Next Run') }</li>
+          <li><span data-bind="text: properties['nextTime']"></span></li>
+          <li class="nav-header">${ _('Total Actions') }</li>
+          <li><span data-bind="text: properties['total_actions']"></span></li>
+          <li class="nav-header">${ _('End time') }</li>
+          <li><span data-bind="text: properties['endTime']"></span></li>
+          <!-- /ko -->
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css: {'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a data-bind="attr: { href: $root.contextId('#schedule-page-calendar') }"
+                              data-toggle="tab">${ _('Tasks') }</a></li>
+        <li><a class="jb-logs-link" data-bind="attr: { href: $root.contextId('#schedule-page-logs') }"
+               data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+            attr: {
+              href: $root.contextId('#schedule-page-metadata')
+            },
+            click: function() {
+              fetchProfile('properties');
+              $root.showTab('#schedule-page-metadata');
+            }">${ _('Properties') }</a></li>
+        <li><a data-bind="
+            attr: {
+              href: $root.contextId('#schedule-page-xml')
+            },
+            click: function() {
+              fetchProfile('xml');
+              $root.showTab('#schedule-page-xml');
+            }">${ _('XML') }</a></li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active" data-bind="attr: { id: $root.contextId('schedule-page-calendar') }">
+          <!-- ko with: coordinatorActions() -->
+          <form class="form-inline">
+            <!-- ko with: $root.job() -->
+            <!-- ko if: type() === 'schedule' -->
+            <span data-bind="foreach: statesValuesFilter">
+                <label class="checkbox">
+                  <div class="pull-left margin-left-5 status-border status-content"
+                       data-bind="css: value, hueCheckbox: checked"></div>
+                  <div class="inline-block" data-bind="text: name, toggle: checked"></div>
+                </label>
+              </span>
+            <!-- /ko -->
+            <!-- /ko -->
+            <div data-bind="template: { name: 'jb-job-actions' }" class="pull-right"></div>
+          </form>
+
+          <table id="schedulesTable" class="datatables table table-condensed status-border-container">
+            <thead>
+            <tr>
+              <th width="1%">
+                <div class="select-all hue-checkbox fa"
+                     data-bind="hueCheckAll: { allValues: apps, selectedValues: selectedJobs }"></div>
+              </th>
+              <th>${_('Status')}</th>
+              <th>${_('Title')}</th>
+              <th>${_('type')}</th>
+              <th>${_('errorMessage')}</th>
+              <th>${_('missingDependencies')}</th>
+              <th>${_('number')}</th>
+              <th>${_('errorCode')}</th>
+              <th>${_('externalId')}</th>
+              <th>${_('id')}</th>
+              <th>${_('lastModifiedTime')}</th>
+            </tr>
+            </thead>
+            <!-- ko if: !$root.job().forceUpdatingJob() -->
+            <tbody data-bind="foreach: apps">
+            <tr class="status-border pointer" data-bind="
+                  css: {
+                    'completed': properties.status() == 'SUCCEEDED',
+                    'running': ['RUNNING', 'FAILED', 'KILLED'].indexOf(properties.status()) != -1,
+                    'failed': properties.status() == 'FAILED' || properties.status() == 'KILLED'
+                  },
+                  click: function(data, event) {
+                    $root.job().onTableRowClick(event, properties.externalId());
+                  }">
+              <td data-bind="click: function() {}, clickBubble: false">
+                <div class="hue-checkbox fa" data-bind="
+                    click: function() {},
+                    clickBubble: false,
+                    multiCheck: '#schedulesTable',
+                    value: $data,
+                    hueChecked: $parent.selectedJobs"></div>
+              </td>
+              <td><span class="label job-status-label" data-bind="text: properties.status"></span></td>
+              <td data-bind="text: properties.title"></td>
+              <td data-bind="text: properties.type"></td>
+              <td data-bind="text: properties.errorMessage"></td>
+              <td data-bind="text: properties.missingDependencies"></td>
+              <td data-bind="text: properties.number"></td>
+              <td data-bind="text: properties.errorCode"></td>
+              <td data-bind="text: properties.externalId"></td>
+              <td data-bind="text: properties.id"></td>
+              <td data-bind="text: properties.lastModifiedTime"></td>
+            </tr>
+            </tbody>
+            <!-- /ko -->
+            <!-- ko if: $root.job().forceUpdatingJob() -->
+            <tbody>
+            <tr>
+              <td colspan="11"><!-- ko hueSpinner: { spin: true, inline: true, size: 'large' } --><!-- /ko --></td>
+            </tr>
+            </tbody>
+            <!-- /ko -->
+          </table>
+          <!-- /ko -->
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('schedule-page-logs') }">
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('schedule-page-metadata') }">
+          <div data-bind="
+              template: {
+                name: 'jb-render-properties',
+                data: properties['properties']
+              }"></div>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('schedule-page-xml') }">
+          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-bundle-page">
+  <div class="row-fluid">
+    <div data-bind="css:{'span2': !$root.isMini(), 'span12': $root.isMini() }">
+      <div class="sidebar-nav">
+        <ul class="nav nav-list">
+          <li class="nav-header">${ _('Id') }</li>
+          <li class="break-word"><span data-bind="text: id"></span></li>
+          <!-- ko if: doc_url -->
+          <li class="nav-header">${ _('Document') }</li>
+          <li>
+            <div>
+              <a data-bind="hueLink: doc_url, text: name" title="${ _('Open') }"></a>
+              <a data-bind="
+                  documentContextPopover: {
+                    uuid: doc_url().split('=')[1],
+                    orientation: 'bottom',
+                    offset: { top: 5 }
+                  }" href="javascript: void(0);" title="${ _('Preview document') }"><i class="fa fa-info"></i></a>
+            </div>
+          </li>
+          <!-- /ko -->
+          <!-- ko ifnot: doc_url -->
+          <li class="nav-header">${ _('Name') }</li>
+          <li><span data-bind="text: name"></span></li>
+          <!-- /ko -->
+          <li class="nav-header">${ _('Type') }</li>
+          <li><span data-bind="text: type"></span></li>
+          <li class="nav-header">${ _('Status') }</li>
+          <li><span data-bind="text: status"></span></li>
+          <li class="nav-header">${ _('User') }</li>
+          <li><span data-bind="text: user"></span></li>
+          <li class="nav-header">${ _('Progress') }</li>
+          <li><span data-bind="text: progress"></span>%</li>
+          <li>
+            <div class="progress-job progress" style="background-color: #FFF; width: 100%" data-bind="
+                css: {
+                  'progress-danger': apiStatus() === 'FAILED',
+                  'progress-warning': apiStatus() !== 'FAILED' && progress() < 100,
+                  'progress-success': apiStatus() !== 'FAILED' && progress() === 100
+                }">
+              <div class="bar" data-bind="style: {'width': progress() + '%'}"></div>
+            </div>
+          </li>
+          <li class="nav-header">${ _('Submitted') }</li>
+          <li><span data-bind="text: submitted"></span></li>
+        </ul>
+      </div>
+    </div>
+    <div data-bind="css:{'span10': !$root.isMini(), 'span12 no-margin': $root.isMini() }">
+      <ul class="nav nav-pills margin-top-20">
+        <li class="active"><a data-bind="attr: { href: $root.contextId('#bundle-page-coordinators') }"
+                              data-toggle="tab">${ _('Tasks') }</a></li>
+        <li><a class="jb-logs-link" data-bind="attr: { href: $root.contextId('#bundle-page-logs') }"
+               data-toggle="tab">${ _('Logs') }</a></li>
+        <li><a data-bind="
+            attr: {
+              href: $root.contextId('#bundle-page-metadata')
+            },
+            click: function() {
+              fetchProfile('properties');
+              $root.showTab('#bundle-page-metadata');
+            }">${ _('Properties') }</a></li>
+        <li><a data-bind="
+            attr: {
+              href:$root.contextId('#bundle-page-xml')
+            },
+            click: function() {
+              fetchProfile('xml');
+              $root.showTab('#bundle-page-xml');
+            }">${ _('XML') }</a></li>
+        <li class="pull-right" data-bind="template: { name: 'jb-job-actions' }"></li>
+      </ul>
+
+      <div class="clearfix"></div>
+
+      <div class="tab-content">
+        <div class="tab-pane active"
+             data-bind="attr: { id: $root.contextId('bundle-page-coordinators') }">
+          <table id="coordsTable" class="datatables table table-condensed status-border-container">
+            <thead>
+            <tr>
+              <th>${_('Status')}</th>
+              <th>${_('Name')}</th>
+              <th>${_('Type')}</th>
+              <th>${_('nextMaterializedTime')}</th>
+              <th>${_('lastAction')}</th>
+              <th>${_('frequency')}</th>
+              <th>${_('timeUnit')}</th>
+              <th>${_('externalId')}</th>
+              <th>${_('id')}</th>
+              <th>${_('pauseTime')}</th>
+            </tr>
+            </thead>
+            <tbody data-bind="foreach: properties['actions']">
+            <tr class="status-border pointer" data-bind="
+                css: {
+                  'completed': ko.unwrap(status) == 'SUCCEEDED',
+                  'running': ['SUCCEEDED', 'FAILED', 'KILLED'].indexOf(ko.unwrap(status)) != -1,
+                  'failed': ko.unwrap(status) == 'FAILED' || ko.unwrap(status) == 'KILLED'
+                },
+                click: function() {
+                  if (ko.unwrap(id)) {
+                    $root.job().id(ko.unwrap(id));
+                    $root.job().fetchJob();
+                  }
+                }">
+              <td><span class="label job-status-label" data-bind="text: status"></span></td>
+              <td data-bind="text: name"></td>
+              <td data-bind="text: type"></td>
+              <td data-bind="text: nextMaterializedTime"></td>
+              <td data-bind="text: lastAction"></td>
+              <td data-bind="text: frequency"></td>
+              <td data-bind="text: timeUnit"></td>
+              <td data-bind="text: externalId"></td>
+              <td data-bind="text: id"></td>
+              <td data-bind="text: pauseTime"></td>
+            </tr>
+            </tbody>
+          </table>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('bundle-page-logs') }">
+          <pre data-bind="html: logs, logScroller: logs"></pre>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('bundle-page-metadata') }">
+          <div data-bind="
+              template: {
+                name: 'jb-render-properties',
+                data: properties['properties']
+              }"></div>
+        </div>
+
+        <div class="tab-pane" data-bind="attr: { id: $root.contextId('bundle-page-xml') }">
+          <div data-bind="readOnlyAce: properties['xml'], path: 'xml', type: 'xml'"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</script>
+
+<script type="text/html" id="jb-render-properties">
+  <!-- ko hueSpinner: { spin: !$data.properties, center: true, size: 'xlarge' } --><!-- /ko -->
+  <!-- ko if: $data.properties -->
+  <!-- ko ifnot: $root.isMini() -->
+  <form class="form-search">
+    <input type="text" data-bind="clearable: $parent.propertiesFilter, valueUpdate: 'afterkeydown'"
+           class="input-xlarge search-query" placeholder="${_('Text Filter')}">
+  </form>
+  <br>
+  <!-- /ko -->
+  <table id="jobbrowserJobPropertiesTable" class="table table-condensed">
+    <thead>
+    <tr>
+      <th>${ _('Name') }</th>
+      <th>${ _('Value') }</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: Object.keys($data.properties)">
+    <tr>
+      <td data-bind="text: $data"></td>
+      <td>
+        <!-- ko template: { name: 'jb-link-or-text', data: { name: $data, value: $parent.properties[$data] } } -->
+        <!-- /ko -->
+      </td>
+    </tr>
+    </tbody>
+  </table>
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-render-page-counters">
+  <!-- ko hueSpinner: { spin: !$data, center: true, size: 'xlarge' } --><!-- /ko -->
+
+  <!-- ko if: $data -->
+  <!-- ko ifnot: $data.counterGroup -->
+  <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
+  <!-- /ko -->
+  <!-- ko if: $data.counterGroup -->
+  <!-- ko foreach: $data.counterGroup -->
+  <h3 data-bind="text: counterGroupName"></h3>
+  <table class="table table-condensed">
+    <thead>
+    <tr>
+      <th>${ _('Name') }</th>
+      <th width="15%">${ _('Maps total') }</th>
+      <th width="15%">${ _('Reduces total') }</th>
+      <th width="15%">${ _('Total') }</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: counter">
+    <tr>
+      <td data-bind="text: name"></td>
+      <td data-bind="text: mapCounterValue"></td>
+      <td data-bind="text: reduceCounterValue"></td>
+      <td data-bind="text: totalCounterValue"></td>
+    </tr>
+    </tbody>
+  </table>
+  <!-- /ko -->
+  <!-- /ko -->
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-render-task-counters">
+  <!-- ko hueSpinner: { spin: !$data.id, center: true, size: 'xlarge' } --><!-- /ko -->
+
+  <!-- ko if: $data.id -->
+  <!-- ko ifnot: $data.taskCounterGroup -->
+  <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
+  <!-- /ko -->
+  <!-- ko if: $data.taskCounterGroup -->
+  <!-- ko foreach: $data.taskCounterGroup -->
+  <h3 data-bind="text: counterGroupName"></h3>
+  <table class="table table-condensed">
+    <thead>
+    <tr>
+      <th>${ _('Name') }</th>
+      <th width="30%">${ _('Value') }</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: counter">
+    <tr>
+      <td data-bind="text: name"></td>
+      <td data-bind="text: value"></td>
+    </tr>
+    </tbody>
+  </table>
+  <!-- /ko -->
+  <!-- /ko -->
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-render-attempt-counters">
+  <!-- ko hueSpinner: { spin: !$data.id, center: true, size: 'xlarge' } --><!-- /ko -->
+
+  <!-- ko if: $data.id -->
+  <!-- ko ifnot: $data.taskAttemptCounterGroup -->
+  <span class="muted">${ _('There are currently no counters to be displayed.') }</span>
+  <!-- /ko -->
+  <!-- ko if: $data.taskAttemptCounterGroup -->
+  <!-- ko foreach: $data.taskAttemptCounterGroup -->
+  <h3 data-bind="text: counterGroupName"></h3>
+  <table class="table table-condensed">
+    <thead>
+    <tr>
+      <th>${ _('Name') }</th>
+      <th width="30%">${ _('Value') }</th>
+    </tr>
+    </thead>
+    <tbody data-bind="foreach: counter">
+    <tr>
+      <td data-bind="text: name"></td>
+      <td data-bind="text: value"></td>
+    </tr>
+    </tbody>
+  </table>
+  <!-- /ko -->
+  <!-- /ko -->
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-render-metadata">
+  <!-- ko hueSpinner: { spin: !$data.property, center: true, size: 'xlarge' } --><!-- /ko -->
+  <!-- ko if: $data.property -->
+  <form class="form-search">
+    <input type="text" data-bind="clearable: $parent.metadataFilter, valueUpdate: 'afterkeydown'"
+           class="input-xlarge search-query" placeholder="${_('Text Filter')}">
+  </form>
+  <div data-bind="
+      attr: {
+        id: $root.contextId('job-mapreduce-page-metadata-template')
+      },
+      style: {
+        'height': !$root.isMini() ? 'calc(100vh - 350px)' : '400px'
+      }" style="overflow-y: hidden;">
+    <table id="jobbrowserJobMetadataTable" class="table table-condensed">
+      <thead>
+      <tr>
+        <th>${ _('Name') }</th>
+        <th width="50%">${ _('Value') }</th>
+      </tr>
+      </thead>
+      <tbody data-bind="
+          foreachVisible: {
+            data: property,
+            minHeight: 20,
+            container: $root.contextId('#job-mapreduce-page-metadata-template')
+          }">
+      <tr>
+        <td data-bind="text: name"></td>
+        <td>
+          <!-- ko template: { name: 'jb-link-or-text', data: { name: name, value: value } } --><!-- /ko -->
+        </td>
+      </tr>
+      </tbody>
+    </table>
+  </div>
+  <!-- /ko -->
+</script>
+
+<script type="text/html" id="jb-link-or-text">
+  <!-- ko if: typeof $data.value === 'string' -->
+  <!-- ko if: $data.name.indexOf('logs') > -1 || $data.name.indexOf('trackingUrl') > -1 -->
+  <a href="javascript:void(0);" data-bind="text: $data.value, attr: { href: $data.value }" target="_blank"></a>
+  <!-- /ko -->
+  <!-- ko if: ($data.name.indexOf('dir') > -1 || $data.name.indexOf('path') > -1 || $data.name.indexOf('output') > -1 || $data.name.indexOf('input') > -1) && ($data.value.startsWith('/') || $data.value.startsWith('hdfs://') || $data.value.startsWith('s3a://')) -->
+  <a href="javascript:void(0);"
+     data-bind="hueLink: '/filebrowser/view=' + $root.getHDFSPath($data.value), text: $data.value"></a>
+  <a href="javascript: void(0);"
+     data-bind="storageContextPopover: { path: $root.getHDFSPath($data.value), orientation: 'left', offset: { top: 5 } }"><i
+    class="fa fa-info"></i></a>
+  <!-- /ko -->
+  <!-- ko ifnot: $data.name.indexOf('logs') > -1 || $data.name.indexOf('trackingUrl') > -1 || (($data.name.indexOf('dir') > -1 || $data.name.indexOf('path') > -1 || $data.name.indexOf('output') > -1 || $data.name.indexOf('input') > -1) && ($data.value.startsWith('/') || $data.value.startsWith('hdfs://') || $data.value.startsWith('s3a://'))) -->
+  <span data-bind="text: $data.value"></span>
+  <!-- /ko -->
+  <!-- /ko -->
+  <!-- ko ifnot: typeof $data.value === 'string' -->
+  <span data-bind="text: $data.value"></span>
+  <!-- /ko -->
+</script>
+</%def>

+ 40 - 33
webpack.config.js

@@ -37,7 +37,11 @@ const config = {
       import: './desktop/core/src/desktop/js/apps/storageBrowser/app.js',
       dependOn: 'hue'
     },
-    jobBrowser: { import: './desktop/core/src/desktop/js/apps/jobBrowser/app.js', dependOn: 'hue' }
+    jobBrowser: { import: './desktop/core/src/desktop/js/apps/jobBrowser/app.js', dependOn: 'hue' },
+    miniJobBrowser: {
+      import: './desktop/core/src/desktop/js/apps/jobBrowser/miniApp.js',
+      dependOn: 'hue'
+    }
   },
   mode: 'development',
   module: {
@@ -50,32 +54,35 @@ const config = {
       {
         test: /\.(jsx?|tsx?)$/,
         exclude: /node_modules/,
-        use: [{
-          loader: 'babel-loader',
-          options: {
-            presets: ['@babel/preset-env', '@babel/preset-react']
+        use: [
+          {
+            loader: 'babel-loader',
+            options: {
+              presets: ['@babel/preset-env', '@babel/preset-react']
+            }
           }
-        }] 
+        ]
       },
       {
         test: /\.(jsx?|tsx?)$/,
-        enforce: "pre",
-        use: ["source-map-loader"],
-      },      
+        enforce: 'pre',
+        use: ['source-map-loader']
+      },
       {
         test: /\.scss$/,
         exclude: /node_modules/,
-        use: ['style-loader', 'css-loader', {
-          loader: 'sass-loader',
-          options: {
-            sassOptions: {
-              includePaths: [
-                'desktop/core/src/desktop/js/components/styles',
-                'node_modules',
-              ],
-            },
-          },
-        },]
+        use: [
+          'style-loader',
+          'css-loader',
+          {
+            loader: 'sass-loader',
+            options: {
+              sassOptions: {
+                includePaths: ['desktop/core/src/desktop/js/components/styles', 'node_modules']
+              }
+            }
+          }
+        ]
       },
       {
         test: /\.css$/i,
@@ -84,19 +91,19 @@ const config = {
       {
         test: /\.less$/i,
         use: [
-          { loader: "style-loader" },
-          { loader: "css-loader" },
+          { loader: 'style-loader' },
+          { loader: 'css-loader' },
           {
-              loader: "less-loader",
-              options: {
-                  lessOptions: {
-                      // This is not ideal but required by antd library
-                      javascriptEnabled: true,
-                  }
+            loader: 'less-loader',
+            options: {
+              lessOptions: {
+                // This is not ideal but required by antd library
+                javascriptEnabled: true
               }
-          }          
-        ],
-      },      
+            }
+          }
+        ]
+      },
       {
         test: /\.html$/,
         exclude: /node_modules/,
@@ -114,7 +121,7 @@ const config = {
     }
   },
   output: {
-    // Needed, at the time of writing with webpack 5.54.0, when using node 18.14.1 and later. 
+    // Needed, at the time of writing with webpack 5.54.0, when using node 18.14.1 and later.
     hashFunction: 'xxhash64',
     path: __dirname + '/desktop/core/src/desktop/static/desktop/js/bundles/hue',
     filename: '[name]-bundle-[fullhash].js',
@@ -130,7 +137,7 @@ const config = {
     maxAssetSize: 400 * 1024 // 400kb
   },
   plugins: getPluginConfig(BUNDLES.HUE).concat([
-    new CleanWebpackPlugin([`${__dirname}/desktop/core/src/desktop/static/desktop/js/bundles/hue`]),    
+    new CleanWebpackPlugin([`${__dirname}/desktop/core/src/desktop/static/desktop/js/bundles/hue`])
   ]),
   resolve: {
     extensions: ['.json', '.jsx', '.js', '.tsx', '.ts', '.vue'],