clusters_bundle.js 7.92 KB
Newer Older
1 2
import Visibility from 'visibilityjs';
import Vue from 'vue';
3
import initDismissableCallout from '~/dismissable_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
import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants';
10 11
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
12
import Applications from './components/applications.vue';
13
import setupToggleButtons from '../toggle_buttons';
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

/**
 * 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,
Amit Rathi's avatar
Amit Rathi committed
29
      installCertManagerPath,
30
      installRunnerPath,
31
      installJupyterPath,
Chris Baumbauer's avatar
Chris Baumbauer committed
32
      installKnativePath,
33
      installPrometheusPath,
34
      managePrometheusPath,
35
      clusterType,
36 37 38
      clusterStatus,
      clusterStatusReason,
      helpPath,
39
      ingressHelpPath,
40
      ingressDnsHelpPath,
41 42 43
    } = document.querySelector('.js-edit-cluster-form').dataset;

    this.store = new ClustersStore();
44
    this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
45
    this.store.setManagePrometheusPath(managePrometheusPath);
46 47 48 49 50
    this.store.updateStatus(clusterStatus);
    this.store.updateStatusReason(clusterStatusReason);
    this.service = new ClustersService({
      endpoint: statusPath,
      installHelmEndpoint: installHelmPath,
Kamil Trzcinski's avatar
Kamil Trzcinski committed
51
      installIngressEndpoint: installIngressPath,
Amit Rathi's avatar
Amit Rathi committed
52
      installCertManagerEndpoint: installCertManagerPath,
53
      installRunnerEndpoint: installRunnerPath,
54
      installPrometheusEndpoint: installPrometheusPath,
55
      installJupyterEndpoint: installJupyterPath,
Chris Baumbauer's avatar
Chris Baumbauer committed
56
      installKnativeEndpoint: installKnativePath,
57 58 59
    });

    this.installApplication = this.installApplication.bind(this);
60
    this.showToken = this.showToken.bind(this);
61 62 63 64 65 66

    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');
67 68
    this.showTokenButton = document.querySelector('.js-show-cluster-token');
    this.tokenField = document.querySelector('.js-cluster-token');
69

70
    initDismissableCallout('.js-cluster-security-warning');
71
    initSettingsPanels();
72
    setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
73
    this.initApplications(clusterType);
74 75

    if (this.store.state.status !== 'created') {
76
      this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
77 78 79 80 81 82 83 84
    }

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

85
  initApplications(type) {
86
    const { store } = this;
87 88 89 90 91 92 93 94 95 96
    const el = document.querySelector('#js-cluster-applications');

    this.applications = new Vue({
      el,
      data() {
        return {
          state: store.state,
        };
      },
      render(createElement) {
97
        return createElement(Applications, {
98
          props: {
99
            type,
100 101
            applications: this.state.applications,
            helpPath: this.state.helpPath,
102
            ingressHelpPath: this.state.ingressHelpPath,
103
            managePrometheusPath: this.state.managePrometheusPath,
104
            ingressDnsHelpPath: this.state.ingressDnsHelpPath,
105 106 107 108 109 110 111
          },
        });
      },
    });
  }

  addListeners() {
112
    if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
113 114 115 116
    eventHub.$on('installApplication', this.installApplication);
  }

  removeListeners() {
117
    if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
118 119 120 121 122 123 124 125 126 127 128 129 130 131
    eventHub.$off('installApplication', this.installApplication);
  }

  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 {
132 133
      this.service
        .fetchData()
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
        .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) {
152
    const prevStatus = this.store.state.status;
153 154
    const prevApplicationMap = Object.assign({}, this.store.state.applications);

155
    this.store.updateStateFromServer(data.data);
156

157
    this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
158
    this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
159 160
  }

161 162 163 164 165
  showToken() {
    const type = this.tokenField.getAttribute('type');

    if (type === 'password') {
      this.tokenField.setAttribute('type', 'text');
166
      this.showTokenButton.textContent = s__('ClusterIntegration|Hide');
167 168
    } else {
      this.tokenField.setAttribute('type', 'password');
169
      this.showTokenButton.textContent = s__('ClusterIntegration|Show');
170 171 172
    }
  }

173 174 175 176 177 178 179 180
  hideAll() {
    this.errorContainer.classList.add('hidden');
    this.successContainer.classList.add('hidden');
    this.creatingContainer.classList.add('hidden');
  }

  checkForNewInstalls(prevApplicationMap, newApplicationMap) {
    const appTitles = Object.keys(newApplicationMap)
181 182 183 184 185 186
      .filter(
        appId =>
          newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
          prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
          prevApplicationMap[appId].status !== null,
      )
187 188 189
      .map(appId => newApplicationMap[appId].title);

    if (appTitles.length > 0) {
190 191 192 193 194 195
      const text = sprintf(
        s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
        {
          appList: appTitles.join(', '),
        },
      );
196
      Flash(text, 'notice', this.successApplicationContainer);
197 198 199
    }
  }

200
  updateContainer(prevStatus, status, error) {
201
    this.hideAll();
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

    // 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();
      }
220 221 222
    }
  }

223 224
  installApplication(data) {
    const appId = data.id;
225 226 227
    this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING);
    this.store.updateAppProperty(appId, 'requestReason', null);

228 229
    this.service
      .installApplication(appId, data.params)
230 231 232 233 234
      .then(() => {
        this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS);
      })
      .catch(() => {
        this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
235 236 237 238 239
        this.store.updateAppProperty(
          appId,
          'requestReason',
          s__('ClusterIntegration|Request to begin installing failed'),
        );
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
      });
  }

  destroy() {
    this.destroyed = true;

    this.removeListeners();

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

    this.applications.$destroy();
  }
}