import { get, set } from '@ember/object'; import Route from '@ember/routing/route'; import { cancel, later, scheduleOnce } from '@ember/runloop'; import { inject as service } from '@ember/service'; import $ from 'jquery'; import { all as PromiseAll, resolve } from 'rsvp'; import { compare, isDevBuild } from 'shared/utils/parse-version'; import Preload from 'ui/mixins/preload'; import C from 'ui/utils/constants'; const CHECK_AUTH_TIMER = 60 * 10 * 1000; export default Route.extend(Preload, { access: service(), globalStore: service(), clusterStore: service(), cookies: service(), language: service('user-language'), modalService: service('modal'), prefs: service(), scope: service(), settings: service(), storeReset: service(), intl: service(), growl: service(), userTheme: service('user-theme'), testTimer: null, shortcuts: { // Global 'c': 'gotoc', 'u': 'gotou', 'shift+k': 'gotoK', 'shift+p': 'gotoP', 'shift+t': 'gotoT', // Cluster or Proejct '`': 'quake', 'shift+`': 'quake', 'm': 'gotom', // Cluster 'd': 'gotod', 'n': 'goton', 'p': 'gotop', 'shift+v': 'gotoV', // Project 'w': 'gotow', 'b': 'gotob', 's': 'gotos', 'h': 'gotoh', 'v': 'gotov', 'a': 'gotoa', // Other // 'g': Defined in subroutes 't': 'nextTab', '/': 'search', 'shift+/': 'help', 'backspace': 'delete', 'delete': 'delete', }, beforeModel() { set(this, 'testTimer', later(() => { this.testAuthToken(); }, CHECK_AUTH_TIMER)); return PromiseAll([ this.testAuthToken(), this.loadPublicSettings(), ]).then(() => { if (get(this, 'access.mustChangePassword')) { this.transitionTo('update-password'); } if (get(this, 'settings.serverUrlIsEmpty')) { get(this, 'router').transitionTo('update-critical-settings'); } }); }, model(params, transition) { get(this, 'session').set(C.SESSION.BACK_TO, undefined); const isPopup = this.controllerFor('application').get('isPopup'); return get(this, 'scope').startSwitchToGlobal(!isPopup) .then(() => { const list = [ this.loadSchemas('globalStore'), this.loadClusters(), this.loadProjects(), this.loadPreferences(), ]; const globalStore = get(this, 'globalStore'); if ( !isPopup ) { list.addObjects([ this.preload('node', 'globalStore', { url: 'nodes' }), this.preload('nodePool', 'globalStore', { url: 'nodePools' }), this.preload('noedTemplates', 'globalStore', { url: 'nodeTemplates' }), this.preload('roleTemplate', 'globalStore', { url: 'roleTemplates', filter: { enabled: true, hidden: false } }), this.preload('clusterRoleTemplateBindings', 'globalStore', { url: 'clusterRoleTemplateBindings' }), this.preload('projectRoleTemplateBinding', 'globalStore', { url: 'projectRoleTemplateBinding' }), this.preload('globalRole', 'globalStore', { url: 'globalRole' }), this.preload('authConfig', 'globalStore', { url: 'authConfigs' }), this.preload('globalRoleBinding', 'globalStore', { url: 'globalRoleBinding' }), this.preload('user', 'globalStore', { url: 'user' }), this.preload('features', 'globalStore', { url: 'features' }), globalStore.findAll('principal').then((principals) => { const me = principals.filter((p) => p.me === true); if (me.length > 0) { set(this, 'access.principal', get(me, 'firstObject')); } principals.forEach((p) => { set(p, '_mine', true); }); }), ]); } return PromiseAll(list) }) .catch((err) => this.loadingError(err, transition)); }, afterModel() { return get(this, 'scope').finishSwitchToGlobal(); }, activate() { this._super(...arguments); if ( this.controllerFor('application').get('isPopup') ) { $('BODY').addClass('popup'); return; } // Don't show any modals when embedded const isEmbedded = window.top !== window; if ( !get(this, `cookies.${ C.COOKIE.REDIRECTED }`) ) { // Send users to dashboard, if there's no redirect cookie, and not embedded // So that if you're on <2.6 in Ember, upgrade, and get reloaded you end up in Dashboard. this.cookies.set(C.COOKIE.REDIRECTED, true); // If isEmbedded then you're already in Dashboard, so just set the cookie. if ( !isEmbedded ) { window.location.href = get(this, 'scope.dashboardBase'); return; } } if ( get(this, 'settings.isRancher') && !isEmbedded) { const telemetry = get(this, `settings.${ C.SETTING.TELEMETRY }`); const form = get(this, `settings.${ C.SETTING.FEEDBACK_FORM }`); const seenWhatsNew = get(this, `prefs.${ C.PREFS.SEEN_WHATS_NEW }`); const version = get(this, 'settings.rancherVersion'); const isDev = isDevBuild(version); if ( get(this, 'access.admin') && (!telemetry || telemetry === 'prompt') ) { // Show the telemetry opt-in if not set scheduleOnce('afterRender', this.modalService, 'toggleModal', 'modal-telemetry'); } else if ( form && !get(this, `prefs.${ C.PREFS.FEEDBACK }`) ) { // Try.rancher feedback scheduleOnce('afterRender', this.modalService, 'toggleModal', 'modal-feedback'); } else if ( !isDev && (!seenWhatsNew || compare(seenWhatsNew, C.WHATS_NEW_VERSION) < 0) ) { // What's new scheduleOnce('afterRender', this.modalService, 'toggleModal', 'modal-whats-new'); } } }, deactivate() { this._super(); const scope = get(this, 'scope'); scope.startSwitchToNothing().then(() => { scope.finishSwitchToNothing(); }); cancel(get(this, 'testTimer')); }, actions: { error(err, transition) { // Unauthorized error, send back to login screen if ( err && err.status === 401 ) { this.send('logout', transition); return false; } else { // Bubble up return true; } }, becameReady() { // This absorbs became ready in case it's not handled elsewhere }, showAbout() { this.controllerFor('application').set('showAbout', true); }, switchProject(projectId, transitionTo = 'authenticated', transitionArgs) { // console.log('Switch to Project:' + projectId); PromiseAll([ get(this, 'scope.subscribeProject').disconnect(), ]).then(() => { // console.log('Switch is disconnected'); this.send('finishSwitch', `project:${ projectId }`, transitionTo, transitionArgs); }); }, finishSwitch(id, transitionTo, transitionArgs) { // console.log('Switch finishing'); const cookies = get(this, 'cookies'); var [whichCookie, idOut] = id.split(':'); get(this, 'storeReset').reset(); if ( transitionTo ) { let args = (transitionArgs || []).slice(); args.unshift(transitionTo); this.transitionTo.apply(this, args); } cookies.set(C.COOKIE[whichCookie.toUpperCase()], idOut); this.refresh(); // console.log('Switch finished'); }, help() { get(this, 'modalService').toggleModal('modal-shortcuts', { escToClose: true }); }, // Special nextTab() { if ( $('.tab-nav').length ) { let cur = $('.tab-nav .active'); let next = cur.closest('li').next() .find('a'); if ( next && next.length ) { next.click(); } else { next = $('.tab-nav li:first-child a'); if ( next && next.length ) { next.click(); } } } }, search(event) { let elem = $("INPUT[type='search']")[0]; if ( elem ) { event.stopPropagation(); event.preventDefault(); elem.focus(); } }, delete() { $('.bulk-actions .icon-trash').closest('a') .click(); }, // Global gotoc() { this._gotoRoute('global-admin.clusters.index', 'global'); }, gotou() { this._gotoRoute('global-admin.security.accounts.users', 'global'); }, gotoK() { this._gotoRoute('authenticated.apikeys', 'global'); }, gotoP() { this._gotoRoute('authenticated.prefs', 'global'); }, gotoT() { this._gotoRoute('nodes.node-templates', 'global'); }, // Cluster or Project gotom() { this._gotoMembers(); }, // Cluster gotod() { this._gotoRoute('authenticated.cluster.index', 'cluster'); }, goton() { this._gotoRoute('authenticated.cluster.nodes', 'cluster'); }, gotop() { this._gotoRoute('authenticated.cluster.projects', 'cluster'); }, gotoV() { this._gotoRoute('authenticated.cluster.storage.persistent-volumes', 'cluster'); }, quake() { const clusterId = get(this, 'scope.currentCluster.id'); if ( clusterId ) { this.get('modalService').toggleModal('modal-kubectl', {}); } }, // Project gotow() { this._gotoRoute('containers', 'project'); }, gotob() { this._gotoRoute('ingresses', 'project'); }, gotos() { this._gotoRoute('authenticated.project.dns', 'project'); }, gotoh() { this._gotoRoute('authenticated.project.hpa', 'project'); }, gotov() { this._gotoRoute('volumes', 'project'); }, gotoa() { this._gotoRoute('apps-tab.index', 'project'); }, }, testAuthToken() { return get(this, 'access').testAuth() .catch(() => { set(this, `session.${ C.SESSION.BACK_TO }`, window.location.href); this.transitionTo('login'); this.send('logout', null); }); }, loadPreferences() { return get(this, 'globalStore').find('preference', null, { url: 'preference' }) .then((res) => { get(this, 'language').initLanguage(true); get(this, 'userTheme').setupTheme(); if (get(this, `prefs.${ C.PREFS.I_HATE_SPINNERS }`)) { $('BODY').addClass('i-hate-spinners'); } return res; }); }, loadClusters() { return get(this, 'scope').getAllClusters(); }, loadProjects() { return get(this, 'scope').getAllProjects(); }, loadPublicSettings() { return get(this, 'globalStore').find('setting', null, { url: 'settings' }); }, loadSecrets() { if ( get(this, 'store').getById('schema', 'secret') ) { return get(this, 'store').find('secret'); } else { return resolve(); } }, _gotoRoute(name, scope = 'global') { // Don't go to routes if in a form page, because you can easily not be on an input // and leave the page accidentally. if ( $('FORM').length > 0 ) { return; } const clusterId = get(this, 'scope.currentCluster.id'); const projectId = get(this, 'scope.currentProject.id'); if ( scope === 'cluster' && clusterId ) { this.transitionTo(name, clusterId); } else if ( scope === 'project' && projectId ) { this.transitionTo(name, projectId); } else if ( scope === 'global' ) { this.transitionTo(name); } }, _gotoMembers() { const clusterId = get(this, 'scope.currentCluster.id'); const projectId = get(this, 'scope.currentProject.id'); if ( projectId ) { this._gotoRoute('authenticated.project.security.members.index', 'project'); } else if ( clusterId ) { this._gotoRoute('authenticated.cluster.security.members.index', 'cluster'); } }, });