clusters_bundle.js 9.0 KB
Newer Older
1 2
import Visibility from 'visibilityjs';
import Vue from 'vue';
3
import PersistentUserCallout from '../persistent_user_callout';
4 5 6 7 8
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
9 10 11 12 13 14 15
import {
  APPLICATION_STATUS,
  REQUEST_SUBMITTED,
  REQUEST_FAILURE,
  UPGRADE_REQUESTED,
  UPGRADE_REQUEST_FAILURE,
} from './constants';
16 17
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
18
import Applications from './components/applications.vue';
E
Eric Eastwood 已提交
19
import setupToggleButtons from '../toggle_buttons';
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

/**
 * Cluster page has 2 separate parts:
 * Toggle button and applications section
 *
 * - Polling status while creating or scheduled
 * - Update status area with the response result
 */

export default class Clusters {
  constructor() {
    const {
      statusPath,
      installHelmPath,
      installIngressPath,
A
Amit Rathi 已提交
35
      installCertManagerPath,
36
      installRunnerPath,
37
      installJupyterPath,
C
Chris Baumbauer 已提交
38
      installKnativePath,
39
      installPrometheusPath,
40
      managePrometheusPath,
41
      hasRbac,
42
      clusterType,
43 44 45
      clusterStatus,
      clusterStatusReason,
      helpPath,
46
      ingressHelpPath,
47
      ingressDnsHelpPath,
48 49 50
    } = document.querySelector('.js-edit-cluster-form').dataset;

    this.store = new ClustersStore();
51
    this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
52
    this.store.setManagePrometheusPath(managePrometheusPath);
53 54
    this.store.updateStatus(clusterStatus);
    this.store.updateStatusReason(clusterStatusReason);
55
    this.store.updateRbac(hasRbac);
56 57 58
    this.service = new ClustersService({
      endpoint: statusPath,
      installHelmEndpoint: installHelmPath,
K
Kamil Trzcinski 已提交
59
      installIngressEndpoint: installIngressPath,
A
Amit Rathi 已提交
60
      installCertManagerEndpoint: installCertManagerPath,
61
      installRunnerEndpoint: installRunnerPath,
62
      installPrometheusEndpoint: installPrometheusPath,
63
      installJupyterEndpoint: installJupyterPath,
C
Chris Baumbauer 已提交
64
      installKnativeEndpoint: installKnativePath,
65 66 67
    });

    this.installApplication = this.installApplication.bind(this);
68
    this.showToken = this.showToken.bind(this);
69 70 71 72 73 74

    this.errorContainer = document.querySelector('.js-cluster-error');
    this.successContainer = document.querySelector('.js-cluster-success');
    this.creatingContainer = document.querySelector('.js-cluster-creating');
    this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
    this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
75 76
    this.showTokenButton = document.querySelector('.js-show-cluster-token');
    this.tokenField = document.querySelector('.js-cluster-token');
77

78
    Clusters.initDismissableCallout();
79
    initSettingsPanels();
E
Eric Eastwood 已提交
80
    setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
81
    this.initApplications(clusterType);
82 83

    if (this.store.state.status !== 'created') {
84
      this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
85 86 87 88 89 90 91 92
    }

    this.addListeners();
    if (statusPath) {
      this.initPolling();
    }
  }

93
  initApplications(type) {
94
    const { store } = this;
95 96 97 98 99 100 101 102 103 104
    const el = document.querySelector('#js-cluster-applications');

    this.applications = new Vue({
      el,
      data() {
        return {
          state: store.state,
        };
      },
      render(createElement) {
105
        return createElement(Applications, {
106
          props: {
107
            type,
108 109
            applications: this.state.applications,
            helpPath: this.state.helpPath,
110
            ingressHelpPath: this.state.ingressHelpPath,
111
            managePrometheusPath: this.state.managePrometheusPath,
112
            ingressDnsHelpPath: this.state.ingressDnsHelpPath,
113
            rbac: this.state.rbac,
114 115 116 117 118 119
          },
        });
      },
    });
  }

120 121
  static initDismissableCallout() {
    const callout = document.querySelector('.js-cluster-security-warning');
122
    PersistentUserCallout.factory(callout);
123 124
  }

125
  addListeners() {
F
Filipa Lacerda 已提交
126
    if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
127
    eventHub.$on('installApplication', this.installApplication);
128 129 130
    eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
    eventHub.$on('upgradeFailed', appId => this.upgradeFailed(appId));
    eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
131 132 133
  }

  removeListeners() {
F
Filipa Lacerda 已提交
134
    if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
135
    eventHub.$off('installApplication', this.installApplication);
136 137 138
    eventHub.$off('upgradeApplication', this.upgradeApplication);
    eventHub.$off('upgradeFailed', this.upgradeFailed);
    eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
139 140 141 142 143 144 145 146 147 148 149 150 151
  }

  initPolling() {
    this.poll = new Poll({
      resource: this.service,
      method: 'fetchData',
      successCallback: data => this.handleSuccess(data),
      errorCallback: () => Clusters.handleError(),
    });

    if (!Visibility.hidden()) {
      this.poll.makeRequest();
    } else {
152 153
      this.service
        .fetchData()
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
        .then(data => this.handleSuccess(data))
        .catch(() => Clusters.handleError());
    }

    Visibility.change(() => {
      if (!Visibility.hidden() && !this.destroyed) {
        this.poll.restart();
      } else {
        this.poll.stop();
      }
    });
  }

  static handleError() {
    Flash(s__('ClusterIntegration|Something went wrong on our end.'));
  }

  handleSuccess(data) {
172
    const prevStatus = this.store.state.status;
173 174
    const prevApplicationMap = Object.assign({}, this.store.state.applications);

175
    this.store.updateStateFromServer(data.data);
176

177
    this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
178
    this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
179 180
  }

181 182 183 184 185
  showToken() {
    const type = this.tokenField.getAttribute('type');

    if (type === 'password') {
      this.tokenField.setAttribute('type', 'text');
186
      this.showTokenButton.textContent = s__('ClusterIntegration|Hide');
187 188
    } else {
      this.tokenField.setAttribute('type', 'password');
189
      this.showTokenButton.textContent = s__('ClusterIntegration|Show');
190 191 192
    }
  }

193 194 195 196 197 198 199 200
  hideAll() {
    this.errorContainer.classList.add('hidden');
    this.successContainer.classList.add('hidden');
    this.creatingContainer.classList.add('hidden');
  }

  checkForNewInstalls(prevApplicationMap, newApplicationMap) {
    const appTitles = Object.keys(newApplicationMap)
201 202 203 204 205 206
      .filter(
        appId =>
          newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
          prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
          prevApplicationMap[appId].status !== null,
      )
207 208 209
      .map(appId => newApplicationMap[appId].title);

    if (appTitles.length > 0) {
210 211 212 213 214 215
      const text = sprintf(
        s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
        {
          appList: appTitles.join(', '),
        },
      );
E
Eric Eastwood 已提交
216
      Flash(text, 'notice', this.successApplicationContainer);
217 218 219
    }
  }

220
  updateContainer(prevStatus, status, error) {
221
    this.hideAll();
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239

    // We poll all the time but only want the `created` banner to show when newly created
    if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
      switch (status) {
        case 'created':
          this.successContainer.classList.remove('hidden');
          break;
        case 'errored':
          this.errorContainer.classList.remove('hidden');
          this.errorReasonContainer.textContent = error;
          break;
        case 'scheduled':
        case 'creating':
          this.creatingContainer.classList.remove('hidden');
          break;
        default:
          this.hideAll();
      }
240 241 242
    }
  }

243 244
  installApplication(data) {
    const appId = data.id;
245
    this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUBMITTED);
246
    this.store.updateAppProperty(appId, 'requestReason', null);
247 248 249 250 251 252 253 254 255 256
    this.store.updateAppProperty(appId, 'statusReason', null);

    this.service.installApplication(appId, data.params).catch(() => {
      this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
      this.store.updateAppProperty(
        appId,
        'requestReason',
        s__('ClusterIntegration|Request to begin installing failed'),
      );
    });
257 258
  }

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
  upgradeApplication(data) {
    const appId = data.id;
    this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUESTED);
    this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
    this.service.installApplication(appId, data.params).catch(() => this.upgradeFailed(appId));
  }

  upgradeFailed(appId) {
    this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUEST_FAILURE);
  }

  dismissUpgradeSuccess(appId) {
    this.store.updateAppProperty(appId, 'requestStatus', null);
  }

274 275 276 277 278 279 280 281 282 283 284 285
  destroy() {
    this.destroyed = true;

    this.removeListeners();

    if (this.poll) {
      this.poll.stop();
    }

    this.applications.$destroy();
  }
}