debian-mirror-gitlab/app/assets/javascripts/clusters/clusters_bundle.js

569 lines
18 KiB
JavaScript
Raw Normal View History

2018-03-17 18:26:18 +05:30
import Visibility from 'visibilityjs';
import Vue from 'vue';
2019-07-31 22:56:46 +05:30
import { GlToast } from '@gitlab/ui';
2020-01-01 13:55:28 +05:30
import AccessorUtilities from '~/lib/utils/accessor';
2019-02-15 15:39:39 +05:30
import PersistentUserCallout from '../persistent_user_callout';
2018-03-17 18:26:18 +05:30
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';
2020-04-22 19:07:51 +05:30
import {
APPLICATION_STATUS,
INGRESS,
INGRESS_DOMAIN_SUFFIX,
CROSSPLANE,
KNATIVE,
2020-05-24 23:13:21 +05:30
FLUENTD,
2020-04-22 19:07:51 +05:30
} from './constants';
2018-03-17 18:26:18 +05:30
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
2019-02-15 15:39:39 +05:30
import Applications from './components/applications.vue';
2020-01-01 13:55:28 +05:30
import RemoveClusterConfirmation from './components/remove_cluster_confirmation.vue';
2018-03-17 18:26:18 +05:30
import setupToggleButtons from '../toggle_buttons';
2019-12-26 22:10:19 +05:30
import initProjectSelectDropdown from '~/project_select';
2020-04-22 19:07:51 +05:30
import initServerlessSurveyBanner from '~/serverless/survey_banner';
2018-03-17 18:26:18 +05:30
2019-12-04 20:38:33 +05:30
const Environments = () => import('ee_component/clusters/components/environments.vue');
2019-07-31 22:56:46 +05:30
Vue.use(GlToast);
2018-03-17 18:26:18 +05:30
/**
* 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,
2019-02-15 15:39:39 +05:30
installCertManagerPath,
2018-03-17 18:26:18 +05:30
installRunnerPath,
2018-11-08 19:23:39 +05:30
installJupyterPath,
2018-12-13 13:39:08 +05:30
installKnativePath,
2019-07-07 11:18:12 +05:30
updateKnativePath,
2019-12-26 22:10:19 +05:30
installElasticStackPath,
installCrossplanePath,
2018-03-17 18:26:18 +05:30
installPrometheusPath,
2020-05-24 23:13:21 +05:30
installFluentdPath,
2018-03-17 18:26:18 +05:30
managePrometheusPath,
2019-12-04 20:38:33 +05:30
clusterEnvironmentsPath,
2019-02-15 15:39:39 +05:30
hasRbac,
2019-12-21 20:55:43 +05:30
providerType,
preInstalledKnative,
2019-02-15 15:39:39 +05:30
clusterType,
2018-03-17 18:26:18 +05:30
clusterStatus,
clusterStatusReason,
helpPath,
ingressHelpPath,
2018-03-27 19:54:05 +05:30
ingressDnsHelpPath,
2020-03-13 15:44:24 +05:30
ingressModSecurityHelpPath,
2019-12-04 20:38:33 +05:30
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
2019-12-21 20:55:43 +05:30
cloudRunHelpPath,
2019-09-04 21:01:54 +05:30
clusterId,
2018-03-17 18:26:18 +05:30
} = document.querySelector('.js-edit-cluster-form').dataset;
2019-09-04 21:01:54 +05:30
this.clusterId = clusterId;
2019-10-12 21:52:04 +05:30
this.clusterNewlyCreatedKey = `cluster_${this.clusterId}_newly_created`;
this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`;
2018-03-17 18:26:18 +05:30
this.store = new ClustersStore();
2019-12-04 20:38:33 +05:30
this.store.setHelpPaths(
helpPath,
ingressHelpPath,
ingressDnsHelpPath,
2020-03-13 15:44:24 +05:30
ingressModSecurityHelpPath,
2019-12-04 20:38:33 +05:30
environmentsHelpPath,
clustersHelpPath,
deployBoardsHelpPath,
2019-12-21 20:55:43 +05:30
cloudRunHelpPath,
2019-12-04 20:38:33 +05:30
);
2018-03-17 18:26:18 +05:30
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
2019-12-21 20:55:43 +05:30
this.store.updateProviderType(providerType);
this.store.updatePreInstalledKnative(preInstalledKnative);
2019-02-15 15:39:39 +05:30
this.store.updateRbac(hasRbac);
2018-03-17 18:26:18 +05:30
this.service = new ClustersService({
endpoint: statusPath,
installHelmEndpoint: installHelmPath,
installIngressEndpoint: installIngressPath,
2019-02-15 15:39:39 +05:30
installCertManagerEndpoint: installCertManagerPath,
2019-12-26 22:10:19 +05:30
installCrossplaneEndpoint: installCrossplanePath,
2018-03-17 18:26:18 +05:30
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
2018-11-08 19:23:39 +05:30
installJupyterEndpoint: installJupyterPath,
2018-12-13 13:39:08 +05:30
installKnativeEndpoint: installKnativePath,
2019-07-07 11:18:12 +05:30
updateKnativeEndpoint: updateKnativePath,
2019-12-26 22:10:19 +05:30
installElasticStackEndpoint: installElasticStackPath,
2019-12-04 20:38:33 +05:30
clusterEnvironmentsEndpoint: clusterEnvironmentsPath,
2020-05-24 23:13:21 +05:30
installFluentdEndpoint: installFluentdPath,
2018-03-17 18:26:18 +05:30
});
this.installApplication = this.installApplication.bind(this);
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating');
2019-09-04 21:01:54 +05:30
this.unreachableContainer = document.querySelector('.js-cluster-api-unreachable');
this.authenticationFailureContainer = document.querySelector(
'.js-cluster-authentication-failure',
);
2018-03-17 18:26:18 +05:30
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.tokenField = document.querySelector('.js-cluster-token');
2019-07-07 11:18:12 +05:30
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
2019-10-12 21:52:04 +05:30
this.ingressDomainSnippet =
this.ingressDomainHelpText &&
this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
2018-03-17 18:26:18 +05:30
2019-12-26 22:10:19 +05:30
initProjectSelectDropdown();
2019-02-15 15:39:39 +05:30
Clusters.initDismissableCallout();
2018-03-17 18:26:18 +05:30
initSettingsPanels();
2019-12-26 22:10:19 +05:30
2019-10-12 21:52:04 +05:30
const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
if (toggleButtonsContainer) {
setupToggleButtons(toggleButtonsContainer);
}
2019-02-15 15:39:39 +05:30
this.initApplications(clusterType);
2019-12-04 20:38:33 +05:30
this.initEnvironments();
if (clusterEnvironmentsPath && this.environments) {
this.store.toggleFetchEnvironments(true);
this.initPolling(
'fetchClusterEnvironments',
data => this.handleClusterEnvironmentsSuccess(data),
() => this.handleEnvironmentsPollError(),
);
}
2018-03-17 18:26:18 +05:30
2019-10-12 21:52:04 +05:30
this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
2018-03-17 18:26:18 +05:30
this.addListeners();
2019-12-04 20:38:33 +05:30
if (statusPath && !this.environments) {
this.initPolling(
'fetchClusterStatus',
data => this.handleClusterStatusSuccess(data),
() => this.handlePollError(),
);
2018-03-17 18:26:18 +05:30
}
2020-01-01 13:55:28 +05:30
this.initRemoveClusterActions();
2018-03-17 18:26:18 +05:30
}
2019-02-15 15:39:39 +05:30
initApplications(type) {
2018-11-08 19:23:39 +05:30
const { store } = this;
2018-03-17 18:26:18 +05:30
const el = document.querySelector('#js-cluster-applications');
this.applications = new Vue({
el,
data() {
return {
state: store.state,
};
},
render(createElement) {
2019-02-15 15:39:39 +05:30
return createElement(Applications, {
2018-03-17 18:26:18 +05:30
props: {
2019-02-15 15:39:39 +05:30
type,
2018-03-17 18:26:18 +05:30
applications: this.state.applications,
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
2018-03-27 19:54:05 +05:30
ingressDnsHelpPath: this.state.ingressDnsHelpPath,
2020-03-13 15:44:24 +05:30
ingressModSecurityHelpPath: this.state.ingressModSecurityHelpPath,
2019-12-21 20:55:43 +05:30
cloudRunHelpPath: this.state.cloudRunHelpPath,
providerType: this.state.providerType,
preInstalledKnative: this.state.preInstalledKnative,
2019-02-15 15:39:39 +05:30
rbac: this.state.rbac,
2018-03-17 18:26:18 +05:30
},
});
},
});
}
2019-12-04 20:38:33 +05:30
initEnvironments() {
const { store } = this;
const el = document.querySelector('#js-cluster-environments');
if (!el) {
return;
}
this.environments = new Vue({
el,
data() {
return {
state: store.state,
};
},
render(createElement) {
return createElement(Environments, {
props: {
isFetching: this.state.fetchingEnvironments,
environments: this.state.environments,
environmentsHelpPath: this.state.environmentsHelpPath,
clustersHelpPath: this.state.clustersHelpPath,
deployBoardsHelpPath: this.state.deployBoardsHelpPath,
},
});
},
});
}
2020-01-01 13:55:28 +05:30
initRemoveClusterActions() {
const el = document.querySelector('#js-cluster-remove-actions');
if (el && el.dataset) {
2020-07-28 23:09:34 +05:30
const { clusterName, clusterPath, hasManagementProject } = el.dataset;
2020-01-01 13:55:28 +05:30
this.removeClusterAction = new Vue({
el,
render(createElement) {
return createElement(RemoveClusterConfirmation, {
props: {
clusterName,
clusterPath,
2020-07-28 23:09:34 +05:30
hasManagementProject,
2020-01-01 13:55:28 +05:30
},
});
},
});
}
}
2019-12-04 20:38:33 +05:30
handleClusterEnvironmentsSuccess(data) {
this.store.toggleFetchEnvironments(false);
this.store.updateEnvironments(data.data);
}
2019-02-15 15:39:39 +05:30
static initDismissableCallout() {
const callout = document.querySelector('.js-cluster-security-warning');
2019-07-07 11:18:12 +05:30
PersistentUserCallout.factory(callout);
2019-02-15 15:39:39 +05:30
}
2019-09-04 21:01:54 +05:30
addBannerCloseHandler(el, status) {
el.querySelector('.js-close-banner').addEventListener('click', () => {
el.classList.add('hidden');
this.setBannerDismissedState(status, true);
});
}
2018-03-17 18:26:18 +05:30
addListeners() {
eventHub.$on('installApplication', this.installApplication);
2019-09-04 21:01:54 +05:30
eventHub.$on('updateApplication', data => this.updateApplication(data));
2019-07-07 11:18:12 +05:30
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
2020-04-22 19:07:51 +05:30
eventHub.$on('setKnativeDomain', data => this.setKnativeDomain(data));
2019-07-31 22:56:46 +05:30
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
2019-12-26 22:10:19 +05:30
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
2020-04-08 14:13:33 +05:30
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
2020-04-22 19:07:51 +05:30
eventHub.$on('setIngressModSecurityMode', data => this.setIngressModSecurityMode(data));
eventHub.$on('resetIngressModSecurityChanges', id => this.resetIngressModSecurityChanges(id));
2020-05-24 23:13:21 +05:30
eventHub.$on('setFluentdSettings', data => this.setFluentdSettings(data));
2019-09-04 21:01:54 +05:30
// Add event listener to all the banner close buttons
this.addBannerCloseHandler(this.unreachableContainer, 'unreachable');
this.addBannerCloseHandler(this.authenticationFailureContainer, 'authentication_failure');
2018-03-17 18:26:18 +05:30
}
removeListeners() {
eventHub.$off('installApplication', this.installApplication);
2019-09-04 21:01:54 +05:30
eventHub.$off('updateApplication', this.updateApplication);
2019-07-07 11:18:12 +05:30
eventHub.$off('saveKnativeDomain');
2020-04-22 19:07:51 +05:30
eventHub.$off('setKnativeDomain');
2019-12-26 22:10:19 +05:30
eventHub.$off('setCrossplaneProviderStack');
2019-07-31 22:56:46 +05:30
eventHub.$off('uninstallApplication');
2020-04-08 14:13:33 +05:30
eventHub.$off('setIngressModSecurityEnabled');
2020-04-22 19:07:51 +05:30
eventHub.$off('setIngressModSecurityMode');
eventHub.$off('resetIngressModSecurityChanges');
2020-05-24 23:13:21 +05:30
eventHub.$off('setFluentdSettings');
2018-03-17 18:26:18 +05:30
}
2019-12-04 20:38:33 +05:30
initPolling(method, successCallback, errorCallback) {
2018-03-17 18:26:18 +05:30
this.poll = new Poll({
resource: this.service,
2019-12-04 20:38:33 +05:30
method,
successCallback,
errorCallback,
2018-03-17 18:26:18 +05:30
});
if (!Visibility.hidden()) {
this.poll.makeRequest();
}
Visibility.change(() => {
if (!Visibility.hidden() && !this.destroyed) {
this.poll.restart();
} else {
this.poll.stop();
}
});
}
2019-12-04 20:38:33 +05:30
handlePollError() {
this.constructor.handleError();
}
handleEnvironmentsPollError() {
this.store.toggleFetchEnvironments(false);
this.handlePollError();
}
2018-03-17 18:26:18 +05:30
static handleError() {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
}
2019-12-04 20:38:33 +05:30
handleClusterStatusSuccess(data) {
2018-03-17 18:26:18 +05:30
const prevStatus = this.store.state.status;
2020-05-24 23:13:21 +05:30
const prevApplicationMap = { ...this.store.state.applications };
2018-03-17 18:26:18 +05:30
this.store.updateStateFromServer(data.data);
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
2020-04-08 14:13:33 +05:30
if (this.ingressDomainHelpText) {
this.toggleIngressDomainHelpText(
prevApplicationMap[INGRESS],
this.store.state.applications[INGRESS],
);
}
2020-04-22 19:07:51 +05:30
if (this.store.state.applications[KNATIVE]?.status === APPLICATION_STATUS.INSTALLED) {
initServerlessSurveyBanner();
}
2018-03-17 18:26:18 +05:30
}
hideAll() {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
2019-09-04 21:01:54 +05:30
this.unreachableContainer.classList.add('hidden');
this.authenticationFailureContainer.classList.add('hidden');
2018-03-17 18:26:18 +05:30
}
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
const appTitles = Object.keys(newApplicationMap)
2018-11-20 20:47:30 +05:30
.filter(
appId =>
newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
prevApplicationMap[appId].status !== null,
)
2018-03-17 18:26:18 +05:30
.map(appId => newApplicationMap[appId].title);
if (appTitles.length > 0) {
2018-11-20 20:47:30 +05:30
const text = sprintf(
s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
{
appList: appTitles.join(', '),
},
);
2018-03-17 18:26:18 +05:30
Flash(text, 'notice', this.successApplicationContainer);
}
}
2019-09-04 21:01:54 +05:30
setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
2019-10-12 21:52:04 +05:30
window.localStorage.setItem(this.clusterBannerDismissedKey, `${status}_${isDismissed}`);
2019-09-04 21:01:54 +05:30
}
}
isBannerDismissed(status) {
let bannerState;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
2019-10-12 21:52:04 +05:30
bannerState = window.localStorage.getItem(this.clusterBannerDismissedKey);
2019-09-04 21:01:54 +05:30
}
return bannerState === `${status}_true`;
}
2019-10-12 21:52:04 +05:30
setClusterNewlyCreated(state) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
window.localStorage.setItem(this.clusterNewlyCreatedKey, Boolean(state));
}
}
isClusterNewlyCreated() {
// once this is true, it will always be true for a given page load
if (!this.isNewlyCreated) {
let newlyCreated;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
newlyCreated = window.localStorage.getItem(this.clusterNewlyCreatedKey);
}
this.isNewlyCreated = newlyCreated === 'true';
}
return this.isNewlyCreated;
}
2018-03-17 18:26:18 +05:30
2019-10-12 21:52:04 +05:30
updateContainer(prevStatus, status, error) {
if (status !== 'created' && this.isBannerDismissed(status)) {
2019-09-04 21:01:54 +05:30
return;
}
this.setBannerDismissedState(status, false);
2019-10-12 21:52:04 +05:30
if (prevStatus !== status) {
this.hideAll();
2018-03-17 18:26:18 +05:30
switch (status) {
case 'created':
2019-10-12 21:52:04 +05:30
if (this.isClusterNewlyCreated()) {
this.setClusterNewlyCreated(false);
this.successContainer.classList.remove('hidden');
} else if (prevStatus) {
this.setClusterNewlyCreated(true);
window.location.reload();
}
2018-03-17 18:26:18 +05:30
break;
case 'errored':
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
2019-09-04 21:01:54 +05:30
case 'unreachable':
this.unreachableContainer.classList.remove('hidden');
break;
case 'authentication_failure':
this.authenticationFailureContainer.classList.remove('hidden');
break;
2018-03-17 18:26:18 +05:30
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
break;
default:
}
}
}
2019-07-31 22:56:46 +05:30
installApplication({ id: appId, params }) {
2019-12-26 22:10:19 +05:30
return Clusters.validateInstallation(appId, params)
.then(() => {
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
this.store.installApplication(appId);
// eslint-disable-next-line promise/no-nesting
this.service.installApplication(appId, params).catch(() => {
this.store.notifyInstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
s__('ClusterIntegration|Request to begin installing failed'),
);
});
})
.catch(error => this.store.updateAppProperty(appId, 'validationError', error));
}
2019-03-02 22:35:43 +05:30
2019-12-26 22:10:19 +05:30
static validateInstallation(appId, params) {
return new Promise((resolve, reject) => {
if (appId === CROSSPLANE && !params.stack) {
reject(s__('ClusterIntegration|Select a stack to install Crossplane.'));
return;
}
2019-07-31 22:56:46 +05:30
2020-06-23 00:09:42 +05:30
if (appId === KNATIVE && !params.hostname && !params.pages_domain_id) {
reject(s__('ClusterIntegration|You must specify a domain before you can install Knative.'));
return;
}
2019-12-26 22:10:19 +05:30
resolve();
2019-03-02 22:35:43 +05:30
});
}
2019-07-31 22:56:46 +05:30
uninstallApplication({ id: appId }) {
this.store.updateAppProperty(appId, 'requestReason', null);
this.store.updateAppProperty(appId, 'statusReason', null);
this.store.uninstallApplication(appId);
return this.service.uninstallApplication(appId).catch(() => {
this.store.notifyUninstallFailure(appId);
this.store.updateAppProperty(
appId,
'requestReason',
s__('ClusterIntegration|Request to begin uninstalling failed'),
);
});
}
2019-09-04 21:01:54 +05:30
updateApplication({ id: appId, params }) {
2019-07-31 22:56:46 +05:30
this.store.updateApplication(appId);
2019-09-04 21:01:54 +05:30
this.service.installApplication(appId, params).catch(() => {
2019-07-31 22:56:46 +05:30
this.store.notifyUpdateFailure(appId);
});
2019-03-02 22:35:43 +05:30
}
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
setFluentdSettings(settings = {}) {
Object.entries(settings).forEach(([key, value]) => {
this.store.updateAppProperty(FLUENTD, key, value);
});
}
2019-07-31 22:56:46 +05:30
toggleIngressDomainHelpText({ externalIp }, { externalIp: newExternalIp }) {
if (externalIp !== newExternalIp) {
this.ingressDomainHelpText.classList.toggle('hide', !newExternalIp);
this.ingressDomainSnippet.textContent = `${newExternalIp}${INGRESS_DOMAIN_SUFFIX}`;
2019-07-07 11:18:12 +05:30
}
}
saveKnativeDomain(data) {
const appId = data.id;
2019-09-04 21:01:54 +05:30
this.store.updateApplication(appId);
this.service.updateApplication(appId, data.params).catch(() => {
this.store.notifyUpdateFailure(appId);
});
2019-07-07 11:18:12 +05:30
}
2020-04-22 19:07:51 +05:30
setKnativeDomain({ id: appId, domain, domainId }) {
this.store.updateAppProperty(appId, 'isEditingDomain', true);
this.store.updateAppProperty(appId, 'hostname', domain);
this.store.updateAppProperty(appId, 'pagesDomain', domainId ? { id: domainId, domain } : null);
2020-06-23 00:09:42 +05:30
this.store.updateAppProperty(appId, 'validationError', null);
2019-07-07 11:18:12 +05:30
}
2019-12-26 22:10:19 +05:30
setCrossplaneProviderStack(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'stack', data.stack.code);
this.store.updateAppProperty(appId, 'validationError', null);
}
2020-04-08 14:13:33 +05:30
setIngressModSecurityEnabled({ id, modSecurityEnabled }) {
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', true);
this.store.updateAppProperty(id, 'modsecurity_enabled', modSecurityEnabled);
}
2020-04-22 19:07:51 +05:30
setIngressModSecurityMode({ id, modSecurityMode }) {
this.store.updateAppProperty(id, 'isEditingModSecurityMode', true);
this.store.updateAppProperty(id, 'modsecurity_mode', modSecurityMode);
}
resetIngressModSecurityChanges(id) {
2020-04-08 14:13:33 +05:30
this.store.updateAppProperty(id, 'isEditingModSecurityEnabled', false);
2020-04-22 19:07:51 +05:30
this.store.updateAppProperty(id, 'isEditingModSecurityMode', false);
2020-04-08 14:13:33 +05:30
}
2018-03-17 18:26:18 +05:30
destroy() {
this.destroyed = true;
this.removeListeners();
if (this.poll) {
this.poll.stop();
}
2019-12-04 20:38:33 +05:30
if (this.environments) {
this.environments.$destroy();
}
2018-03-17 18:26:18 +05:30
this.applications.$destroy();
}
}