Pārlūkot izejas kodu

HUE-8687 [frontend] Move the hue.mako view models into webpack

Johan Ahlen 6 gadi atpakaļ
vecāks
revīzija
cfff677

+ 1 - 1
.eslintrc.js

@@ -11,7 +11,7 @@ const hueGlobals = [
   'LOGGED_USERGROUPS', 'METASTORE_PARTITION_LIMIT', 'WorkerGlobalScope',
 
   // other misc, TODO
-  'ace', 'CodeMirror', 'impalaDagre', 'less', 'MediumEditor', 'moment', 'Plotly', 'Role', 'sqlStatementsParser', 'trackOnGA'
+  'ace', 'Autocompleter', 'CodeMirror', 'impalaDagre', 'less', 'MediumEditor', 'moment', 'Plotly', 'Role', 'sqlStatementsParser', 'trackOnGA'
 ];
 
 const globals = normalGlobals.concat(hueGlobals).reduce((acc, key) => {

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook/notebook.js

@@ -19,4 +19,4 @@ import 'jquery/plugins/jquery.hdfstree';
 import 'ext/jquery.hotkeys';
 import Clipboard from 'clipboard';
 
-window.Clipboard = Clipboard;
+window.Clipboard = Clipboard;

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook/notebook.ko.js

@@ -2071,7 +2071,7 @@ const Snippet = function(vm, notebook, snippet) {
 
         if (vm.editorMode() && data.history_id) {
           if (!vm.isNotificationManager()) {
-            let url = vm.URLS.editor + '?editor=' + data.history_id;
+            const url = vm.URLS.editor + '?editor=' + data.history_id;
             vm.changeURL(url);
           }
           notebook.id(data.history_id);

+ 71 - 0
desktop/core/src/desktop/js/hue.js

@@ -46,6 +46,10 @@ import { PigFunctions, SqlSetOptions, SqlFunctions } from 'sql/sqlFunctions';
 import sqlWorkerHandler from 'sql/sqlWorkerHandler';
 
 import 'assist/assistViewModel';
+import OnePageViewModel from 'onePageViewModel';
+import SideBarViewModel from 'sideBarViewModel';
+import SidePanelViewModel from 'sidePanelViewModel';
+import TopNavViewModel from 'topNavViewModel';
 
 // TODO: Move to notebook.js
 import EditorViewModel from 'apps/notebook/notebook.ko';
@@ -76,3 +80,70 @@ window.SqlFunctions = SqlFunctions;
 window.sqlUtils = sqlUtils;
 window.sqlWorkerHandler = sqlWorkerHandler;
 window.qq = qq;
+
+$(document).ready(() => {
+  const onePageViewModel = new OnePageViewModel();
+  ko.applyBindings(onePageViewModel, $('.page-content')[0]);
+
+  const sidePanelViewModel = new SidePanelViewModel();
+  ko.applyBindings(sidePanelViewModel, $('.left-panel')[0]);
+  ko.applyBindings(sidePanelViewModel, $('#leftResizer')[0]);
+  ko.applyBindings(sidePanelViewModel, $('.right-panel')[0]);
+  ko.applyBindings(sidePanelViewModel, $('.context-panel')[0]);
+
+  const topNavViewModel = new TopNavViewModel(onePageViewModel);
+  if (!window.IS_EMBEDDED) {
+    ko.applyBindings(topNavViewModel, $('.top-nav')[0]);
+  }
+
+  const sidebarViewModel = new SideBarViewModel(onePageViewModel, topNavViewModel);
+  ko.applyBindings(sidebarViewModel, $('.hue-sidebar')[0]);
+  if (window.IS_MULTICLUSTER_ONLY) {
+    ko.applyBindings(sidebarViewModel, $('.hue-dw-sidebar-container')[0]);
+  }
+
+  huePubSub.publish('cluster.config.get.config');
+
+  $(document).on('hideHistoryModal', e => {
+    $('#clearNotificationHistoryModal').modal('hide');
+  });
+
+  huePubSub.subscribe('query.and.watch', query => {
+    $.post(
+      query['url'],
+      {
+        format: 'json',
+        sourceType: query['sourceType']
+      },
+      resp => {
+        if (resp.history_uuid) {
+          huePubSub.publish('open.editor.query', resp.history_uuid);
+        } else if (resp.message) {
+          $(document).trigger('error', resp.message);
+        }
+      }
+    ).fail(xhr => {
+      $(document).trigger('error', xhr.responseText);
+    });
+  });
+
+  let clickThrottle = -1;
+
+  $(window).click(e => {
+    window.clearTimeout(clickThrottle);
+    clickThrottle = window.setTimeout(() => {
+      if (
+        $(e.target).parents('.navbar-default').length > 0 &&
+        $(e.target).closest('.history-panel').length === 0 &&
+        $(e.target).closest('.btn-toggle-jobs-panel').length === 0 &&
+        $(e.target).closest('.hamburger-hue').length === 0 &&
+        $('.jobs-panel').is(':visible')
+      ) {
+        huePubSub.publish('hide.jobs.panel');
+        huePubSub.publish('hide.history.panel');
+      }
+    }, 10);
+  });
+
+  $('.page-content').jHueScrollUp();
+});

+ 1 - 1
desktop/core/src/desktop/js/ko/bindings/ko.hueLink.js

@@ -27,7 +27,7 @@ ko.bindingHandlers.hueLink = {
         return function(data, event) {
           const url = ko.unwrap(valueAccessor());
           if (url) {
-            let prefix = '/hue' + (url.indexOf('/') === 0 ? '' : '/');
+            const prefix = '/hue' + (url.indexOf('/') === 0 ? '' : '/');
             if ($(element).attr('target')) {
               window.open(prefix + url, $(element).attr('target'));
             } else if (event.ctrlKey || event.metaKey || event.which === 2) {

+ 1 - 0
desktop/core/src/desktop/js/ko/ko.all.js

@@ -15,6 +15,7 @@
 // limitations under the License.
 
 import ko from 'knockout';
+import 'ko/ko.init';
 import komapping from 'knockout.mapping';
 import 'knockout-switch-case';
 import 'knockout-sortable';

+ 29 - 0
desktop/core/src/desktop/js/ko/ko.init.js

@@ -0,0 +1,29 @@
+// 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 ko from 'knockout';
+
+const proxiedKoRegister = ko.components.register;
+const registeredComponents = [];
+
+ko.components.register = function() {
+  // This guarantees a ko component is only registered once
+  // Some currently get registered twice when switching between notebook and editor
+  if (registeredComponents.indexOf(arguments[0]) === -1) {
+    registeredComponents.push(arguments[0]);
+    return proxiedKoRegister.apply(this, arguments);
+  }
+};

+ 807 - 0
desktop/core/src/desktop/js/onePageViewModel.js

@@ -0,0 +1,807 @@
+// 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 hueUtils from 'utils/hueUtils';
+import huePubSub from 'utils/huePubSub';
+import page from 'page';
+import _ from 'lodash';
+
+class OnePageViewModel {
+  constructor() {
+    const self = this;
+
+    self.embeddable_cache = {};
+    self.currentApp = ko.observable();
+    self.currentContextParams = ko.observable(null);
+    self.currentQueryString = ko.observable(null);
+    self.isLoadingEmbeddable = ko.observable(false);
+    self.extraEmbeddableURLParams = ko.observable('');
+
+    self.getActiveAppViewModel = function(callback) {
+      const checkInterval = window.setInterval(() => {
+        const $koElement = $('#' + self.currentApp() + 'Components');
+        if ($koElement.length > 0 && ko.dataFor($koElement[0])) {
+          window.clearInterval(checkInterval);
+          callback(ko.dataFor($koElement[0]));
+        }
+      }, 25);
+    };
+
+    self.changeEditorType = function(type) {
+      self.getActiveAppViewModel(viewModel => {
+        if (viewModel && viewModel.selectedNotebook) {
+          hueUtils.waitForObservable(viewModel.selectedNotebook, () => {
+            if (viewModel.editorType() !== type) {
+              viewModel.selectedNotebook().selectedSnippet(type);
+              viewModel.editorType(type);
+              viewModel.newNotebook(type);
+            }
+          });
+        }
+      });
+    };
+
+    self.currentApp.subscribe(newApp => {
+      huePubSub.publish('set.current.app.name', newApp);
+      self.getActiveAppViewModel(viewModel => {
+        huePubSub.publish('set.current.app.view.model', viewModel);
+      });
+    });
+
+    huePubSub.subscribe('get.current.app.view.model', () => {
+      self.getActiveAppViewModel(viewModel => {
+        huePubSub.publish('set.current.app.view.model', viewModel);
+      });
+    });
+
+    huePubSub.subscribe('get.current.app.name', () => {
+      huePubSub.publish('set.current.app.name', self.currentApp());
+    });
+
+    huePubSub.subscribe('open.editor.query', uuid => {
+      self.loadApp('editor');
+      self.getActiveAppViewModel(viewModel => {
+        viewModel.openNotebook(uuid);
+      });
+    });
+
+    huePubSub.subscribe('open.importer.query', data => {
+      self.loadApp('importer');
+      self.getActiveAppViewModel(viewModel => {
+        hueUtils.waitForVariable(viewModel.createWizard, () => {
+          hueUtils.waitForVariable(viewModel.createWizard.prefill, () => {
+            viewModel.createWizard.prefill.source_type(data['source_type']);
+            viewModel.createWizard.prefill.target_type(data['target_type']);
+            viewModel.createWizard.prefill.target_path(data['target_path']);
+            viewModel.createWizard.destination.outputFormat(data['target_type']);
+          });
+          hueUtils.waitForVariable(viewModel.createWizard.source.query, () => {
+            viewModel.createWizard.source.query({ id: data.id, name: data.name });
+          });
+          hueUtils.waitForVariable(viewModel.createWizard.loadSampleData, () => {
+            viewModel.createWizard.loadSampleData(data);
+          });
+        });
+      });
+    });
+
+    huePubSub.subscribe('resize.form.actions', () => {
+      document.styleSheets[0].addRule(
+        '.form-actions',
+        'width: ' + $('.page-content').width() + 'px'
+      );
+      if ($('.content-panel:visible').length > 0) {
+        document.styleSheets[0].addRule('.form-actions', 'margin-left: -11px !important');
+      }
+    });
+
+    huePubSub.subscribe('split.panel.resized', () => {
+      huePubSub.publish('resize.form.actions');
+      huePubSub.publish('resize.plotly.chart');
+    });
+
+    huePubSub.publish('resize.form.actions');
+
+    huePubSub.subscribe('open.editor.new.query', statementOptions => {
+      self.loadApp('editor'); // Should open in Default
+
+      self.getActiveAppViewModel(viewModel => {
+        const editorType = statementOptions['type'] || 'hive'; // Next: use file extensions and default type of Editor for SQL
+        viewModel.newNotebook(editorType, () => {
+          self.changeEditorType(editorType);
+
+          if (statementOptions['statementPath']) {
+            viewModel
+              .selectedNotebook()
+              .snippets()[0]
+              .statementType(statementOptions['statementType']);
+            viewModel
+              .selectedNotebook()
+              .snippets()[0]
+              .statementPath(statementOptions['statementPath']);
+          }
+          if (statementOptions['directoryUuid']) {
+            viewModel.selectedNotebook().directoryUuid(statementOptions['directoryUuid']);
+          }
+        });
+      });
+    });
+
+    const loadedJs = [];
+    const loadedCss = [];
+    const loadedApps = [];
+
+    $('script[src]').each(function() {
+      loadedJs.push($(this).attr('src'));
+    });
+
+    $('link[href]').each(function() {
+      loadedCss.push($(this).attr('href'));
+    });
+
+    const loadScript = function(scriptUrl) {
+      if (scriptUrl.indexOf('-bundle') !== -1) {
+        const s = document.createElement('script');
+        s.src = scriptUrl;
+        s.type = 'text/javascript';
+        s.async = false;
+        document.getElementsByTagName('head')[0].appendChild(s);
+        return $.Deferred()
+          .resolve({ url: scriptUrl, head: true })
+          .promise();
+      }
+      const deferred = $.Deferred();
+      $.ajax({
+        url: scriptUrl,
+        converters: {
+          'text script': function(text) {
+            return text;
+          }
+        }
+      })
+        .done(contents => {
+          loadedJs.push(scriptUrl);
+          deferred.resolve({ url: scriptUrl, contents: contents });
+        })
+        .fail(() => {
+          deferred.resolve('');
+        });
+      return deferred.promise();
+    };
+
+    const loadScripts = function(scriptUrls) {
+      const promises = [];
+      while (scriptUrls.length) {
+        const scriptUrl =
+          typeof window.adaptHueEmbeddedUrls !== 'undefined'
+            ? window.adaptHueEmbeddedUrls(scriptUrls.shift())
+            : scriptUrls.shift();
+        if (loadedJs.indexOf(scriptUrl) !== -1) {
+          continue;
+        }
+        promises.push(loadScript(scriptUrl));
+      }
+      return promises;
+    };
+
+    const addGlobalCss = function($el) {
+      const cssFile = $el.attr('href').split('?')[0];
+      if (loadedCss.indexOf(cssFile) === -1) {
+        loadedCss.push(cssFile);
+        $.ajaxSetup({ cache: true });
+        if (typeof window.adaptHueEmbeddedUrls !== 'undefined') {
+          $el.attr('href', window.adaptHueEmbeddedUrls($el.attr('href')));
+        }
+        if (window.DEV) {
+          $el.attr('href', $el.attr('href') + '?dev=' + Math.random());
+        }
+        $el.clone().appendTo($('head'));
+        $.ajaxSetup({ cache: false });
+      }
+      $el.remove();
+    };
+
+    // Only load CSS and JS files that are not loaded before
+    self.processHeaders = function(response) {
+      const promise = $.Deferred();
+      const $rawHtml = $('<span>').html(response);
+
+      const $allScripts = $rawHtml.find('script[src]');
+      const scriptsToLoad = $allScripts
+        .map(function() {
+          return $(this).attr('src');
+        })
+        .toArray();
+      $allScripts.remove();
+
+      $rawHtml.find('link[href]').each(function() {
+        addGlobalCss($(this)); // Also removes the elements;
+      });
+
+      $rawHtml.find('a[href]').each(function() {
+        let link = $(this).attr('href');
+        if (link.startsWith('/') && !link.startsWith('/hue')) {
+          link = '/hue' + link;
+        }
+        $(this).attr('href', link);
+      });
+
+      if (typeof adaptHueEmbeddedUrls !== 'undefined') {
+        $rawHtml.find('img[src]').each(function() {
+          const $img = $(this);
+          $img.attr('src', window.adaptHueEmbeddedUrls($img.attr('src')));
+        });
+      }
+
+      $rawHtml.unwrap('span');
+
+      const scriptPromises = loadScripts(scriptsToLoad);
+
+      const evalScriptSync = function() {
+        if (scriptPromises.length) {
+          // Evaluate the scripts in the order they were defined in the page
+          const nextScriptPromise = scriptPromises.shift();
+          nextScriptPromise.done(scriptDetails => {
+            if (scriptDetails.contents) {
+              $.globalEval(scriptDetails.contents);
+            }
+            evalScriptSync();
+          });
+        } else {
+          // All evaluated
+          promise.resolve($rawHtml);
+        }
+      };
+
+      evalScriptSync();
+      return promise;
+    };
+
+    huePubSub.subscribe('hue4.process.headers', opts => {
+      self.processHeaders(opts.response).done(rawHtml => {
+        opts.callback(rawHtml);
+      });
+    });
+
+    self.loadApp = function(app, loadDeep) {
+      if (self.currentApp() === 'editor' && $('#editorComponents').length) {
+        const vm = ko.dataFor($('#editorComponents')[0]);
+        if (vm.isPresentationMode()) {
+          vm.selectedNotebook().isPresentationMode(false);
+        }
+      }
+
+      if (
+        self.currentApp() === 'editor' &&
+        self.embeddable_cache['editor'] &&
+        !$('#editorComponents').length
+      ) {
+        self.embeddable_cache['editor'] = undefined;
+      }
+
+      self.currentApp(app);
+      if (!app.startsWith('security')) {
+        self.lastContext = null;
+      }
+      window.SKIP_CACHE.forEach(skipped => {
+        huePubSub.publish('app.dom.unload', skipped);
+        $('#embeddable_' + skipped).html('');
+      });
+      self.isLoadingEmbeddable(true);
+      loadedApps.forEach(loadedApp => {
+        window.pauseAppIntervals(loadedApp);
+        huePubSub.pauseAppSubscribers(loadedApp);
+      });
+      $('.tooltip').hide();
+      huePubSub.publish('hue.datatable.search.hide');
+      huePubSub.publish('hue.scrollleft.hide');
+      huePubSub.publish('context.panel.visible', false);
+      huePubSub.publish('context.panel.visible.editor', false);
+      if (app === 'filebrowser') {
+        $(window).unbind('hashchange.fblist');
+      }
+      if (app.startsWith('oozie')) {
+        huePubSub.clearAppSubscribers('oozie');
+      }
+      if (app.startsWith('security')) {
+        $('#embeddable_security_hive').html('');
+        $('#embeddable_security_hdfs').html('');
+        $('#embeddable_security_hive2').html('');
+        $('#embeddable_security_solr').html('');
+      }
+      if (typeof self.embeddable_cache[app] === 'undefined') {
+        if (loadedApps.indexOf(app) === -1) {
+          loadedApps.push(app);
+        }
+        let baseURL = window.EMBEDDABLE_PAGE_URLS[app].url;
+        if (self.currentContextParams() !== null) {
+          if (loadDeep && self.currentContextParams()[0]) {
+            baseURL += self.currentContextParams()[0];
+          } else {
+            const route = new page.Route(baseURL);
+            route.keys.forEach(key => {
+              if (key.name === 0) {
+                if (typeof self.currentContextParams()[key.name] !== 'undefined') {
+                  if (app === 'filebrowser') {
+                    baseURL = baseURL
+                      .replace('*', self.currentContextParams()[key.name])
+                      .replace(/#/g, '%23');
+                  } else {
+                    baseURL = baseURL.replace('*', self.currentContextParams()[key.name]);
+                  }
+                } else {
+                  baseURL = baseURL.replace('*', '');
+                }
+              } else {
+                baseURL = baseURL.replace(':' + key.name, self.currentContextParams()[key.name]);
+              }
+            });
+          }
+          self.currentContextParams(null);
+        }
+        if (self.currentQueryString() !== null) {
+          baseURL += (baseURL.indexOf('?') > -1 ? '&' : '?') + self.currentQueryString();
+          self.currentQueryString(null);
+        }
+        baseURL = encodeURI(baseURL);
+        $.ajax({
+          url:
+            baseURL +
+            (baseURL.indexOf('?') > -1 ? '&' : '?') +
+            'is_embeddable=true' +
+            self.extraEmbeddableURLParams(),
+          beforeSend: function(xhr) {
+            xhr.setRequestHeader('X-Requested-With', 'Hue');
+          },
+          dataType: 'html',
+          success: function(response, status, xhr) {
+            const type = xhr.getResponseHeader('Content-Type');
+            if (type.indexOf('text/') > -1) {
+              window.clearAppIntervals(app);
+              huePubSub.clearAppSubscribers(app);
+              self.extraEmbeddableURLParams('');
+
+              self.processHeaders(response).done($rawHtml => {
+                if (window.SKIP_CACHE.indexOf(app) === -1) {
+                  self.embeddable_cache[app] = $rawHtml;
+                }
+                $('#embeddable_' + app).html($rawHtml);
+                huePubSub.publish('app.dom.loaded', app);
+                window.setTimeout(() => {
+                  self.isLoadingEmbeddable(false);
+                }, 0);
+              });
+            } else {
+              window.location.href = baseURL;
+            }
+          },
+          error: function(xhr) {
+            console.error('Route loading problem', xhr);
+            if ((xhr.status === 401 || xhr.status === 403) && app !== '403') {
+              self.loadApp('403');
+            } else if (app !== '500') {
+              self.loadApp('500');
+            } else {
+              $.jHueNotify.error(window.HUE_I18n.general.offlineOrError);
+            }
+          }
+        });
+      } else {
+        self.isLoadingEmbeddable(false);
+      }
+      window.document.title = 'Hue - ' + window.EMBEDDABLE_PAGE_URLS[app].title;
+      window.resumeAppIntervals(app);
+      huePubSub.resumeAppSubscribers(app);
+      $('.embeddable').hide();
+      $('#embeddable_' + app).show();
+      huePubSub.publish('app.gained.focus', app);
+      huePubSub.publish('resize.form.actions');
+    };
+
+    self.dropzoneError = function(filename) {
+      self.loadApp('importer');
+      self.getActiveAppViewModel(vm => {
+        vm.createWizard.source.path(DROPZONE_HOME_DIR + '/' + filename);
+      });
+      $('.dz-drag-hover').removeClass('dz-drag-hover');
+    };
+
+    const openImporter = function(path) {
+      self.loadApp('importer');
+      self.getActiveAppViewModel(vm => {
+        vm.createWizard.source.path(path);
+      });
+    };
+
+    self.dropzoneComplete = function(path) {
+      if (path.toLowerCase().endsWith('.csv')) {
+        openImporter(path);
+      } else {
+        huePubSub.publish('open.link', '/filebrowser/view=' + path);
+      }
+      $('.dz-drag-hover').removeClass('dz-drag-hover');
+    };
+
+    huePubSub.subscribe('open.in.importer', openImporter);
+
+    huePubSub.subscribe('assist.dropzone.complete', self.dropzoneComplete);
+
+    // prepend /hue to all the link on this page
+    $(window.IS_EMBEDDED ? '.hue-embedded-container a[href]' : 'a[href]').each(function() {
+      let link = $(this).attr('href');
+      if (link.startsWith('/') && !link.startsWith('/hue')) {
+        link = '/hue' + link;
+      }
+      $(this).attr('href', link);
+    });
+
+    if (window.IS_EMBEDDED) {
+      page.base(window.location.pathname + window.location.search);
+      page.baseSearch = window.location.search.replace('?', '');
+      if (!window.location.hash) {
+        window.location.hash = '#!/editor?type=impala';
+      }
+      page({ hashbang: true });
+    } else {
+      page.base('/hue');
+    }
+
+    const getUrlParameter = function(name) {
+      if (window.IS_EMBEDDED) {
+        if (~window.location.hash.indexOf('?')) {
+          const paramString = window.location.hash.substring(window.location.hash.indexOf('?'));
+          const params = paramString.split('&');
+          for (let i = 0; i < params.length; i++) {
+            if (~params[i].indexOf(name + '=')) {
+              return params[i].substring(name.length + 2);
+            }
+          }
+        }
+        return '';
+      } else {
+        return window.location.getParameter(name) || '';
+      }
+    };
+
+    self.lastContext = null;
+
+    let pageMapping = [
+      { url: '/403', app: '403' },
+      { url: '/500', app: '500' },
+      { url: '/about/', app: 'admin_wizard' },
+      { url: '/about/admin_wizard', app: 'admin_wizard' },
+      {
+        url: '/accounts/logout',
+        app: function() {
+          location.href = '/accounts/logout';
+        }
+      },
+      {
+        url: '/dashboard/admin/collections',
+        app: function(ctx) {
+          page('/home/?type=search-dashboard');
+        }
+      },
+      { url: '/dashboard/*', app: 'dashboard' },
+      { url: '/desktop/dump_config', app: 'dump_config' },
+      {
+        url: '/desktop/debug/threads',
+        app: function() {
+          self.loadApp('threads');
+          self.getActiveAppViewModel(viewModel => {
+            viewModel.fetchThreads();
+          });
+        }
+      },
+      {
+        url: '/desktop/metrics',
+        app: function() {
+          self.loadApp('metrics');
+          self.getActiveAppViewModel(viewModel => {
+            viewModel.fetchMetrics();
+          });
+        }
+      },
+      {
+        url: '/desktop/download_logs',
+        app: function() {
+          location.href = '/desktop/download_logs';
+        }
+      },
+      {
+        url: '/editor',
+        app: function() {
+          // Defer to allow window.location param update
+          _.defer(() => {
+            if (typeof self.embeddable_cache['editor'] === 'undefined') {
+              if (getUrlParameter('editor') !== '') {
+                self.extraEmbeddableURLParams('&editor=' + getUrlParameter('editor'));
+              } else if (getUrlParameter('type') !== '' && getUrlParameter('type') !== 'notebook') {
+                self.extraEmbeddableURLParams('&type=' + getUrlParameter('type'));
+              }
+              self.loadApp('editor');
+            } else {
+              self.loadApp('editor');
+              if (getUrlParameter('editor') !== '') {
+                self.getActiveAppViewModel(viewModel => {
+                  self.isLoadingEmbeddable(true);
+                  viewModel.openNotebook(getUrlParameter('editor')).always(() => {
+                    self.isLoadingEmbeddable(false);
+                  });
+                });
+              } else if (getUrlParameter('type') !== '') {
+                self.changeEditorType(getUrlParameter('type'));
+              }
+            }
+          });
+        }
+      },
+      {
+        url: '/notebook/editor',
+        app: function(ctx) {
+          page('/editor?' + ctx.querystring);
+        }
+      },
+      { url: '/filebrowser/view=*', app: 'filebrowser' },
+      { url: '/filebrowser/download=*', app: 'filebrowser' },
+      {
+        url: '/filebrowser/*',
+        app: function() {
+          page('/filebrowser/view=' + DROPZONE_HOME_DIR);
+        }
+      },
+      { url: '/hbase/', app: 'hbase' },
+      { url: '/help', app: 'help' },
+      {
+        url: '/home2*',
+        app: function(ctx) {
+          page(ctx.path.replace(/home2/gi, 'home'));
+        }
+      },
+      { url: '/home*', app: 'home' },
+      { url: '/catalog', app: 'catalog' },
+      { url: '/kafka/', app: 'kafka' },
+      { url: '/indexer/topics/*', app: 'kafka' },
+      { url: '/indexer/indexes/*', app: 'indexes' },
+      { url: '/indexer/', app: 'indexes' },
+      { url: '/indexer/importer/', app: 'importer' },
+      {
+        url: '/indexer/importer/prefill/*',
+        app: function(ctx) {
+          self.loadApp('importer');
+          self.getActiveAppViewModel(viewModel => {
+            const _params = ctx.path.match(
+              /\/indexer\/importer\/prefill\/?([^/]+)\/?([^/]+)\/?([^/]+)?/
+            );
+            if (!_params) {
+              console.warn('Could not match ' + ctx.path);
+            }
+            hueUtils.waitForVariable(viewModel.createWizard, () => {
+              hueUtils.waitForVariable(viewModel.createWizard.prefill, () => {
+                viewModel.createWizard.prefill.source_type(_params && _params[1] ? _params[1] : '');
+                viewModel.createWizard.prefill.target_type(_params && _params[2] ? _params[2] : '');
+                viewModel.createWizard.prefill.target_path(_params && _params[3] ? _params[3] : '');
+              });
+            });
+          });
+        }
+      },
+      {
+        url: '/jobbrowser/jobs/job_*',
+        app: function(ctx) {
+          page.redirect(
+            '/jobbrowser#!id=application_' + _.trimRight(ctx.params[0], '/').split('/')[0]
+          );
+        }
+      },
+      {
+        url: '/jobbrowser/jobs/application_*',
+        app: function(ctx) {
+          page.redirect(
+            '/jobbrowser#!id=application_' + _.trimRight(ctx.params[0], '/').split('/')[0]
+          );
+        }
+      },
+      { url: '/jobbrowser*', app: 'jobbrowser' },
+      { url: '/logs', app: 'logs' },
+      {
+        url: '/metastore',
+        app: function() {
+          page('/metastore/tables');
+        }
+      },
+      { url: '/metastore/*', app: 'metastore' },
+      {
+        url: '/notebook',
+        app: function(ctx) {
+          self.loadApp('notebook');
+          const notebookId = hueUtils.getSearchParameter('?' + ctx.querystring, 'notebook');
+          if (notebookId !== '') {
+            self.getActiveAppViewModel(viewModel => {
+              self.isLoadingEmbeddable(true);
+              viewModel.openNotebook(notebookId).always(() => {
+                self.isLoadingEmbeddable(false);
+              });
+            });
+          } else {
+            self.getActiveAppViewModel(viewModel => {
+              viewModel.newNotebook('notebook');
+            });
+          }
+        }
+      },
+      {
+        url: '/notebook/notebook',
+        app: function(ctx) {
+          page('/notebook?' + ctx.querystring);
+        }
+      },
+      {
+        url: '/notebook/notebooks',
+        app: function(ctx) {
+          page('/home/?' + ctx.querystring);
+        }
+      },
+      {
+        url: '/oozie/editor/bundle/list',
+        app: function(ctx) {
+          page('/home/?type=oozie-bundle');
+        }
+      },
+      { url: '/oozie/editor/bundle/*', app: 'oozie_bundle' },
+      {
+        url: '/oozie/editor/coordinator/list',
+        app: function(ctx) {
+          page('/home/?type=oozie-coordinator');
+        }
+      },
+      { url: '/oozie/editor/coordinator/*', app: 'oozie_coordinator' },
+      {
+        url: '/oozie/editor/workflow/list',
+        app: function(ctx) {
+          page('/home/?type=oozie-workflow');
+        }
+      },
+      { url: '/oozie/editor/workflow/*', app: 'oozie_workflow' },
+      { url: '/oozie/list_oozie_info', app: 'oozie_info' },
+      {
+        url: '/oozie/list_oozie_sla',
+        app: function() {
+          page.redirect('/jobbrowser/#!slas');
+        }
+      },
+      {
+        url: '/pig',
+        app: function() {
+          self.loadApp('editor');
+          self.changeEditorType('pig');
+        }
+      },
+      { url: '/search/*', app: 'dashboard' },
+      {
+        url: '/security/hdfs',
+        app: function(ctx) {
+          if (self.lastContext == null || ctx.path !== self.lastContext.path) {
+            self.loadApp('security_hdfs');
+          }
+          self.lastContext = ctx;
+        }
+      },
+      {
+        url: '/security/hive',
+        app: function(ctx) {
+          if (self.lastContext == null || ctx.path !== self.lastContext.path) {
+            self.loadApp('security_hive');
+          }
+          self.lastContext = ctx;
+        }
+      },
+      {
+        url: '/security/hive2',
+        app: function(ctx) {
+          if (self.lastContext == null || ctx.path !== self.lastContext.path) {
+            self.loadApp('security_hive2');
+          }
+          self.lastContext = ctx;
+        }
+      },
+      {
+        url: '/security/solr',
+        app: function(ctx) {
+          if (self.lastContext == null || ctx.path !== self.lastContext.path) {
+            self.loadApp('security_solr');
+          }
+          self.lastContext = ctx;
+        }
+      },
+      {
+        url: '/security',
+        app: function() {
+          page('/security/hive');
+        }
+      },
+      { url: '/sqoop', app: 'sqoop' },
+      { url: '/jobsub', app: 'jobsub' },
+      { url: '/useradmin/configurations/', app: 'useradmin_configurations' },
+      { url: '/useradmin/groups/', app: 'useradmin_groups' },
+      { url: '/useradmin/groups/new', app: 'useradmin_newgroup' },
+      { url: '/useradmin/groups/edit/:group', app: 'useradmin_editgroup' },
+      { url: '/useradmin/permissions/', app: 'useradmin_permissions' },
+      { url: '/useradmin/permissions/edit/*', app: 'useradmin_editpermission' },
+      { url: '/useradmin/users/', app: 'useradmin_users' },
+      { url: '/useradmin/users/add_ldap_users', app: 'useradmin_addldapusers' },
+      { url: '/useradmin/users/add_ldap_groups', app: 'useradmin_addldapgroups' },
+      { url: '/useradmin/users/edit/:user', app: 'useradmin_edituser' },
+      { url: '/useradmin/users/new', app: 'useradmin_newuser' },
+      { url: '/useradmin/users/', app: 'useradmin_users' },
+      { url: '/useradmin', app: 'useradmin_users' }
+    ];
+
+    window.OTHER_APPS.forEach(otherApp => {
+      pageMapping.push({
+        url: '/' + otherApp + '*',
+        app: ctx => {
+          self.currentContextParams(ctx.params);
+          self.currentQueryString(ctx.querystring);
+          self.loadApp(otherApp, true);
+        }
+      });
+    });
+
+    if (typeof window.HUE_EMBEDDED_PAGE_MAPPINGS !== 'undefined') {
+      pageMapping = pageMapping.concat(window.HUE_EMBEDDED_PAGE_MAPPINGS);
+    }
+
+    pageMapping.forEach(mapping => {
+      page(
+        mapping.url,
+        _.isFunction(mapping.app)
+          ? mapping.app
+          : ctx => {
+              self.currentContextParams(ctx.params);
+              self.currentQueryString(ctx.querystring);
+              self.loadApp(mapping.app);
+            }
+      );
+    });
+
+    huePubSub.subscribe('cluster.config.set.config', clusterConfig => {
+      page('/', () => {
+        page(clusterConfig['main_button_action'].page);
+      });
+      page('*', ctx => {
+        console.error('Route not found', ctx);
+        self.loadApp('404');
+      });
+      page();
+    });
+
+    huePubSub.subscribe('open.link', href => {
+      if (href) {
+        const prefix = window.IS_EMBEDDED ? '' : '/hue';
+        if (href.startsWith('/') && !href.startsWith(prefix)) {
+          page(prefix + href);
+        } else {
+          page(href);
+        }
+      } else {
+        console.warn('Received an open.link without href.');
+      }
+    });
+  }
+}
+
+export default OnePageViewModel;

+ 138 - 0
desktop/core/src/desktop/js/sideBarViewModel.js

@@ -0,0 +1,138 @@
+// 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 huePubSub from 'utils/huePubSub';
+
+class SideBarViewModel {
+  constructor(onePageViewModel, topNavViewModel) {
+    const self = this;
+
+    self.items = ko.observableArray();
+
+    self.pocClusterMode = topNavViewModel.pocClusterMode;
+
+    huePubSub.subscribe('cluster.config.set.config', clusterConfig => {
+      const items = [];
+
+      if (clusterConfig && clusterConfig['app_config']) {
+        const appsItems = [];
+        const appConfig = clusterConfig['app_config'];
+        if (appConfig['editor']) {
+          let editor = null;
+          if (
+            clusterConfig['main_button_action'] &&
+            clusterConfig['main_button_action'].page.indexOf('/editor') === 0
+          ) {
+            editor = clusterConfig['main_button_action'];
+          }
+
+          if (!editor) {
+            const defaultEditor = appConfig['editor']['default_sql_interpreter'];
+            if (defaultEditor) {
+              const foundEditor = appConfig['editor']['interpreters'].filter(interpreter => {
+                return interpreter.type === defaultEditor;
+              });
+              if (foundEditor.length === 1) {
+                editor = foundEditor[0];
+              }
+            }
+          }
+
+          if (!editor && appConfig['editor']['interpreters'].length > 1) {
+            editor = appConfig['editor']['interpreters'][1];
+          }
+
+          if (editor) {
+            appsItems.push({
+              displayName: window.HUE_I18n.nav.editor,
+              url: editor['page'],
+              icon: 'editor'
+            });
+          } else {
+            appsItems.push({
+              displayName: appConfig['editor']['displayName'],
+              url: appConfig['editor']['page'],
+              icon: 'editor'
+            });
+          }
+        }
+        ['dashboard', 'scheduler'].forEach(appName => {
+          if (appConfig[appName]) {
+            appsItems.push({
+              displayName: appConfig[appName]['displayName'],
+              url: appConfig[appName]['page'],
+              icon: appName
+            });
+          }
+        });
+        if (appsItems.length > 0) {
+          items.push({
+            isCategory: true,
+            displayName: window.HUE_I18n.nav.apps,
+            children: appsItems
+          });
+        }
+
+        const browserItems = [];
+        browserItems.push({
+          displayName: window.HUE_I18n.nav.documents,
+          url: '/home/',
+          icon: 'documents'
+        });
+        if (appConfig['browser'] && appConfig['browser']['interpreters']) {
+          appConfig['browser']['interpreters'].forEach(browser => {
+            browserItems.push({
+              displayName: browser.displayName,
+              url: browser.page,
+              icon: browser.type
+            });
+          });
+        }
+        if (browserItems.length > 0) {
+          items.push({
+            isCategory: true,
+            displayName: window.HUE_I18n.nav.browsers,
+            children: browserItems
+          });
+        }
+
+        const sdkItems = [];
+        if (appConfig['sdkapps'] && appConfig['sdkapps']['interpreters']) {
+          appConfig['sdkapps']['interpreters'].forEach(browser => {
+            sdkItems.push({
+              displayName: browser['displayName'],
+              url: browser['page']
+            });
+          });
+        }
+        if (sdkItems.length > 0) {
+          items.push({
+            isCategory: true,
+            displayName: appConfig['sdkapps']['displayName'],
+            children: sdkItems
+          });
+        }
+      }
+
+      self.items(items);
+    });
+
+    self.leftNavVisible = topNavViewModel.leftNavVisible;
+    self.onePageViewModel = onePageViewModel;
+  }
+}
+
+export default SideBarViewModel;

+ 163 - 0
desktop/core/src/desktop/js/sidePanelViewModel.js

@@ -0,0 +1,163 @@
+// 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 apiHelper from 'api/apiHelper';
+import hueAnalytics from 'utils/hueAnalytics';
+import huePubSub from 'utils/huePubSub';
+
+class SidePanelViewModel {
+  constructor() {
+    const self = this;
+    self.assistWithoutStorage = ko.observable(false);
+    self.leftAssistVisible = ko.observable(
+      apiHelper.getFromTotalStorage('assist', 'left_assist_panel_visible', true)
+    );
+    self.leftAssistVisible.subscribe(val => {
+      if (!self.assistWithoutStorage()) {
+        apiHelper.setInTotalStorage('assist', 'left_assist_panel_visible', val);
+      }
+      hueAnalytics.convert('hue', 'leftAssistVisible/' + val);
+      window.setTimeout(() => {
+        huePubSub.publish('split.panel.resized');
+        $(window).trigger('resize');
+      }, 0);
+    });
+
+    self.rightAssistVisible = ko.observable(
+      apiHelper.getFromTotalStorage('assist', 'right_assist_panel_visible', true)
+    );
+    self.rightAssistVisible.subscribe(val => {
+      if (!self.assistWithoutStorage()) {
+        apiHelper.setInTotalStorage('assist', 'right_assist_panel_visible', val);
+      }
+      hueAnalytics.convert('hue', 'rightAssistVisible/' + val);
+      window.setTimeout(() => {
+        huePubSub.publish('reposition.scroll.anchor.up');
+        huePubSub.publish('split.panel.resized');
+        $(window).trigger('resize');
+      }, 0);
+    });
+    self.rightAssistAvailable = ko.observable(false);
+
+    huePubSub.subscribe('assist.highlight.risk.suggestions', () => {
+      if (self.rightAssistAvailable() && !self.rightAssistVisible()) {
+        self.rightAssistVisible(true);
+      }
+    });
+
+    huePubSub.subscribe('set.current.app.name', appName => {
+      if (appName === 'dashboard') {
+        self.rightAssistAvailable(true);
+      } else if (appName !== 'editor' && appName !== 'notebook') {
+        self.rightAssistAvailable(false);
+      }
+    });
+
+    huePubSub.subscribe('active.snippet.type.changed', snippetType => {
+      self.rightAssistAvailable(
+        snippetType === 'impala' || snippetType === 'hive' || snippetType === 'pig'
+      );
+    });
+
+    huePubSub.publish('get.current.app.name');
+
+    self.activeAppViewModel = ko.observable();
+    self.currentApp = ko.observable('');
+    self.templateApp = ko.pureComputed(() => {
+      if (['editor', 'notebook'].indexOf(self.currentApp()) > -1) {
+        return self.currentApp();
+      } else {
+        return '';
+      }
+    });
+
+    self.contextPanelVisible = ko.observable(false);
+    self.contextPanelVisible.subscribe(() => {
+      let $el = $('.snippet .ace-editor:visible');
+      if ($el.length === 0) {
+        $el = $('.content-panel:visible');
+      }
+      $('.context-panel')
+        .width($el.width())
+        .css('left', $el.offset().left);
+    });
+
+    self.sessionsAvailable = ko.observable(false);
+
+    self.activeAppViewModel.subscribe(viewModel => {
+      self.sessionsAvailable(typeof viewModel.selectedNotebook !== 'undefined');
+    });
+
+    huePubSub.subscribe('context.panel.visible', visible => {
+      self.contextPanelVisible(visible);
+    });
+
+    huePubSub.subscribe('set.current.app.view.model', self.activeAppViewModel);
+    huePubSub.subscribe('app.dom.loaded', self.currentApp);
+
+    huePubSub.publish('get.current.app.view.model');
+
+    let previousVisibilityValues = {};
+    huePubSub.subscribe('both.assists.hide', withoutStorage => {
+      previousVisibilityValues = {
+        left: self.leftAssistVisible(),
+        right: self.rightAssistVisible()
+      };
+      self.assistWithoutStorage(withoutStorage);
+      self.leftAssistVisible(false);
+      self.rightAssistVisible(false);
+      window.setTimeout(() => {
+        self.assistWithoutStorage(false);
+      }, 0);
+    });
+
+    huePubSub.subscribe('both.assists.show', withoutStorage => {
+      self.assistWithoutStorage(withoutStorage);
+      self.leftAssistVisible(previousVisibilityValues.left);
+      self.rightAssistVisible(previousVisibilityValues.right);
+      window.setTimeout(() => {
+        self.assistWithoutStorage(false);
+      }, 0);
+    });
+
+    huePubSub.subscribe('right.assist.hide', withoutStorage => {
+      previousVisibilityValues = {
+        left: self.leftAssistVisible(),
+        right: self.rightAssistVisible()
+      };
+      self.assistWithoutStorage(withoutStorage);
+      self.rightAssistVisible(false);
+      window.setTimeout(() => {
+        self.assistWithoutStorage(false);
+      }, 0);
+    });
+
+    huePubSub.subscribe('right.assist.show', () => {
+      if (!self.rightAssistVisible()) {
+        self.rightAssistVisible(true);
+      }
+    });
+
+    huePubSub.subscribe('left.assist.show', () => {
+      if (!self.leftAssistVisible()) {
+        self.leftAssistVisible(true);
+      }
+    });
+  }
+}
+
+export default SidePanelViewModel;

+ 136 - 0
desktop/core/src/desktop/js/topNavViewModel.js

@@ -0,0 +1,136 @@
+// 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 apiHelper from 'api/apiHelper';
+import hueAnalytics from 'utils/hueAnalytics';
+import huePubSub from 'utils/huePubSub';
+
+class TopNavViewModel {
+  constructor(onePageViewModel) {
+    const self = this;
+    self.onePageViewModel = onePageViewModel;
+    self.leftNavVisible = ko.observable(false);
+    self.leftNavVisible.subscribe(val => {
+      huePubSub.publish('left.nav.open.toggle', val);
+      hueAnalytics.convert('hue', 'leftNavVisible/' + val);
+      if (val) {
+        // Defer or it will be triggered by the open click
+        window.setTimeout(() => {
+          $(document).one('click', () => {
+            if (self.leftNavVisible()) {
+              self.leftNavVisible(false);
+            }
+          });
+        }, 0);
+      }
+    });
+
+    huePubSub.subscribe('hue.toggle.left.nav', self.leftNavVisible);
+
+    // TODO: Drop. Just for PoC
+    self.pocClusterMode = ko.observable();
+    apiHelper.withTotalStorage('topNav', 'multiCluster', self.pocClusterMode, 'dw');
+    huePubSub.subscribe('set.multi.cluster.mode', self.pocClusterMode);
+
+    self.onePageViewModel.currentApp.subscribe(() => {
+      self.leftNavVisible(false);
+    });
+
+    self.mainQuickCreateAction = ko.observable();
+    self.quickCreateActions = ko.observableArray();
+
+    self.hasJobBrowser = ko.observable(true);
+
+    huePubSub.subscribe('cluster.config.set.config', clusterConfig => {
+      if (clusterConfig && clusterConfig['main_button_action']) {
+        const topApp = clusterConfig['main_button_action'];
+        self.mainQuickCreateAction({
+          displayName: topApp.buttonName,
+          icon: topApp.type,
+          tooltip: topApp.tooltip,
+          url: topApp.page
+        });
+      } else {
+        self.mainQuickCreateAction(undefined);
+      }
+
+      if (clusterConfig && clusterConfig['button_actions']) {
+        const apps = [];
+        const buttonActions = clusterConfig['button_actions'];
+        buttonActions.forEach(app => {
+          const interpreters = [];
+          let toAddDivider = false;
+          let dividerAdded = false;
+          let lastInterpreter = null;
+          $.each(app['interpreters'], (index, interpreter) => {
+            // Promote the first catagory of interpreters
+            if (!dividerAdded) {
+              toAddDivider =
+                (app.name === 'editor' || app.name === 'dashboard') &&
+                (lastInterpreter != null && lastInterpreter.is_sql != interpreter.is_sql);
+            }
+            interpreters.push({
+              displayName: interpreter.displayName,
+              dividerAbove: toAddDivider,
+              icon: interpreter.type,
+              url: interpreter.page
+            });
+            lastInterpreter = interpreter;
+            if (toAddDivider) {
+              dividerAdded = true;
+              toAddDivider = false;
+            }
+          });
+
+          if (window.SHOW_ADD_MORE_EDITORS && app.name === 'editor') {
+            interpreters.push({
+              displayName: window.HUE_I18n.general.addMore,
+              dividerAbove: true,
+              href: 'http://gethue.com/sql-editor/'
+            });
+          }
+
+          apps.push({
+            displayName: app.displayName,
+            icon: app.name,
+            isCategory: interpreters.length > 0,
+            children: interpreters,
+            url: app.page
+          });
+        });
+
+        self.quickCreateActions(apps);
+      } else {
+        self.quickCreateActions([]);
+      }
+
+      self.hasJobBrowser(
+        clusterConfig &&
+          clusterConfig['app_config'] &&
+          clusterConfig['app_config']['browser'] &&
+          (clusterConfig['app_config']['browser']['interpreter_names'].indexOf('yarn') != -1 ||
+            clusterConfig['app_config']['browser']['interpreter_names'].indexOf('dataeng') != -1)
+      );
+    });
+
+    huePubSub.subscribe('hue.new.default.app', () => {
+      huePubSub.publish('cluster.config.refresh.config');
+    });
+  }
+}
+
+export default TopNavViewModel;

+ 14 - 1
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -18,7 +18,7 @@
   from django.utils.translation import ugettext as _
 
   from desktop import conf
-  from desktop.conf import IS_EMBEDDED, IS_K8S_ONLY
+  from desktop.conf import IS_EMBEDDED, IS_K8S_ONLY, IS_MULTICLUSTER_ONLY
   from desktop.models import hue_version
 
   from beeswax.conf import LIST_PARTITIONS_LIMIT
@@ -35,6 +35,8 @@
     optimizer: ${ OPTIMIZER.CACHEABLE_TTL.get() }
   };
 
+  window.DEV = '${ conf.DEV.get() }' === 'True';
+
   %if request and request.COOKIES and request.COOKIES.get('csrftoken', '') != '':
     window.CSRF_TOKEN = '${request.COOKIES.get('csrftoken')}';
   %else:
@@ -56,6 +58,7 @@
 
   window.HUE_CONTAINER = '${ IS_EMBEDDED.get() }' === 'True' ? '.hue-embedded-container' : 'body';
 
+  window.IS_MULTICLUSTER_ONLY = '${ IS_MULTICLUSTER_ONLY.get() }' === 'True';
   window.IS_EMBEDDED = '${ IS_EMBEDDED.get() }' === 'True';
   window.IS_K8S_ONLY = '${ IS_K8S_ONLY.get() }' === 'True';
   window.JB_HEADER_CHECK_INTERVAL_IN_MILLIS = 30000;
@@ -154,6 +157,12 @@
     metastore: {
       errorRefreshingTableStats: '${_('An error occurred refreshing the table stats. Please try again.')}'
     },
+    nav: {
+      apps: '${ _('Apps') }',
+      browsers: '${ _('Browsers') }',
+      documents: '${ _('Documents') }',
+      editor: '${ _('Editor') }',
+    },
     selectize: {
       choose: "${ _('Choose...') }",
       editTags: "${ _('Edit tags') }"
@@ -191,6 +200,10 @@
       compilation: "${ _('Compilation') }",
       planning: "${ _('Planning') }",
       risks: "${ _('Risks') }"
+    },
+    general: {
+      addMore: '${ _('Add more...') }',
+      offlineOrError: '${ _('It looks like you are offline or an unknown error happened. Please refresh the page.') }'
     }
   };
 

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 77 - 1195
desktop/core/src/desktop/templates/hue.mako


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
webpack-stats.json


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels